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" />
</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>
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):
(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 http://www.colinsalmcorner.com/post/genericautomationpeer--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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With