Как использовать UI Automation в WPF ItemsControl, который группирует элементы? - PullRequest
4 голосов
/ 05 мая 2010

Я использую 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>

Это создает окно, содержащее элементы, сгруппированные по их категориям, и у каждого элемента есть кнопка, которую я хотел бы нажать с помощью автоматизации пользовательского интерфейса:

Скриншот окна со списком http://pics.brizzly.com/thumb_lg_2C45.jpg

Однако, если я смотрю в UISpy.exe (или перемещаюсь с помощью AutomationElement), я вижу только группы (даже в режиме Raw):

UISpy http://pics.brizzly.com/thumb_lg_2C47.jpg

Как видите, группы есть, но в них нет элементов, поэтому искать кнопки некуда. Я пробовал это и в WPF 3.5 SP1, и в WPF 4.0 и получал одинаковый результат.

Можно ли использовать UI Automation для элементов, которые сгруппированы, и если да, то как?

Ответы [ 4 ]

5 голосов
/ 02 августа 2016

Я столкнулся с этой проблемой и сумел решить ее, реализовав GenericAutomationPeer из http://www.colinsalmcorner.com/post/genericautomationpeer--helping-the-coded-ui-framework-find-your-custom-controls и добавив специальный случай для GroupItem s.

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;
        }
    }

}

Надеюсь, это поможет любому, кто все еще ищет ответ!

2 голосов
/ 17 мая 2010

Я не уверен на 100% в кнопках, но TextBlock элементы управления, которые находятся внутри DataTemplate s, делают , а не , помещаются в дерево автоматизации пользовательского интерфейса. По-видимому, это оптимизация, позволяющая избежать ненужных текстовых блоков тысячами.

Вы можете обойти это, используя подкласс 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 Automation также не предоставляет различные другие элементы управления, такие как Canvas, Panel, вы можете заставить их отображаться в аналогичном подклассе.

Говоря это, я не уверен, почему Button не появляется .... Хмммм

2 голосов
/ 17 ноября 2011

Я решил эту проблему в своем приложении, используя TreeWalker.RawViewWalker для ручной навигации по дереву после использования AutomationElement.FindFirst для поиска шаблона. FindFirst, по-видимому, надежно исключает всю информацию, которую вы хотите при автоматизации чужого приложения. RawViewWalker, кажется, работает, когда элементы отображаются в «Inspect Objects», но не в UISpy или вашем приложении.

1 голос
/ 05 мая 2010

Какие инструменты вы используете для написания автоматизированных скриптов? Я бы подумал, что будет возможность детализировать логические / визуальные деревья WPF вместо того, чтобы полагаться на дерево Win32 (как показано в UISpy).

Если вы посмотрите на то же приложение, используя Snoop , вы увидите полное визуальное и логическое деревья.

...