Binding to TemplatedParent by two levels - wpf

I'm sure this has been solved before but I can't find a proper solution right know.. Probably I just don't know the terms I'm searching for.
Assuming I have this Custom Control Template
<Style x:Key="ColorPicker" TargetType="{x:Type local:ColorPicker}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ColorPicker}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Thumb Width="30" Height="30" Canvas.Left="0" Canvas.Top="0">
<Thumb.Style>
<Style TargetType="Thumb">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Ellipse Fill="{TemplateBinding SelectedColor}" Width="30" Height="30" Stroke="Black" StrokeThickness="1" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Thumb.Style>
</Thumb>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
where SelectedColor is a Property of ColorPicker.
In the sample above, template binding would look for SelectedColor in the template parent of type Thumb, however how can i get a binding to the second level template parent?

Fill="{Path=SelectedColor, RelativeSource={RelativeSource FindAncestor, AncestorType={local:ColorPicker}}}"
In your ColorPicker style, this will traverse up to the ColorPickers property and not the one on Thumb. I generally find this to be a far safer binding, and hardly ever use TemplateBinding. Been burned so many times in customcontrols using TemplateBinding!
Anyways full code :)
<Style x:Key="ColorPicker" TargetType="{x:Type local:ColorPicker}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ColorPicker}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Thumb Width="30" Height="30" Canvas.Left="0" Canvas.Top="0">
<Thumb.Style>
<Style TargetType="Thumb">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Ellipse Fill="{Path=SelectedColor.Color, RelativeSource={RelativeSource FindAncestor, AncestorType={local:ColorPicker}}}" Width="30" Height="30" Stroke="Black" StrokeThickness="1" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Thumb.Style>
</Thumb>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
WPF Cheatsheet is a compact list of all types of bindings, very handy!
Cheers,
Stian

Related

WPF Creating DataTemplate for a control that doesn't have a DataTemplate property

