Assign an AutomationId to ContentPresenter in a GridViewRowPresenter (ListView) - wpf

I'm trying to do coded UI testing with a ListView that is has a list of checkbox items.
Due to trouble with the coded UI code selecting the checkbox cell, I have been trying to add AutomationId to the controls, so that the coded UI test works.
I'm almost there, in snoop I can see that the UIItemCell does not have AutomationId set, but I can't figure out how to set it in my app.
The UIItemCell is where I need to set AutomationId
I found with Snoop that it's the ContentPresenter
The ListView code is this complex, so I'll distill it a bit
<ListView HorizontalAlignment="Left"
Height="194"
Margin="53,123,0,0"
VerticalAlignment="Top"
Width="424"
AutomationProperties.AutomationId="listviewoption">
<ListView.Resources>
<Style x:Key="ListViewItemContainerStyle1" TargetType="{x:Type ListViewItem}">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self}, Path=Content.Description }" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<Border SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}"
AutomationProperties.AutomationId="Bxaid1" >
<Grid AutomationProperties.AutomationId="Gxaid1">
<!-- This is used when GridView is put inside the ListView -->
<GridViewRowPresenter AutomationProperties.AutomationId="gvrp"
Content="{TemplateBinding Content}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
<!-- ... -->
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.Resources>
<ListView.ItemContainerStyle>
<StaticResource ResourceKey="ListViewItemContainerStyle1"/>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView AutomationProperties.AutomationId="aid1">
<GridViewColumn AutomationProperties.AutomationId="xc0"
DisplayMemberBinding="{Binding OptionName, Converter={StaticResource CamelCaseConverter}, Mode=OneWay}"
Width="180"/>
<GridViewColumn AutomationProperties.AutomationId="xc1"
Width="60">
<GridViewRowPresenter AutomationProperties.AutomationId="pp" />
<GridViewColumn.CellTemplate>
<DataTemplate >
<CheckBox Name="x1"
AutomationProperties.AutomationId="xaid1"
IsHitTestVisible="False"
HorizontalAlignment="Right"
Tag="{Binding OptionName}"
IsChecked=""
Padding="0"
Margin="0"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
There are some AutomationIds in there that haven't helped but are good points of reference; 'gvrp' is the GridViewRowPresenter [016] that holds the Content Presenter [017] that I want to put the id on, and 'xaid1' is the CheckBox inside the Content Presenter [017].
Please help before my head explodes.

I was able to do it eventually with
<GridViewRowPresenter Content="{TemplateBinding Content}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
<GridViewRowPresenter.Resources>
<Style TargetType="{x:Type ContentPresenter}">
<Setter Property="AutomationProperties.AutomationId"
Value="{Binding RelativeSource={RelativeSource Self}, Path=Content.Name }"/>
</Style>
</GridViewRowPresenter.Resources>
</GridViewRowPresenter>
However, the automatically testing generated code (coded UI) still referenced the table column (even though it was redundant) which was the problem I was trying to avoid in the first place...
Anyway, it is possible to set the AutomationId in the ContentPresenter and in case it's helpful to anyone living in the future, here it is!

Related

Data binding in custom WPF controls

