How should I define Custom Controls to enable UI Automation and TestStack White?
See the question and my original answer on StackOverflowThe default automation peer for TextBlock
(TextBlockAutomationPeer
) removes the corresponding owner from the UI tree if it's part of a ControlTemplate, for some reason.
You can find the relevant code here: https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Automation/Peers/TextBlockAutomationPeer.cs,16e7fab76ffcb40a
override protected bool IsControlElementCore()
{
// Return true if TextBlock is not part of a ControlTemplate
TextBlock tb = (TextBlock)Owner;
DependencyObject templatedParent = tb.TemplatedParent;
return templatedParent == null || templatedParent is ContentPresenter; // If the templatedParent is a ContentPresenter, this TextBlock is generated from a DataTemplate
}
So, to fix this, you'll have to declare the TextBlock not in a ControlTemplate, or workaround with a code like this (difficult to generalize to a whole app...):
public class LabeledComboBox : Control
{
static LabeledComboBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(LabeledComboBox), new FrameworkPropertyMetadata(typeof(LabeledComboBox)));
}
// define our own peer
protected override AutomationPeer OnCreateAutomationPeer()
{
return new LabeledComboBoxAutomationPeer(this);
}
protected class LabeledComboBoxAutomationPeer : FrameworkElementAutomationPeer
{
public LabeledComboBoxAutomationPeer(LabeledComboBox owner) : base(owner)
{
}
// replace all TextBlockAutomationPeer by our custom peer for TextBlock
protected override List<AutomationPeer> GetChildrenCore()
{
var list = base.GetChildrenCore();
for (int i = 0; i < list.Count; i++)
{
var tb = list[i] as TextBlockAutomationPeer;
if (tb != null)
{
list[i] = new InteractiveTextBlockAutomationPeer((TextBlock)tb.Owner);
}
}
return list;
}
}
// just do the default stuff, instead of the strange TextBlockAutomationPeer implementation
protected class InteractiveTextBlockAutomationPeer : FrameworkElementAutomationPeer
{
public InteractiveTextBlockAutomationPeer(TextBlock owner) : base(owner)
{
}
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Text;
}
protected override string GetClassNameCore()
{
return "TextBlock";
}
}
}
One other solution is to create your own TextBlock control class (deriving from TextBlock) and override OnCreateAutomationPeer
to return the custom one.