I'm creating a custom TickBar for a Slider. This CustomTickBar allows me to put different markers on the Slider. I'm gonna use the following model:
Interface IModel
{
string Id;
}
Class Model1 : IModel
{
string Id;
string SomeProperty;
}
Class Model2 : IModel
{
string Id;
string SomeOtherProperty;
}
The idea is I provide a List<IModel> to this TickBar control and based on the type of IModel the marker icon would change; e.g. for Model1 it would be a triangle and for Model2 it would be a rectangle. I understand this would be possible using a DataTemplate. But WPF TickBar doesn't have a DataTemplate property. Now is there a way I can do this using a DataTemplate property and subclassing TickBar?
Note: I understand I can create custom tick using OnRender(), but I'm trying to check if there's a way to do it by writing as less code-behind as possible.
TickBar does not have default style, so it looks like using OnRender is the way they designed it.
Another solution I'm think about, would be:
Create custom control, you own TickBar. Maybe inherit from TickBar.
You can set your own style for this custom TickBar, even based on some of your model data.
Use themes/generic.xaml and this code to apply custom style for your control:
static void MyCustomTickBar() {
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyCustomTickBar), new FrameworkPropertyMetadata(typeof(MyCustomTickBar)));
}
Edit template of Slider and use you new TickBar instead of built in one.
Here is default template for Slider. I used style snooper to extract it. Sorry, I could not provide it in my answer, it's too long.
Try to create a style for the slider first.
e.g. Something like this:
<Window.Resources>
<SolidColorBrush x:Key="HorizontalSliderTrackNormalBackground" Color="#FFE7EAEA"/>
<LinearGradientBrush x:Key="HorizontalSliderTrackNormalBorder" EndPoint="0,1" StartPoint="0,0">
<GradientStop Color="#FFAEB1AF" Offset="0.1"/>
<GradientStop Color="#FFAEB1AF" Offset=".9"/>
</LinearGradientBrush>
<Style x:Key="SliderRepeatButtonStyle" TargetType="{x:Type RepeatButton}">
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="IsTabStop" Value="false"/>
<Setter Property="Focusable" Value="false"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type RepeatButton}">
<Rectangle Fill="Transparent"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="CustomThumbForSlider" TargetType="{x:Type Thumb}">
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Ellipse Fill="#009EFF" Stroke="#009EFF" Height="14" Width="14"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="MyCustomStyleForSlider" TargetType="{x:Type Slider}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Slider}">
<Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="Auto" MinHeight="{TemplateBinding MinHeight}"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TickBar x:Name="TopTick" Visibility="Collapsed" Fill="{TemplateBinding Foreground}" Placement="Top" Height="10" Grid.Row="2"/>
<TickBar x:Name="BottomTick" Visibility="Collapsed" Fill="{TemplateBinding Foreground}" Placement="Bottom" Height="10" Grid.Row="2"/>
<Border x:Name="TrackBackground"
Background="{StaticResource HorizontalSliderTrackNormalBackground}"
BorderBrush="{StaticResource HorizontalSliderTrackNormalBorder}"
BorderThickness="2" CornerRadius="1"
Margin="5,0" VerticalAlignment="Center" Height="10.0" Grid.Row="1" >
<Canvas Margin="-6,-2">
<Rectangle Visibility="Hidden" x:Name="PART_SelectionRange" Height="6.0"
Fill="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"
Stroke="{DynamicResource {x:Static SystemColors.ControlDarkDarkBrushKey}}"
StrokeThickness="2.0"/>
</Canvas>
</Border>
<Track x:Name="PART_Track" Grid.Row="1" >
<Track.DecreaseRepeatButton>
<RepeatButton Style="{StaticResource SliderRepeatButtonStyle}" Command="{x:Static Slider.DecreaseLarge}"/>
</Track.DecreaseRepeatButton>
<Track.IncreaseRepeatButton>
<RepeatButton Style="{StaticResource SliderRepeatButtonStyle}" Command="{x:Static Slider.IncreaseLarge}"/>
</Track.IncreaseRepeatButton>
<Track.Thumb>
<Thumb x:Name="Thumb" Style="{StaticResource CustomThumbForSlider}" Background="Black"/>
</Track.Thumb>
</Track>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
You can then define a Slider which uses the style:
<Slider Name="CustomSlider" Style="{StaticResource MyCustomStyleForSlider}"/>
In order to change the style depending on some properties you can add a datatrigger. just replace the existing style with a new one:
<Style x:Key="CustomThumbForSlider" TargetType="{x:Type Thumb}">
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Ellipse Fill="#009EFF" Stroke="#009EFF" Height="14" Width="14"/>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsDifferent}" Value="True">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Ellipse Fill="#009055" Stroke="#009055" Height="14" Width="14"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
This new Style is going to change the appearance when the datacontext of slider has a different value for the property "IsDifferent".
<Slider Name="CustomSlider" Style="{StaticResource MyCustomStyleForSlider}" DataContext="{Binding Path=MyContext}"/>
Of course it would be possible to replace the green ellipse with a different shape of your liking and also use a different property.
For the templating problem it is usally best to use a ControlTemplate or alternativly a ContentControl whose DataTemplate can be set freely and which will act as a parent for your own controls.
I've recently tried overriding onrender for a custom slider myself and it's tricky. I wouldn't go that route.
I suggest you consider adding another control to hold the markers and make that match the height or width of your slider.
If your "ticks" at fixed then that could be just a uniformgrid containing paths and you use a resource to define their data using a DynamicResource geometry. You can switch out the geometry by merging different ones or datatemplate it.

How to draw a separator line below DataGrid headers in WPF

