Setting the scene
I have a simple Microsoft WPF application that consists of a (data driven) GroupBox containing a Group of Cats and a group of Dogs. Both the Cat and Dog groups contain two group items each. When I run the application all appears fine, I can see the groups and their content on the screen.
Running the application produces the following window:
However, when I create a UIAutomation test I can’t find any AutomationElements for the group items, just the groups; only the Cat and Dog Groups can be accessed using either the AutomationElement route, or seen in UISpy.exe as per the image below:
The child components for the individual Cat and Dog group items are not present and I need to be able to retrieve them as AutomationElements in my test code:
[TestMethod]
public void MyTest()
{
Condition controlTypeCondition = new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Group);
var foreGroundWindow = GetForegroundWindow();
var collection = foreGroundWindow.FindAll(TreeScope.Descendants, controlTypeCondition);
foreach (AutomationElement element in collection)
{
logger.Debug("Name: " + element.Current.Name);
var children = element.FindAll(TreeScope.Children, Condition.TrueCondition);
logger.Debug("Number of children: " + children.Count);
}
}
The above currently outputs:
Name: Cat
Number of children: 0
Name: Dog
Number of children: 0
Reproducing the Issue
To reproduce this issue, create a new WPF application in Visual Studio (called WpfApplication1) and replace the contents of the MainWindow.xaml with the following:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
</ResourceDictionary.MergedDictionaries>
<XmlDataProvider x:Key="data">
<x:XData>
<Animals xmlns="">
<Animal name="Felix" Species="Cat" />
<Animal name="Garfield" Species="Cat" />
<Animal name="Rex" Species="Dog" />
<Animal name="Rover" Species="Dog" />
</Animals>
</x:XData>
</XmlDataProvider>
<CollectionViewSource x:Key="AnimalsBySpecies" Source="{Binding Source={StaticResource data}, XPath=Animals/Animal}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="#Species" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</ResourceDictionary>
</Window.Resources>
<ItemsControl ItemsSource="{Binding Source={StaticResource AnimalsBySpecies}}">
<ItemsControl.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<GroupBox Header="{Binding Name}">
<ItemsPresenter />
</GroupBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ItemsControl.GroupStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding XPath=#name}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
In reality my code doesn't look exactly like this as it is an MVVM application. But for brevity I have simplified that down into a single XAML file which reproduces the same issue. The key point to note is that the group contents are populated from a XAML Binding.
So how can I get to the content using UIAutomation?
Any help greatly appreciated!
This is a well-known issue where TextBlock's that are inside data templates are not visible in the Control or Content views of the automation tree. This is an optimization made by WPF. There are three possible resolutions.
One is to use .NET 4.5 where, I believe, this was changed so that TextBlock's will now be included.
Another is to make sure you use the Raw view. This is easy in UI Spy (go to View > Raw View) however it somewhat cumbersome in automated tests as you cannot use normal FindFirst or FindAll conditions. You have to use TreeWalker.RawViewWalker to manually inspect the automation tree.
Finally, you can subclass the TextBlock control and override the OnCreateAutomationPeer method to return a custom AutomationPeer implementation with IsControlElement returning true. This outlined in this answer.
Try using the the native-code UI Automation API, instead of the managed API that's part of the .Net framework. This newer API supports additional accessibility, and is recommended by Microsoft. Here's some more information, be sure to read the links in the original post.
In addition, UISpy is deprecated and no longer recommended. Instead, use UIA Verify tool which, in addition to being much more stable and faster than UISpy, also works with the Windows Automation API described above and thus may expose more information about the controls you're interested at.
UISpy is not a good tool to inspect WPF windows or elements. I would suggest using Snoop.
However, this question seems similar and the solution given there may solve your problem.
Related
I want to access one of the named elements within the original control template that another element is using, in the code-behind.
This is an example of the XAML code (obviously the original is more complicated, or I'd just be doing this in XAML):
<Window x:Class="Temp.MainWindow" Title="MainWindow">
<Window.Resources>
<ControlTemplate x:Key="MyTemplate" TargetType="{x:Type Expander}">
<Expander Header="Some header">
<StackPanel>
<Grid Name="MyGrid"/>
</StackPanel>
</Expander>
</ControlTemplate>
</Window.Resources>
<Grid>
<Expander Name="expander" Template="{DynamicResource MyTemplate}"/>
</Grid>
</Window>
What I've tried:
public MainWindow()
{
InitializeComponent();
Grid grid = expander.Template.FindName("MyGrid", expander) as Grid;
}
I've also tried
Grid grid = expander.Template.Resources.FindName("MyGrid") as Grid;
But g is always null.
I've looked at:
How do I access an element of a control template from within code behind?
How to access a WPF control located in a ControlTemplate
How do I programmatically interact with template-generated elements Part I
The links above are how I got the code I'm working with, but for some reason, g is just always null. Am I doing something wrong with the ContentTemplate? Any help would be appreciated!
You need to wait until the template is applied to the control
protected override OnApplyTemplate()
{
Grid grid = Template.FindName("YourTemplateName") as Grid;
}
The real problem here is that you're mixing technologies. You're attempting to use something meant for grabbing the template of a lookless control, in the behind code of the main window. I would be surprised if you didn't run into more issues.
Instead, I would suggest looking into How to Create Lookless Controls and redesigning your application. It wouldn't take much effort and it would all play nice together.
I suspect a XAML compiler and/or WPF bug, but I want to make sure I haven't done something wrong here (other than trusting the XAML compiler and/or WPF, that is :) ).
Consider the following XAML for a minimal WPF program that will reproduce the problem:
<Window x:Class="TestxSharedMenuItem.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<x:Array x:Key="menuItemValues1" Type="{x:Type s:String}">
<s:String>value #1</s:String>
<s:String>value #2</s:String>
<s:String>value #3</s:String>
</x:Array>
<MenuItem x:Key="menuItem1" x:Shared="False"
ItemsSource="{StaticResource menuItemValues1}"
Header="Shared menu item"/>
</Window.Resources>
<StackPanel>
<Menu HorizontalAlignment="Left" VerticalAlignment="Top">
<StaticResource ResourceKey="menuItem1"/>
<StaticResource ResourceKey="menuItem1"/>
</Menu>
</StackPanel>
</Window>
When the program is run, WPF throws an exception:
XamlParseException: 'Add value to collection of type 'System.Windows.Controls.ItemCollection' threw an exception.' Line number '20' and line position '23'.
The InnerException is:
InvalidOperationException: Element already has a logical parent. It must be detached from the old parent before it is attached to a new one.
This is, of course, exactly what one would expect to see if one were trying to use the same control in multiple places. But using the x:Shared="False" attribute should be causing a new instance of the MenuItem resource object to be returned each time it's retrieved, avoiding that problem.
For some reason, having the x:Array element before the MenuItem element causes the x:Shared attribute to be ignored, resulting in a single MenuItem element being shared when the resource is referenced, and so causing the exception.
Other observations:
Adding x:Shared to the x:Array element doesn't help (it shouldn't, but I figured it was worth checking).
It does not matter whether the MenuItem element is actually referencing the x:Array element or not. The mere presence of the x:Array element is sufficient to cause the problem.
The example above uses MenuItem because this is where I ran across the problem, but the issue happens for other control types too.
Work-arounds include:
Moving the x:Array to a different ResourceDictionary (e.g. in App.xaml). Its presence appears to only affect the dictionary declaration in which it's found. (Doing so may or may not be feasible, depending on where the non-shared control resource needs to be declared).
Moving the x:Array to appear later in the dictionary declaration than the non-shared control resource's declaration. Of course, if the non-shared control resource requires the x:Array element, this doesn't help.
Declaring the x:Array inline with the non-shared control rather than as a separate resource. This solves the dependency issues that might exist in the previous two work-arounds, but of course prevents sharing of the x:Array with other dictionary entries that could use it, and exacerbates the issues surrounding the non-sharable control element (i.e. not only do you have multiple copies of the control element, you get multiple copies of the array it's dependent on as well). E.g:
<MenuItem x:Key="menuItem1" x:Shared="False"
Header="Shared menu item">
<MenuItem.ItemsSource>
<x:Array Type="{x:Type s:String}">
<s:String>value #1</s:String>
<s:String>value #2</s:String>
<s:String>value #3</s:String>
</x:Array>
</MenuItem.ItemsSource>
</MenuItem>
Defining the array in code-behind (e.g. as a C# static readonly field instead of in XAML). E.g.:
public static readonly string[] MenuItemValues = { "value #1", "value #2", "value #3" };
Then for example:
<MenuItem x:Key="menuItem1" x:Shared="False"
ItemsSource="{x:Static App.MenuItemValues}"
Header="Shared menu item"/>
Declaring the collection using different markup. E.g. use ArrayList or a non-generic class that inherits List<T> (since XAML and generics don't play nicely together).
The XAML looks fine to me. Have I done something wrong? Did I violate some "by design" rule the XAML compiler and/or WPF imposes and which I'm not aware of?
This isn't the first time I've run into problems with the x:Array markup extension causing problems (see XAML fails to compile, but without any error message, if user-defined object is first resource and followed immediately by x:Array resource and Spurious XAML errors in Visual Studio when declaring x:Array of x:Reference elements), but I want to check to make sure I'm not overlooking something here and that the XAML I wrote should in fact work as I expect.
Addendum:
For now, lacking an answer explaining that I've written the XAML incorrectly, I'm going to assume that I'm correct in my belief that this is a bug in the XAML compiler and/or WPF. I have submitted a bug report on the Microsoft Connect site, in case anyone else runs into this and would like to chime in:
https://connect.microsoft.com/VisualStudio/feedback/details/2443920/declaring-x-array-element-in-resources-causes-x-shared-attribute-on-later-element-to-be-ignored
Write <ResourceDictionary> explicitely into <Window.Resources>:
<Window x:Class="TestxSharedMenuItem.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<ResourceDictionary> <!-- modified here -->
<x:Array x:Key="menuItemValues1" Type="{x:Type s:String}">
<s:String>value #1</s:String>
<s:String>value #2</s:String>
<s:String>value #3</s:String>
</x:Array>
<MenuItem x:Key="menuItem1" x:Shared="False"
ItemsSource="{StaticResource menuItemValues1}"
Header="Shared menu item"/>
</ResourceDictionary> <!-- modified here -->
</Window.Resources>
<StackPanel>
<Menu HorizontalAlignment="Left" VerticalAlignment="Top">
<StaticResource ResourceKey="menuItem1"/>
<StaticResource ResourceKey="menuItem1"/>
</Menu>
</StackPanel>
</Window>
I had a very similar issue (actually, quite exactly the same, only with UserControl). I was desperate when I tried the above workaround :), but it worked.
I've just tried it out with your example code now, with explicit <ResourceDictionary> it works by me, without it it doesn't.
How to add a .svg file in a WPF window in C# as an image (,png || ,jpg)?
I use the code
<Image HorizontalAlignment="Left" Height="53" Margin="34,39,0,0"
VerticalAlignment="Top" Width="71"
Source="Test.svg" Name="MyImage"/>
But I get an error:
Blend does not support format svg.
I found that I could change the .svg file into a .xaml file. But I still do not know how to add the xaml as an image.
Based on an answer, I changed my code like this:
<Window x:Class="NIA_UI_Demo_CSharp.ShareDocumentsWin"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:svgc="http://sharpvectors.codeplex.com/svgc/"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ShareDocumentsWin" Height="350" Width="569">
<ResourceDictionary>
<Style x:Key="TheAwesomeXAMLimage" TargetType="ContentControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ContentControl">
my code
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
<Grid Margin="0,0,2,3">
<ContentControl Style="{StaticResource TheAwesomeXAMLimage}"/>
</Grid>
</Window>
But I get an error:
Content is set more than once;
As far as I know you cannot include svg-files directly.
Two options:
use library that can handle svg-files in runtime: https://sharpvectors.codeplex.com/ (moved to https://github.com/ElinamLLC/SharpVectors)
convert the svg to xaml and use them with native wpf objects (Path, Image..)
I prefer the second option, so I wrote a tool which can convert a single svg to xaml and can also batch convert a bunch of svg-files. The workflow is: just put the svg-file to your images-folder, call the batch-converter and find the images.xaml file (a resource-dictionary) updated with the new icons/images.
See https://github.com/BerndK/SvgToXaml
I was lucky that I have DevExpress available where you can use WpfSvgRenderer.CreateImageSource. Don't want to advertise here, but since it's a widely used library, probably some are happy to know.
Unfortunately text element inside the svg is not supported yet.
I am in the process of creating a base window class for most of my windows to derive from. Obviously the best solution for this was a separate class, and a style that applies to it.
The issue is that the <Style ../> I have is not being applied when it is in App.Resources. That is, if it's defined in an external ResourceDictionary, and merged into App.xaml's resources, or a local dictionary and merged, or placed inline into App.Resources. The <Style ../> is, however, applied when it is placed into Themes/Generic.xaml.
The problem can be demonstrated without doing anything special at all in the base window, apart from overriding the DefaultStyleKeyProperty.
Below is ThemeWindow:
public class ThemeWindow : Window
{
static ThemeWindow()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ThemeWindow), new FrameworkPropertyMetadata(typeof(ThemeWindow)));
}
}
Here is the very simple <Style ../> I am trying to apply (it makes the Window background red, nothing more):
<Style TargetType="{x:Type testing:ThemeWindow}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type testing:ThemeWindow}">
<Grid>
<Grid.Background>
<SolidColorBrush Color="Red"/>
</Grid.Background>
<AdornerDecorator>
<ContentPresenter />
</AdornerDecorator>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The MainWindow that uses ThemeWindow, is simply the following XAML:
<testing:ThemeWindow x:Class="Testing.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:testing="clr-namespace:Testing"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button Content="Button" HorizontalAlignment="Left" Margin="125,83,0,0" VerticalAlignment="Top" Width="75"/>
</Grid>
</testing:ThemeWindow>
Now, as stated, if you place that Style in its own ResourceDictionary, and include it like this:
<App.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Themes/ThemeWindow.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</App.Resources>
.. it does not work. If you inline the style straight into App.Resources, it does not work.
The only situation I can find it working is to call the ResourceDictionary xaml Generic.xaml, and place it into the Themes/ directory of the application.
I am wondering exactly why this is happening.
My only theory is that when WPF sees a control type, it will head over to Themes, and scan all ResourceDictionarys for the type, then fall back to Generic.xaml and load it. This doesn't explain why it would not load if the <Style /> is available in a merged ResourceDictionary though. Note that it does work if the MergedDictionary is placed into Generic.xaml, for obvious reasons.
I'm perfectly fine with having to merge the ResourceDictionary into Generic.xaml if that's what I have to do. I just want to get down at the technical details as to why it needs to be like this.
Screenshots of this not working / working:
I have a simple workaround that would allow you to set your Style in you app.xaml.
Define your style in app.xaml like this :
<Style x:Key="{x:Type testing:ThemeWindow}" TargetType="{x:Type testing:ThemeWindow}">
And change your ThemWindow to this :
public class ThemeWindow : Window
{
static ThemeWindow()
{
StyleProperty.OverrideMetadata(typeof(ThemeWindow), new FrameworkPropertyMetadata(GetDefautlStyle()));
}
private static Style GetDefautlStyle()
{
if (defaultStyle == null)
{
defaultStyle = Application.Current.FindResource(typeof(ThemeWindow)) as Style;
}
return defaultStyle;
}
private static Style defaultStyle = null;
}
It does not really solve the question, but that would allow you to achieve what you need !
EDIT : Looking at DefaultStyleKey reference, it's clearly stated that it's used for theme style lookup. That explains why it won't find it in app.xaml or any other dictionary. It will only search in Theme dictionaries. So you either have to define your style in a Theme Dictionary, or to use the Style property directly as in the above example.
I come around the following solution already discussed in stackoverflow. it would required to add in the load component when load of the application.
Refer Solution
First of all let me say that I'm rather new to WPF, so excuse me for any silly mistakes, but I've been cracking my head at this for a while now.
I have a simple sollution with three classes:
public class MyCustomItem
public class MyCustomLayout : ThirdPartyLayout<MyCustomItem>
public class MyViewController : INotifyPropertyChanged
MyCustomItem is a simple class with some properties ("Name" being one of them). ThirdPartyLayoutTool is a generic component inside an SDK that inherits from System.Windows.Controls.Panel. And MyViewController is the view controller I'm using as a data content.
I then created this simple XAML as the projects main window:
<Window x:Class="DependencyViewer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sdk="clr-namespace:Sdk;assembly=Sdk"
xmlns:local="clr-namespace:MyNamespace"
Title="MainWindow" Height="350" Width="525">
<local:MyCustomLayout x:Name="myLayout"/>
</Window>
Everything displays accordingly.Now my objective is to enhance the display of one of the sub components that is displayed by the ThirdPartyLayout panel, called TargetControl. So I add the following code:
<Window.Resources>
<Style TargetType="{x:Type sdk:TargetControl}">
<Style.Resources>
<ToolTip x:Key="ToolTipContent">
<StackPanel>
<TextBlock FontWeight="Bold" Text="Testing 1 2 3"/>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</ToolTip>
</Style.Resources>
<Setter Property="ToolTip" Value="{StaticResource ToolTipContent}"/>
</Style>
</Window.Resources>
When I run the code, the "Testing 1 2 3" message appears correctly, however, I don't see the Name property. On the output window, I get the following message:
BindingExpression path error: 'Name' property not found on 'object' ''MyViewController' (HashCode=31884011)'
What I don't get is why the binding is happening on the MyViewController class, instead of TargetControl class. Any ideas?
Best regards,
Carlos Jourdan
EDIT:
After tinkering guide mainly by the recommendations given by newb, I eventually found out that the source of the error is in fact in the SDK. The current release is still faulty, but when compiling from source I get the expected behavior.
Thanks for the help.
When you create a binding in XAML, you are, by default, binding to the current DataContext. In this isntance, it seems that MyViewController is the DataContext of the sdk:TargetControl. To bind to the Name property of the skd:TargetControl instead, try the following:
<TextBlock Text="{Binding Name, RelativeSource={RelativeSource AncestorType={x:Type sdk:TargetControl}}}"/>
Seems like DataContext of xaml.cs of MyViewController has the reference of MyCustomItem.
If you want you can set in xaml.cs, MyCustomLayout.ItemsSource = this.DataContext.
Or you can do MyCustomLayout.ItemsSource = specific property of MyCustomItem.