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:
(source: brizzly.com)However, if I look in UISpy.exe (or navigate with AutomationElement
) I only see the groups (even in the Raw view):
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 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;
}
}
}
I hope this helps anyone still searching for an answer!
I'm not 100% sure about buttons, but TextBlock
controls that are inside DataTemplate
s 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.
精彩评论