Is there a way to draw a separator line below the headers in a DataGrid? I have set GridLinesVisibility to None as I don't want any gridlines except the one below the headers. I'm struggling to find a way to do this and any help would be greatly appreciated.
This is what I want to achieve.
You can modify the DataGridColumnHeader's ControlTemplate.
I used the original DataGrid's template and replaced the default border and fill with a Rectangle with a height of 1.
<DataGrid>
<DataGrid.ColumnHeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Style.Resources>
<!-- This style is required for the column resize thumbs -->
<Style x:Key="ColumnHeaderGripperStyle" TargetType="{x:Type Thumb}">
<Setter Property="Width" Value="8" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Cursor" Value="SizeWE" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Border Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Style.Resources>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
<Grid Background="White">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="1"/>
</Grid.RowDefinitions>
<ContentPresenter Grid.Row="0" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
<Thumb Grid.Row="0" x:Name="PART_LeftHeaderGripper" HorizontalAlignment="Left" Style="{StaticResource ColumnHeaderGripperStyle}" />
<Thumb Grid.Row="0" x:Name="PART_RightHeaderGripper" HorizontalAlignment="Right" Style="{StaticResource ColumnHeaderGripperStyle}" />
<Rectangle Grid.Row="1" Height="1" HorizontalAlignment="Stretch" Stroke="Black"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.ColumnHeaderStyle>
</DataGrid>

How to change the background colour of column header of a WPF datagrid without affecting other properties

The style used for column header :-
<Style x:Key="DataGridColumnHeaderStyle" TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
<Grid VerticalAlignment="Bottom" HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<theme:DataGridHeaderBorder Grid.Column="0" Grid.Row="0" SortDirection="{TemplateBinding SortDirection}"
IsHovered="{TemplateBinding IsMouseOver}"
IsPressed="{TemplateBinding IsPressed}"
IsClickable="{TemplateBinding CanUserSort}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding ="{TemplateBinding Padding}"
SeparatorVisibility="{TemplateBinding SeparatorVisibility}"
ContextMenu="{DynamicResource DataGridColumnHeaderContextMenu}"
SeparatorBrush="{TemplateBinding SeparatorBrush}">
<TextBlock Grid.Column="0" Grid.Row="0" Text="{TemplateBinding Content}" FontWeight="Bold"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Padding ="{TemplateBinding Padding}"
TextWrapping="NoWrap"></TextBlock>
</theme:DataGridHeaderBorder>
<Thumb Grid.Column="0" Grid.Row="0" Name="PART_LeftHeaderGripper" HorizontalAlignment="Left">
<Thumb.Style>
<Style TargetType="Thumb">
<Style.Resources>
<ResourceDictionary />
</Style.Resources>
<Setter Property="FrameworkElement.Width">
<Setter.Value>
<thumbConversion:Double>8</thumbConversion:Double>
</Setter.Value>
</Setter>
<Setter Property="Panel.Background">
<Setter.Value>
<SolidColorBrush>#00FFFFFF</SolidColorBrush>
</Setter.Value>
</Setter>
<Setter Property="FrameworkElement.Cursor">
<Setter.Value>
<Cursor>SizeWE</Cursor>
</Setter.Value>
</Setter>
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate TargetType="Thumb">
<Border Padding="{TemplateBinding Control.Padding}" Background="{TemplateBinding Panel.Background}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Thumb.Style>
</Thumb>
<Thumb Grid.Column="0" Grid.Row="0" Name="PART_RightHeaderGripper" HorizontalAlignment="Right">
<Thumb.Style>
<Style TargetType="Thumb">
<Style.Resources>
<ResourceDictionary />
</Style.Resources>
<Setter Property="FrameworkElement.Width">
<Setter.Value>
<thumbConversion:Double>8</thumbConversion:Double>
</Setter.Value>
</Setter>
<Setter Property="Panel.Background">
<Setter.Value>
<SolidColorBrush>#00FFFFFF</SolidColorBrush>
</Setter.Value>
</Setter>
<Setter Property="FrameworkElement.Cursor">
<Setter.Value>
<Cursor>SizeWE</Cursor>
</Setter.Value>
</Setter>
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate TargetType="Thumb">
<Border Padding="{TemplateBinding Control.Padding}" Background="{TemplateBinding Panel.Background}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Thumb.Style>
</Thumb>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Before changing the Background color, the Column header looks like this.
I changed the Background color by adding
<Style x:Key="DataGridColumnHeaderStyle" TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Background" Value="White" />
<Setter Property="Template">
...........
It overrides everything I have specified in the template property. The sort symbols are not shown.
Then I tried adding the background color in the TextBlock part of the template. I changed the horizontal and vertical alignments to "Stretch" and provided the background color. The sort symbols are chopped and it looks ugly when I select the header.
I just want to change the background color. How do I do it?
You cannot change the Background colour of the DataGridHeaderBorder without losing the sort arrows.
If you set the Background property you will have to create the sort arrows yourself in your custom template.
The default background that you see is actually drawn in the OnRender method of the Microsoft.Windows.Themes.DataGridHeaderBorder class. It is not defined in some XAML template.
First in your DataGridView you need to set EnableHeadersVisualStyles to false. After you've done that you can set the individual header style on each column.
DataGridViewColumn dataGridViewColumn = dataGridView1.Columns[0];
dataGridViewColumn.HeaderCell.Style.BackColor = Color.Magenta;
dataGridViewColumn.HeaderCell.Style.ForeColor = Color.Yellow;

