AutomationPeer

本文介绍了如何在WPF中为第三方控件ActiPro的TreeListBox创建AutomationPeer,以支持微软的TAF技术进行自动化测试。通过派生自己的TreeListBox,选择适当的AutomationPeer基类并重写关键方法如GetClassNameCore和GetChildrenCore,实现树形结构的自动化支持。遇到的问题包括第三方控件类型不匹配导致的自动化难度,以及使用Inspect.exe工具进行验证。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

微软的TAF技术,可以实现自动化测试,或是对其他进程进行UI操作。https://blog.youkuaiyun.com/jfyy/article/details/80700977

参考:https://www.codeproject.com/Articles/172391/UIAutomation-Coded-UI-Tests-AutomationPeer-and-WPF

https://docs.microsoft.com/en-us/dotnet/framework/wpf/controls/ui-automation-of-a-wpf-custom-control

 

为了在WPF里支持这套技术,微软使用了Peer对象,并为其标准控件(button,textblock)制作了对应的Peer类,例如ButtonAutomationPeer。并在控件的protected override AutomationPeer OnCreateAutomationPeer()方法里,实例化对应的Peer类。

 

但如果是我们制作的Customize控件,或使用第三方的控件,因为没有对应地制作Peer类,就不能支持TAF。下面的例子就是我在研究如何为第三方ActiPro的TreeListBox控件,制作Peer的过程。

 

1,先如下派生自己的TreeListBox,并实现OnCreateAutomationPeer()

    public class AutomationTreeListBox : TreeListBox
    {
        protected override AutomationPeer OnCreateAutomationPeer()
        {
            return new TreeListAutomationPeer(this);
        }
    }

只要我们能够制作出TreeListAutomationPeer,就能完美支持TAF。

 

2, TreeListAutomationPeer基类的选择

没必要重复做轮子,选择恰当的AutomationPeer基类会减少很多工作量。

如果是制作的是一个简单Control, 可使用FrameworkElementAutomationPeer。
如果是制作一个TabControl,可使用TabControlAutomationPeer。

所以我第一时间想选择TreeViewAutomationPeer作为基类,可第三方ActiPro的TreeListBox控件是派生于ItemsControl,而TreeViewAutomationPeer构造函数需要传入的是TreeView控件。因此TreeViewAutomationPeer作为基类是不可能的。故最终选择ItemsControlAutomationPeer作为我们的基类。

 

3,主要的Override方法:GetClassNameCore()和GetChildrenCore()。

尤其是GetChildrenCore()。 因为树结构,每个节点的GetChildrenCore如果都能返回正确的List<AutomationPeer>,我们的工作基本就完成了。

我的思路是:

  • 为每个TreeNode定义TreeNodeAutomationPeer类,其基类是ItemsControlAutomationPeer。
  • 重写TreeNodeAutomationPeer类的GetChildrenCore()方法,根据Children数据先找到所有Children的UIElment。针对每一个UIElment,var peer = new TreeNodeAutomationPeer(treeNode, ctrl),最后将peer加入list后返回。

