WPF Expander Button Styled so it is inside Expander Header - wpf

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.

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.

WPF LayoutTransform: ToolTip size not modified

I'm using LayoutTransform in my windows' top level 'Grid' control, to do perform a ScaleTransform and implement a zoom factor on the UI (similar to how browsers do it).
Things work well, but somehow tooltips show up with the unadjusted size.
If there a way to for example enumerate all toolTips in a window and adjust their size from the .cs file? ...or any other way to deal with this?
The reason why the tooltips are unaffected is because tooltips are popups that are not part of the window's visual tree. Thus, any layout transformation performed on the componenets of the window will not carry over to the tooltips. If you have mostly generic tooltips (text basically), you can create a non-keyed style in your window resources and WPF will automatically apply that to all your tooltips:
<Window.Resources>
<Style TargetType="ToolTip">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToolTip}">
<Border x:Name="Border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
<Border.LayoutTransform>
<ScaleTransform ScaleX="{Binding ScaleFactor}"></ScaleTransform>
</Border.LayoutTransform>
<ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" ContentStringFormat="{TemplateBinding ContentStringFormat}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<StackPanel x:Name="MainPanel">
<StackPanel.LayoutTransform>
<ScaleTransform ScaleX="{Binding ScaleFactor}" ScaleY="{Binding ScaleFactor}"></ScaleTransform>
</StackPanel.LayoutTransform>
<TextBlock ToolTip="blah">haha!</TextBlock>
</StackPanel>
In the example I have ScaleX/Y for the tooltip bound to a ScaleFactor property on my window's View Model. You can keep it dynamic this way I believe.
Like what Rowbear has mentioned, Tooltips are popups (which are windows), so they have their own visual tree. But, as far as I know, the big problem is, these popups do not inherit DataContext from the spawning control, too.
<Window.Resources>
<Style TargetType="ToolTip">
<Setter Property="LayoutTransform" Value="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget.LayoutTransform}" />
</Style>
</Window.Resources>

Assign an AutomationId to ContentPresenter in a GridViewRowPresenter (ListView)

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!

DataGrid header alignment

I populate a DataGrid control using DataSet in WPF(c#). I need a way align text of header to center.
Note: During running of my program, It is possible to DataGrid.ItemsSource updated.
It is not same as my previous questions...
<DataGrid ItemsSource="{Binding Items}"
AutoGenerateColumns="True">
<DataGrid.ColumnHeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Style.Setters>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
</Style.Setters>
</Style>
</DataGrid.ColumnHeaderStyle>
</DataGrid>
You just have to style the HorizontalContentAlignment property of the DataGridColumnHeader type. So add to the resources:
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
Addendum In general, you can see the control templates at this page on MSDN, which allows you to deduce how to apply the styles you need. In this case, the template for the control DataGridColumnHeader looks like this:
<Grid>
<Border x:Name="columnHeaderBorder" BorderThickness="1" Padding="3,0,3,0">
<!-- (ellided brushes for brevity) -->
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</Border>
<Thumb x:Name="PART_LeftHeaderGripper"
HorizontalAlignment="Left"
Style="{StaticResource ColumnHeaderGripperStyle}" />
<Thumb x:Name="PART_RightHeaderGripper"
HorizontalAlignment="Right"
Style="{StaticResource ColumnHeaderGripperStyle}" />
</Grid>
The ContentPresenter is where the header text is displayed -- you can see that has its HorizontalAlignment set to the control's HorizontalContentAlignment (TemplateBinding). Hence all that's needed is to set that style property on the control type DataGridColumnHeader.

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.

Resources