WPF how to access a property through Template of a control (XAML only)

I have a simple xaml example and I want to change CornerRadius property, which is set through ControlTemplate of Button. I need a way to change it through Button. One use case is: I have 2 buttons, one sets to "BigRadius" and another one sets to "SmallRadius". If I change the default one on Border, then both buttons will have the same CornerRadius. Is there a way to do this in XAML only?
<Window x:Class="StyleDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border CornerRadius="5" BorderBrush="Blue"
BorderThickness="5"
Width="80"
Height="40"
x:Name="BaseBorder">
<ContentPresenter Content="{Binding Path=Content, RelativeSource={RelativeSource TemplatedParent}}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="Grid" x:Name="GridWithMarginStyle">
<Setter Property="Margin" Value="12"></Setter>
</Style>
</Window.Resources>
<StackPanel>
<!--This button will have a big corner radius-->
<Button Name="Ok" Content="Ok"></Button>
<!--This button will have a small corner radius-->
<Button Name="CancelBtn" Click="CancelBtn_Click">Cancel</Button>
</StackPanel>
</Window>
You will need two styles for the Button to achieve what you are doing or create a Custom Button to implement CornerRadius as DependencyProperty, and bind it with CornerRadius of Border in ControlTemplate.
<Style TargetType="Button" x:Key="LargeRadiusStyle">
<Setter Property="Background" Value="White" />
<Setter Property="TextBlock.TextAlignment" Value="Center" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border CornerRadius="10" Background="White" BorderBrush="Black" BorderThickness="1" >
<ContentPresenter x:Name="contentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="Button" x:Key="SmallRadiusStyle">
<Setter Property="Background" Value="White" />
<Setter Property="TextBlock.TextAlignment" Value="Center" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border CornerRadius="3" Background="White" BorderBrush="Black" BorderThickness="1" >
<ContentPresenter x:Name="contentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Not sure what you have against DPs. You need SOMETHING to tell the button whether you want the big radius or the small radius. WPF can't read your mind (yet) :). If you don't want a custom button class, then you can use an attached property.

wpf chart legend

How do I enlarge those rectangles ?
I'm using wpf toolkit charts and I've tried to play with the control legend but it didn't helped.
With Blend, in the Objects panel:
Right Click on [PieSeries]
-Edit Additional Templates
-Edit LegendItemStyle
-Edit a Copy
You should get a default style:
<Style x:Key="PieChartLegendItemStyle" TargetType="{x:Type chartingToolkit:LegendItem}">
<Setter Property="IsTabStop" Value="False" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type chartingToolkit:LegendItem}">
<Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
<StackPanel Orientation="Horizontal">
<Rectangle Width="8" Height="8" Fill="{Binding Background}" Stroke="{Binding BorderBrush}" StrokeThickness="1" Margin="0,0,3,0" />
<visualizationToolkit:Title Content="{TemplateBinding Content}" />
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And your control will get a LegendItemStyle
<Charting:PieSeries ItemsSource="{Binding PutYourBindingHere}"
IndependentValueBinding="{Binding Key}" DependentValueBinding="{Binding Value}" IsSelectionEnabled="True" LegendItemStyle="{DynamicResource PieChartLegendItemStyle}">

Resources