I'm completely stuck trying to bind an image to my custom WPF Expander.
I found an examle for creating expander template here: https://www.codeproject.com/Articles/248112/Templating-WPF-Expander-Control and tried to edit this to use an image instead of expander icon.
Here is my custom template for expander button (I added an image source here, so it works properly with straight resource path, not binding):
<ControlTemplate x:Key="SimpleExpanderButtonTemp"
TargetType="{x:Type ToggleButton}">
<Border x:Name="ExpanderButtonBorder"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}"
>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image
Height="35"
Width="35"
Source="{Binding Path = ImageSource,
RelativeSource={RelativeSource TemplatedParent}}">
</Image>
<ContentPresenter x:Name="HeaderContent"
Grid.Column="1"
Margin="4,0,0,0"
ContentSource="Content"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<!-- MouseOver, Pressed behaviours-->
<Trigger Property="IsMouseOver"
Value="true">
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
Afterwards, I add template to expander itself:
<ControlTemplate x:Key="SidePanelExpander" TargetType="Expander">
<DockPanel>
<ToggleButton x:Name="ExpanderButton"
DockPanel.Dock="Top"
ImageSource="{Binding Path = ImageSource,
RelativeSource={RelativeSource TemplatedParent}}"
Template="{StaticResource SimpleExpanderButtonTemp}"
Content="{TemplateBinding Header}"
IsChecked="{Binding Path=IsExpanded,
RelativeSource={RelativeSource TemplatedParent}}"
OverridesDefaultStyle="True"
Padding="1.5,0">
</ToggleButton>
<ContentPresenter x:Name="ExpanderContent"
Visibility="Collapsed"
DockPanel.Dock="Bottom"/>
</DockPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="True">
<Setter TargetName="ExpanderContent"
Property="Visibility" Value="Visible"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
And then I'm trying to use it this way:
<Expander ExpandDirection="Right"
Template="{StaticResource SidePanelExpander}"
ImageSource="../Res/Images/engine.png"
>
I guess there are some difficulties in data binding forwarding through templates, but have no idea on how to solve this.
As Clemens said in the comments, ToggleButton and Expander don't have a property called ImageSource, so this code was never going to work. The "correct" way to do this would be to create a custom control, but a quick fix (hack) would be to specify the image path using the Tag property:
In the "SimpleExpanderButtonTemp" template, change the Image element Source as follows:
<Image Height="35"
Width="35"
Source="{Binding Path=Tag, RelativeSource={RelativeSource TemplatedParent}}" />
Next, in the "SidePanelExpander" template ToggleButton, get rid of the "ImageSource=..." line and replace it with this:
Tag="{TemplateBinding Tag}"
Finally, in the control itself, specify your image:
<Expander Tag="../Res/Images/engine.png"
...
Also, in the first template, I've noticed you use TemplateBindings on certain properties of the Border control (Background, BorderBrush, BorderThickness). These won't work either. To fix this, copy those same lines to the ToggleButton control of the second template, i.e.
<ToggleButton x:Name="ExpanderButton"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
...
You can now specify (say) a background colour in the control itself:
<Expander Background="Blue"
...
The TemplateBindings on the ToggleButton act like stepping stones, allowing the property value to pass from the control itself to the ToggleButton, then on to the Border.

Both 'ItemTemplate' and 'ItemTemplateSelector' are set; 'ItemTemplateSelector' will be ignored

Following this question, I have another question about TreeView.
What I already have is a TreeViewwith HierarchicalDataTemplate, in which I can change the HierarchicalDataTemplate of level2 (like explained in the question and the answer).
What I want now, is to change the look of the expander of the Treeview. For this, I have defined a ControlTemplate named ctForTreeViewItem, and I use it like this:
<Window.Resources>
<ControlTemplate x:Key="ctForTreeViewItem"
TargetType="{x:Type TreeViewItem}">
<Expander IsExpanded="True"
Background="Grey"
BorderBrush="Transparent"
Foreground="White"
BorderThickness="1,1,1,3">
<Expander.Header>
<Border BorderThickness="{TemplateBinding Border.BorderThickness}"
Padding="{TemplateBinding Control.Padding}"
BorderBrush="{TemplateBinding Border.BorderBrush}"
Background="{TemplateBinding Panel.Background}"
Name="Bd"
SnapsToDevicePixels="True"
Grid.Column="1">
<ContentPresenter Content="{TemplateBinding HeaderedContentControl.Header}"
ContentTemplate="{TemplateBinding HeaderedContentControl.HeaderTemplate}"
ContentStringFormat="{TemplateBinding HeaderedItemsControl.HeaderStringFormat}"
ContentSource="Header"
Name="PART_Header"
HorizontalAlignment="{TemplateBinding Control.HorizontalContentAlignment}"
SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
</Border>
</Expander.Header>
<Expander.Content>
<ItemsPresenter x:Name="ItemsHost" />
</Expander.Content>
</Expander>
</ControlTemplate>
<DataTemplate x:Key="Level3Template">
<Border Background="LightBlue">
<TextBlock Text="Level3"/>
</Border>
</DataTemplate>
<HierarchicalDataTemplate x:Key="Level2RedTemplate"
ItemsSource="{Binding Value}"
ItemTemplate="{StaticResource Level3Template}">
<Border Background="Red">
<TextBlock Text="Level2"/>
</Border>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="Level2YellowTemplate"
ItemsSource="{Binding Value}"
ItemTemplate="{StaticResource Level3Template}">
<Border Background="Yellow">
<TextBlock Text="Level2"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="Level1Template"
ItemsSource="{Binding Value}"
ItemTemplateSelector="{StaticResource MySelector}">
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="Template"
Value="{StaticResource ctForTreeViewItem}" />
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
<Border Background="Green">
<TextBlock Text="Level1"/>
</Border>
</HierarchicalDataTemplate>
</Window.Resources>
<TreeView Grid.Row="1"
Name="tv"
ItemsSource="{Binding Items}"
ItemTemplate="{StaticResource Level1Template}">
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="Template"
Value="{StaticResource ctForTreeViewItem}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
This works for Level1 as expected, so on Level 1, I have TreeViewItems with the defined ControlTemplate and the Correct HierarchicalDataTemplate.
However, it doesn't work on Level2, where I have an ItemTemplateSelector. At this position, I get this error: System.Windows.Data Error: 25 : Both 'ItemTemplate' and 'ItemTemplateSelector' are set; 'ItemTemplateSelector' will be ignored.
Is there any way, that I can assign a ControlTemplate to the TreeViewItems, while keeping the ItemTemplateSelector? Or even
Is there any other way, that I can change the style of the expander of the TreeView?
Your TreeViewItem ControlTemplate is broken. It ignores the data template selector because you explicitly override that by setting ContentTemplate on the ContentPresenter. That's similar to what's causing the (harmless) errors in your debug output stream as well: The level 2 templates inherit an ItemTemplateSelector from their ancestors, in the same way that if you set ItemContainerStyle once on the TreeView, it will be inherited by all children of the treeview unless some intervening child overrides it explicitly. No need to set it more than once. Because the level 2 templates inherit ItemTemplateSelector and override it with a different property, you get an error, but it's harmless.
I fixed the control template by changing the ContentPresenter for the Header to match the default TreeViewItem control template: I removed the Content, ContentTemplate, and ContentStringFormat attributes. The ContentPresenter has default behavior for all that stuff so you don't need to specify it explicitly.
<ContentPresenter
ContentSource="Header"
Name="PART_Header"
HorizontalAlignment="{TemplateBinding Control.HorizontalContentAlignment}"
SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}"
/>
The template/template selector errors are harmless but I was able to silence them by setting ItemTemplateSelector explicitly to null on the level 2 hierarchical data templates:
<HierarchicalDataTemplate
x:Key="Level2RedTemplate"
ItemsSource="{Binding Value}"
ItemTemplateSelector="{x:Null}"
ItemTemplate="{StaticResource Level3Template}"
>
<Border Background="Red">
<TextBlock Text="Level2"/>
</Border>
</HierarchicalDataTemplate>

