How to recognize WPF visual drawings in UIAutomation?
Our application has a canvas to which we add visual drawings (e.g. lines, polygons, etc.)
// sample code
var canvas = new Canvas(); // create canvas
var visuals = new VisualCollection(canvas); // link the canvas to the visual collection
visuals.Add(new DrawingVisual()); // add the visuals to the canvas
visuals.Add(new DrawingVisual());
Our goal is to add these visuals to the canvas using automation and confirm that they are added correctly. We use an infrastructure based on Microsoft UIAutomation.
When using the Inspection tool to check the visual structure, I couldn't find the canvas. Do some research and figured out that you need to override the method OnCreateAutomationPeer
from UIElement
and return an applicable object AutomationPeer
in order to be able to see this in automation.
Made the change and I can now see the canvas, however I can still see any images added under the canvas.
Can anyone help me understand what is the problem?
Attempts / Alternatives:
- Tried using OnCreateAutomationPeer method but
DrawingVisual
dont get fromUIElement
and i cant addUIElement
s beforeCanvas.VisualCollection
. - Image recognition is an option, but we are trying to avoid it for performance / maintenance reasons.
source to share
Only UIElement can be seen from UI Automation (as you saw, OnCreateAutomationPeer starts from this class, not from the Visual class).
So, you need to add a UIElement (or obtained as a FrameworkElement) to the canvas if you want it to be accessible by UIAutomation.
You can create your own class as described here: Using DrawingVisual Objects either with a custom UserControl or use an existing one that suits you, but that has to come from UIElement somehow.
Once you have a good class, you can use the default AutomationPeer automatically, or override the method and adapt it more closely.
If you want to keep the Visuals, one solution is to change the containing object (but it must still come from the UIElement). For example, here, if I follow the article in the link, I can write a custom containing object (instead of a canvas of your sample code, so you might be able to adjust a bit):
public class MyVisualHost : UIElement
{
public MyVisualHost()
{
Children = new VisualCollection(this);
}
public VisualCollection Children { get; private set; }
public void AddChild(Visual visual)
{
Children.Add(visual);
}
protected override int VisualChildrenCount
{
get { return Children.Count; }
}
protected override Visual GetVisualChild(int index)
{
return Children[index];
}
protected override AutomationPeer OnCreateAutomationPeer()
{
return new MyVisualHostPeer(this);
}
// create a custom AutomationPeer for the container
private class MyVisualHostPeer : UIElementAutomationPeer
{
public MyVisualHostPeer(MyVisualHost owner)
: base(owner)
{
}
public new MyVisualHost Owner
{
get
{
return (MyVisualHost)base.Owner;
}
}
// a listening client (like UISpy is requesting a list of children)
protected override List<AutomationPeer> GetChildrenCore()
{
List<AutomationPeer> list = new List<AutomationPeer>();
foreach (Visual visual in Owner.Children)
{
list.Add(new MyVisualPeer(visual));
}
return list;
}
}
// create a custom AutomationPeer for the visuals
private class MyVisualPeer : AutomationPeer
{
public MyVisualPeer(Visual visual)
{
}
// here you'll need to implement the abstrat class the way you want
}
}
source to share