开发者

How do I use UI Automation on a WPF ItemsControl that groups items?

I am using Microsoft UI Automation (i.e. AutomationElement) to run automated acceptance tests against my application. This has gone well, but I've hit a situation that doesn't appear to be exposed to the automation framework.

I have an ItemsControl (although I could be using one of its derived controls, e.g. ListBox) and I am using CollectionViewSource to group items. Here is a complete window to demonstrate:

<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" />
            </CollectionView开发者_运维问答Source.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>

This produces a window containing the items grouped into their categories, and each item has a button that I'd like to click with UI Automation:

How do I use UI Automation on a WPF ItemsControl that groups items?

(source: brizzly.com)

However, if I look in UISpy.exe (or navigate with AutomationElement) I only see the groups (even in the Raw view):

How do I use UI Automation on a WPF ItemsControl that groups items?

(source: brizzly.com)

As you can see, the groups are there but they contain no items, so there is nowhere to look for the buttons. I have tried this in both WPF 3.5 SP1 and WPF 4.0 and get the same result.

Is it possible to use UI Automation on items that are grouped, and if so, how?


I came across this problem and managed to solve it by implementing a 'GenericAutomationPeer' from

  • Helping the Coded UI Framework Find Your Custom Controls

and adding a special case for GroupItems.

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

}

I hope this helps anyone still searching for an answer!


I'm not 100% sure about buttons, but TextBlock controls that are inside DataTemplates do not get put into the UI Automation tree. Apparently this is an optimization to avoid 1000's of unneccessary textblocks.

You can work around it by SubClassing TextBlock. Here's mine:

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

Note: UI Automation also doesn't expose various other controls like Canvas, Panel, you can get them to show up with a similar subclass.

In saying that, I'm not sure why the Button isn't appearing.... Hrmmm


What tools are you using to write the automated scripts? I would have thought there'd be an option to drill into WPF's logical/visual trees rather than relying on the Win32 tree (as surfaced by UISpy).

If you have a look at the same application using Snoop, you'll see the full visual and logical trees.


I ended up solving this in my application by using TreeWalker.RawViewWalker to manually navigate the tree after using AutomationElement.FindFirst to find the template. FindFirst seems to reliably exclude all the information you want when automating somebody else's application. RawViewWalker seems to work when the elements show up in "Inspect Objects", but not in UISpy or your application.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