ListViewItem custom template: ContentPresenter stays empty

I have the following ListView in my code. views:GameCard is a custom UserControl and {Binding} is a valid DataContext object with three items. Without the custom ItemContainerStyle everything works perfectly — the list shows three GameCards with correct info, etc. As soon as I add the ItemContainerStyle part, I get nothing but three "ABCD"s; so the data is still loaded correctly, but my UserControl is no longer displayed (I only added the "ABCD" to check if the data was there, as otherwise I got nothing but empty box).
Every piece of info I could find online seems to indicate that just putting a ContentPresenter element in the template should work, but it doesn't seem to in this case. What am I missing?
<ListView Grid.Row="1" ItemsSource="{Binding}" BorderThickness="0,0,1,0"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListView.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FF614B4B" Offset="0"/>
<GradientStop Color="#FFDA7070" Offset="1"/>
</LinearGradientBrush>
</ListView.Background>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<views:GameCard />
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<TextBlock Text="ABCD" />
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
</ListView>
You need to set the TargetType of your ControlTemplate. And in order to make your ItemTemplate work, you'd also need to bind the Content and ContentTemplate properties.
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<Grid>
....
<ContentPresenter
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
... />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
This may not be the case with you but so far I haven't had to modify the ItemContainerStyle yet, just the ListView.View. Since you're putting a Grid in your template style I'd assume you're looking for a GridView and this is how you do that:
<ListView.View>
<GridView>
<GridViewColumn Width="120">
<GridViewColumnHeader Height="14" >
<TextBlock Text="Type" FontSize="9"/>
</GridViewColumnHeader>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name, FallbackValue=MISSING}" />
<!-- or content presenter with bindings here -->
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
...
your style and everything is fine .All what required is to Assign or bind the Content property to your ContentPresenter. I hope this will help.

How do I stretch the contents of a HeaderedContentControl?