采用上面的思路,找到Root节点,并找到其UIElement:rootCtrl。按上面的方法return new List<TreeNodeAutomationPeer>() { new TreeNodeAutomationPeer(Root, rootCtrl)。该方法递会用归方式完成tree所有节点AutomationPeer的创建。

 

问题出来了!!!

第三方节点的类型是TreeListBoxItem,它派生于ContentControl,不是ItemsControl。这样无法用它来创建ItemsControlAutomationPeer。(一般来说,针对有多个子节点的控件,一般都会采用ItemsControl。)

所以,后来我只能用var peer = new FrameworkElementAutomationPeer(ctrl);来完成对根节点的构建。子节点无法再构建了。

(网上有这种写法,var peer = UIElementAutomationPeer.CreatePeerForElement(MyOwner.PART_Header); 但peer返回null,原因不明,放弃使用。)

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Automation.Peers;
using System.Windows.Controls;
using System.Windows.Media;
using ActiproSoftware.Windows.Controls.Grids;
using RA.CCW.OrganizerControl;

namespace RA.CCW.ControllerOrganizer.TreeListControl
{
    public class TreeListAutomationPeer : ItemsControlAutomationPeer
    {
        public TreeListAutomationPeer(AutomationTreeListBox owner) : base(owner)
        {
        }

        protected override string GetClassNameCore()
        {
            return "AutomationTreeListBox";
        }

        public override object GetPattern(PatternInterface patternInterface)
        {
            if (patternInterface == PatternInterface.ExpandCollapse)
            {
                return this;
            }

            return base.GetPattern(patternInterface);
        }

        protected override ItemAutomationPeer CreateItemAutomationPeer(object item)
        {
            return null;
        }


        // todo!!!!
        protected override List<AutomationPeer> GetChildrenCore()
        {
            var ret = new List<AutomationPeer>();
            if(TreeList.RootItem != null)
            {
                var root = TreeList.RootItem as TreeNodeModel;
                if (root != null)
                {
                    var peer = GetTreeNodeAutomationPeer(root);
                    if(peer != null)
                        ret.Add(peer);
                }
            }
            return ret;
        }

 
        protected AutomationPeer GetTreeNodeAutomationPeer(TreeNodeModel treeNode)
        {
            var ctrl = SearchVisualTree<TreeListBoxItem>(TreeList, x => x.Name == treeNode.Name);
            if (ctrl != null)
            {
                var peer = new FrameworkElementAutomationPeer(ctrl);
                //var peer = new ItemsControlAutomationPeer(ctrl); // can't create ItemsControlAutomationPeer...
                return peer;
            }

            return null;
        }

        // need???
        protected override AutomationControlType GetAutomationControlTypeCore()
        {
            return AutomationControlType.Tree;
        }

        private AutomationTreeListBox TreeList
        {
            get { return (AutomationTreeListBox)base.Owner; }
        }

        private T SearchVisualTree<T>(DependencyObject tarElem) where T : DependencyObject
        {
            var count = VisualTreeHelper.GetChildrenCount(tarElem);
            if (count == 0)
                return null;

            for (int i = 0; i < count; ++i)
            {
                var child = VisualTreeHelper.GetChild(tarElem, i);
                if (child != null && child is T)
                {
                    return (T)child;
                }
                else
                {
                    var res = SearchVisualTree<T>(child);
                    if (res != null)
                    {
                        return res;
                    }
                }
            }

            return null;
        }

        private T SearchVisualTree<T>(DependencyObject tarElem, Func<T, bool> predicate) where T : DependencyObject
        {
            var count = VisualTreeHelper.GetChildrenCount(tarElem);
            if (count == 0)
                return null;

            for (int i = 0; i < count; ++i)
            {
                var child = VisualTreeHelper.GetChild(tarElem, i);
                if (child != null && child is T && predicate(child as T))
                {
                    return (T)child;
                }
                else
                {
                    var res = SearchVisualTree<T>(child);
                    if (res != null)
                    {
                        return res;
                    }
                }
            }

            return null;
        }

        private TextBlock SearchVisualTree(DependencyObject tarElem, string name) 
        {
            var count = VisualTreeHelper.GetChildrenCount(tarElem);
            if (count == 0)
                return null;

            for (int i = 0; i < count; ++i)
            {
                var child = VisualTreeHelper.GetChild(tarElem, i);
                if (child != null && child is TreeListBoxItem && ((TreeListBoxItem)child).Name == name)
                {
                    return child as TextBlock;
                }
                else
                {
                    var res = SearchVisualTree(child, name);
                    if (res != null)
                    {
                        return res;
                    }
                }
            }

            return null;
        }
    }

    class TreeNodeAutomationPeer : ItemsControlAutomationPeer
    {
    }

    public class AutomationTreeListBox : TreeListBox
    {
        protected override AutomationPeer OnCreateAutomationPeer()
        {
            return new TreeListAutomationPeer(this);
        }
    }

}

 

另外:

我们用Inspect.exe来观看控件的层次结构。当然也可以来验证实现Automation是否成功。

另外,上面说得都是WPF程序。如果对应的是Winform的程序,微软使用的是Implement Pattern Providers。这个尚未研究。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值