如何在分组项的WPF ItemsControl上使用UI自动化?

9
我正在使用 Microsoft UI Automation(即AutomationElement)来对我的应用程序运行自动化验收测试。这做得很好,但我遇到了一个似乎没有暴露给自动化框架的情况。

我有一个ItemsControl(虽然我也可以使用它的派生控件,例如ListBox),我正在使用CollectionViewSource对项目进行分组。这是一个完整的窗口演示:

<Window x:Class="GroupAutomation.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Orchestra">
    <Window.Resources>

        <!-- Take some simple data -->
        <XmlDataProvider x:Key="SampleData" XPath="Orchestra/Instrument">
            <x:XData>
                <Orchestra xmlns="">
                    <Instrument Name="Flute" Category="Woodwind" />
                    <Instrument Name="Trombone" Category="Brass" />
                    <Instrument Name="French horn" Category="Brass" />
                </Orchestra>
            </x:XData>
        </XmlDataProvider>

        <!-- Add grouping -->
        <CollectionViewSource Source="{Binding Source={StaticResource SampleData}}" x:Key="GroupedView">
            <CollectionViewSource.GroupDescriptions>
                <PropertyGroupDescription PropertyName="@Category" />
            </CollectionViewSource.GroupDescriptions>
        </CollectionViewSource>
    </Window.Resources>

    <!-- Show it in an ItemsControl -->
    <ItemsControl ItemsSource="{Binding Source={StaticResource GroupedView}}" HorizontalAlignment="Left" Margin="4">
        <ItemsControl.GroupStyle>
            <GroupStyle>
                <GroupStyle.HeaderTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Path=Name}" FontWeight="Bold" />
                    </DataTemplate>
                </GroupStyle.HeaderTemplate>
            </GroupStyle>
        </ItemsControl.GroupStyle>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Border Padding="4" Margin="4" Background="#FFDEDEDE">
                    <StackPanel>
                        <Label Content="{Binding XPath=@Name}" />
                        <Button Content="Play" />
                    </StackPanel>
                </Border>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</Window>

这将生成一个窗口,其中包含按类别分组的项目,每个项目都有一个按钮,我想使用UI自动化点击它: 带有列表窗口截图
(来源: brizzly.com) 然而,如果我在UISpy.exe中查看(或使用AutomationElement导航),我只看到了组(即使在原始视图中): UISpy
(来源: brizzly.com) 正如您所看到的,这些组是存在的,但它们不包含任何项目,因此没有地方可以查找按钮。我已经在WPF 3.5 SP1和WPF 4.0中尝试过了,并得到了相同的结果。
是否有可能在分组的项目上使用UI自动化?如果可以,怎么做?

直接使用ItemsControl项,如此所示。链接 - user1912383
4个回答

9

我遇到了这个问题,并通过实现来自《帮助编码UI框架找到您的自定义控件》的“GenericAutomationPeer”,并为GroupItem添加特殊情况来解决它。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Automation;
using System.Windows.Automation.Peers;
using System.Windows.Media;
using System.Xml;

namespace ClassLibrary1
{
    public class MyItemsControl : ItemsControl
    {
        protected override AutomationPeer OnCreateAutomationPeer()
        {
            return new GenericAutomationPeer(this);
        }
    }

    public class GenericAutomationPeer : UIElementAutomationPeer
    {
        public GenericAutomationPeer(UIElement owner) : base(owner)
        {
        }
        
        protected override List<AutomationPeer> GetChildrenCore()
        {
            var list = base.GetChildrenCore();
            list.AddRange(GetChildPeers(Owner));
            return list;
        }

        private List<AutomationPeer> GetChildPeers(UIElement element)
        {
            var list = new List<AutomationPeer>();
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
            {
                var child = VisualTreeHelper.GetChild(element, i) as UIElement;
                if (child != null)
                {
                    AutomationPeer childPeer;
                    if (child is GroupItem)
                    {
                        childPeer = new GenericAutomationPeer(child);
                    }
                    else
                    {
                        childPeer = UIElementAutomationPeer.CreatePeerForElement(child);
                    }
                    if (childPeer != null)
                    {
                        list.Add(childPeer);
                    }
                    else
                    {
                        list.AddRange(GetChildPeers(child));
                    }
                }
            }
            return list;
        }
    }

}

我希望这能帮助那些仍在寻找答案的人!

这不是理想的解决方案,但是考虑到修复分组 ItemsControls 的 AutomationPeer 需要付出的努力,这是一种非常有效的解决方法。谢谢! - grzegorz_p

4
我不确定按钮是否存在这个问题,但是在DataTemplate中的TextBlock控件不会被放入UI自动化树。显然这是一种优化方式,以避免产生数千个不必要的文本块。

您可以通过子类化TextBlock来解决此问题。下面是我的实现:

public class AutomatableTextBlock : TextBlock
{
    protected override AutomationPeer OnCreateAutomationPeer()
    {
        return new AutomatableTextBlockAutomationPeer(this);
    }

    class AutomatableTextBlockAutomationPeer : TextBlockAutomationPeer
    {
        public AutomatableTextBlockAutomationPeer(TextBlock owner)
            : base(owner)
        { }

        protected override bool IsControlElementCore()
        { return true; }
    }
}

注意:UI自动化也不会暴露Canvas、Panel等其他控件,您可以使用类似的子类来显示它们。
话虽如此,我不确定为什么按钮没有出现.... 嗯。

如果禁用分组,我需要的元素就会正常显示,所以恐怕你的答案没有帮助。我在示例中使用按钮是为了清晰明了,因为它们总是会出现(除了这种情况!)。 - GraemeF

1
你使用什么工具来编写自动化脚本?我认为应该有一种选项可以深入WPF的逻辑/视觉树,而不是依赖于Win32树(如UISpy所显示的)。
如果你使用Snoop查看同一应用程序,你将看到完整的视觉和逻辑树。

我正在使用MbUnit(但也可以是NUnit、xUnit.NET或其他)编写验收测试,并使用UI自动化框架驱动应用程序。这通过处理用户可以看到和/或交互的UI项来在某种程度上将测试与实现解耦,而不是直接查看WPF树。我已经这样做了一年左右,这是我发现的第一个不能“正常工作”的情况,因此我希望填补这个空白,而不是在这个阶段切换框架。 - GraemeF

1
我最终在我的应用程序中使用 TreeWalker.RawViewWalker 手动导航树来解决这个问题,之前使用 AutomationElement.FindFirst 找到模板后似乎无法可靠地排除所有你想要的信息。当自动化别人的应用程序时,FindFirst 似乎会排除所有你想要的信息。而 RawViewWalker 在 "检查对象" 中显示元素时有效,但在 UISpy 或你的应用程序中则不行。

成功了!有趣的事实是,UISpy在一台电脑上找不到子节点,但在另一台电脑上找到了。两台电脑具有相同的规格和安装了相同的软件。无论如何,使用TreeWalker的技巧对两台电脑都有效。 - SlavaGu
在这个分组情况下,RawViewWalker 对我似乎不起作用。调用组项元素的 RawViewWalker.GetFirstChild 仍然返回没有子项。 - Lukáš Koten

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接