I have a HeaderedContentControl that contains a TreeView.
<HeaderedContentControl Header="Steps" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<TreeView Name="WizardSteps" ItemsSource="{Binding WizardSteps}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<!-- Hierarchical data templates here -->
</TreeView>
</HeaderedContentControl>
Although the HeaderedContentControl stretches to fill the area inside its parent grid, my TreeView control only occupies a small portion of the space available.
How do I get my TreeView to expand to fill the content area of my HeaderedContentControl?
The default control template for HeaderedContentControl is something like this:
<ControlTemplate TargetType="{x:Type HeaderedContentControl}">
<StackPanel>
<ContentPresenter ContentSource="Header" />
<ContentPresenter />
</StackPanel>
</ControlTemplate>
The StackPanel lets each child have its own desired height, so the TreeView won't stretch. You could replace it with a template that uses a DockPanel:
<HeaderedContentControl Header="Steps" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" VerticalContentAlignment="Stretch" >
<HeaderedContentControl.Template>
<ControlTemplate TargetType="HeaderedContentControl">
<DockPanel>
<ContentPresenter DockPanel.Dock="Top" ContentSource="Header" />
<ContentPresenter />
</DockPanel>
</ControlTemplate>
</HeaderedContentControl.Template>
If you want to make it more reusable, set the template in a Style and use VerticalContentAlignment:
<Style TargetType="HeaderedContentControl">
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="HeaderedContentControl">
<DockPanel>
<ContentPresenter DockPanel.Dock="Top" ContentSource="Header" />
<ContentPresenter VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
That way, all your HeaderedContentControls will have their content fill by default, and you can override that by setting VerticalContentAlignment on an individual control.
Alternately, you could use a DockPanel directly instead of a HeaderedContentControl.

WPF Expander Button Styled so it is inside Expander Header

I am using the Expander control and have styled the header as shown in the picture below:
http://www.hughgrice.com/Expander.jpg
The problem I have is that I want the expander button to be contained within the header so that the line for the end of the header template aligns with the Expander content i.e. I ultimatly want to end up with something similar to the image below:
http://www.hughgrice.com/Expander.gif
Thanks in advance.
I see that you want to actually move the expander button into your HeaderTemplate, not just restyle it. This is easily done with FindAncestor:
First add a ToggleButton and bind its IsChecked property using FindAncestor, along these lines:
<DataTemplate x:Key="MyHeaderTemplate">
<Border ...>
<DockPanel>
<!-- Expander button -->
<ToggleButton
IsChecked="{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource FindAncestor,Header,1}}"
Content=... />
<!-- Other content here -->
...
</DockPanel>
</Border>
</DataTemplate>
This adds an expand button inside the header template but does not hide the original button provided by the Expander. To do this I recommend you replace the Expander's ControlTemplate.
Here is a complete copy of Expander's ControlTemplate with the ToggleButton replaced with a simple ContentPresenter:
<ControlTemplate x:Key="ExpanderWithoutButton" TargetType="{x:Type Expander}">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
CornerRadius="3"
SnapsToDevicePixels="true">
<DockPanel>
<ContentPresenter
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
ContentTemplateSelector="{TemplateBinding HeaderTemplateSelector}"
DockPanel.Dock="Top"
Margin="1"
Focusable="false" />
<ContentPresenter
x:Name="ExpandSite"
Visibility="Collapsed"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Margin="{TemplateBinding Padding}"
Focusable="false" />
</DockPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="true">
<Setter Property="Visibility" Value="Visible" TargetName="ExpandSite"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
It might be used as follows:
<Expander Template="{StaticResource ExpanderWithoutButton}">
<Expander.HeaderTemplate>
<DataTemplate ...>
<Border ...>
<DockPanel>
<ToggleButton ...
IsChecked="{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource FindAncestor,Header,1}}" />
... other header template content here ...
A simpler alternative would be to just set a negative margin in yourHeaderTemplate to cover the expander button. Instead of the ControlTemplate shown above, your DataTemplat would just contain something like this:
<DataTemplate ...>
<Border Margin="-20 0 0 0" ... />
Adjust the negative margin to get the look you want. This solution is simpler but inferior in that if you switch to a different system theme the required margin may change and your expander may no longer look good.
You will need to edit the Expander's Template, not the HeaderTemplate. The HeaderTemplate doesn't contain the expand button, just the content inside of it.
The default control template looks something like this:
<ControlTemplate TargetType="{x:Type Expander}">
<Border>
<DockPanel>
<ToggleButton x:Name="HeaderSite"
ContentTemplate="{TemplateBinding HeaderTemplate}"
Content="{TemplateBinding Header}"
DockPanel.Dock="Top"
IsChecked="{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" />
<ContentPresenter x:Name="ExpandSite" />
</DockPanel>
</Border>
</ControlTemplate>
I took out most of the attributes but left in the important stuff. Basically, you will want to add your customizations around the ToggleButton. That is what contains the expand button and the header content.
If you have Expression Blend, it makes this process much easier because you can simply edit a copy of the original template. Visual Studio doesn't really have this ability yet.

Resources