How to pass properties to WPF Style - wpf

I'm trying to write a reusable Template for a WPF ItemContainerStyle.
This Template changes the way the TabControl's Item looks.
This template is meant to be used in several places in the application.
In each place it is used I want to be able to pass different parameters to it.
For example: to change the Margin of the Border of the Item:
<Style x:Key="TabItemStyle1" TargetType="{x:Type TabItem}">
<Setter Property="Margin" Value="10,0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid SnapsToDevicePixels="true">
<Border x:Name="Bd" Width="80"
Background="Gray"
Margin="{TemplateBinding Margin}">
<ContentPresenter x:Name="Content"
ContentSource="Header" />
</Border>
</Grid>
<ControlTemplate.Triggers>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
...
<TabControl ItemContainerStyle="{DynamicResource TabItemStyle1}">
In the place where the style is used I Would like to Write something like:
ItemContainerStyle="{DynamicResource TabItemStyle1 Margin='5,0'}"
or
<TabControl Margin="78,51,167,90" ItemContainerStyle="{DynamicResource TabItemStyle1}"
ItemContainerStyle.Margin="5,0">
The motivation is to use this template in different places with different Margins.
Is there a way to do this ?
Thank you

You can do it with attached properties. I wrote a blog post explaining how to do it:
http://www.thomaslevesque.com/2011/10/01/wpf-creating-parameterized-styles-with-attached-properties/
Another option is to use DynamicResource, and redefine the resource in derived styles

OK, I've found a way to do this with dave's help.
The Solution is to create a derived template and set the properties in it.
This way the original template can be reused.
<Style x:Key="TabItemStyle2" TargetType="{x:Type TabItem}"
BasedOn="{StaticResource TabItemStyle1}">
<Style.Setters>
<Setter Property="Margin" Value="40,0"></Setter>
</Style.Setters>
</Style>
And set the TabControl's ItemContainerStyle to the derived style:
<TabControl ItemContainerStyle="{DynamicResource TabItemStyle2}">

In my case I had to change some parameters deep in the applied template (so I couldn't use just a setter).
And I didn't want to code some classes that traverse the visual tree or register an attached property to do the changes.
However, it is possible to define resources within the base style and override these values in the derived definitions. So, with the original example this would look like this:
<Style x:Key="AbsTabItemStyle" TargetType="{x:Type TabItem}">
<!-- Override these default values in derived style definitions -->
<Style.Resources>
<s:Double x:Key="GridBorderMargin">10</s:Double>
<Color x:Key="GridBorderColor">Grey</Color>
</Style.Resources>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid SnapsToDevicePixels="true">
<Border x:Name="Bd"
Width="80"
Background="{DynamicResouces GridBorderColor}"
Margin="{DynamicResouces GridBorderMargin}"
>
<ContentPresenter x:Name="Content"
ContentSource="Header" />
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="BigMarginTabItemStyle" TargetType="{x:Type TabItem}" BasedOn="{StaticResource AbsTabItemStyle}">
<!-- Set different values in this derived style definition -->
<Style.Resources>
<s:Double x:Key="GridBorderMargin">20</s:Double>
</Style.Resources>
</Style>
<Style x:Key="RedTabItemStyle" TargetType="{x:Type TabItem}" BasedOn="{StaticResource AbsTabItemStyle}">
<!-- Set different values in this derived style definition -->
<Style.Resources>
<c:Color x:Key="GridBorderColor">Red</Color>
</Style.Resources>
</Style>

A way of solving it is by adding a Margin property to the objects/ViewModels you want to display and (data)bind to that value in the template.
As far as I know there is no support of parameterized styles/templates.

Related

DataGrid CellStyle based on global CellStyle

I've a DataGrid and I want to change the background colours of individual cells. This is reasonably simple to do after some searching with xaml such as
<DataGridTextColumn.CellStyle>
<Style>
<Setter Property="Border.Background" Value="{Binding Converter={StaticResource ImportTableBackgroundColorConverter},ConverterParameter=GotName}" />
</Style>
</DataGridTextColumn.CellStyle>
However, in an app-wide ResourceDictionary I also have
<Style TargetType="DataGrid" x:Key="GlobalCellStyle">
<!-- Cell style -->
<Setter Property="CellStyle">
<Setter.Value>
<Style TargetType="DataGridCell">
<!-- Single Click Editing -->
<EventSetter Event="PreviewMouseLeftButtonDown"
Handler="DataGridCell_PreviewMouseLeftButtonDown" />
<EventSetter Event="KeyDown" Handler="DataGridCell_KeyDown" />
<EventSetter Event="GotFocus" Handler="DataGridCell_GotFocus"/>
<!--body content datagrid cell vertical centering-->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridCell}">
<Grid Background="{TemplateBinding Background}">
<ContentPresenter VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
This sets all DataGrid cells to centre their content and, using some codebehind that goes with the same file, makes the cells go into edit mode on a single click. Specifying a new style locally loses this. If I try and specify the new local style based on the global, I get the exception Can only base on a Style with target type that is base type 'IFrameworkInputElement'.
I've tried bringing the global DataGridCell style itself outside the global DataGrid style and get the same error. This is despite DataGridCell appearing to implement IFrameworkInputElement.
Because I'm passing a parameter to the ValueConverter to let it identify which field the cell is displaying, I can't move my background colour stuff to the global style- I'd have to have the whole row background changing colour together. And copying the global style to each column declaration in my table, as well as possibly having to copy the codebehind as well, seems quite horrendous both initially and to maintain later.
Does anyone know how I can either get the style inheritance to work, or to know what column I'm in when the ValueConverter is called?
You probably just need to use BasedOn:
<Style BasedOn="{StaticResource GlobalCellStyle}">
<Setter Property="Border.Background" Value="{Binding Converter={StaticResource ImportTableBackgroundColorConverter},ConverterParameter=GotName}" />
</Style>
Very strangely, i.e. I don't understand why, the original method fails (even though DataGridCell clearly implements IFrameworkInputElement because I can cast the former to the latter) yet if the inherited style is defined in the same ResourceDictionary as the style it's inheriting from, it works. i.e
<Style TargetType="DataGridCell" x:Key="GlobalCellStyle">
<!-- Your DataGrid Cell style definition goes here -->
<!-- Single Click Editing -->
<EventSetter Event="PreviewMouseLeftButtonDown"
Handler="DataGridCell_PreviewMouseLeftButtonDown" />
<EventSetter Event="KeyDown" Handler="DataGridCell_KeyDown" />
<EventSetter Event="GotFocus" Handler="DataGridCell_GotFocus"/>
<!--body content datagrid cell vertical centering-->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridCell}">
<Grid Background="{TemplateBinding Background}">
<ContentPresenter VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<conv:ImportTableBackgroundColorConverter x:Key="ImportTableBackgroundColorConverter" />
<Style BasedOn="{StaticResource GlobalCellStyle}" TargetType="DataGridCell" x:Key="DOBCellStyle">
<Setter Property="Border.Background" Value="{Binding Converter={StaticResource ImportTableBackgroundColorConverter},ConverterParameter=GotDOB}" />
</Style>
Might be useful for someone else at some point.

Is it possible to create a BulletDecorator using the current theme?

I'm using the well known technique of styling a list box to look like a radio button group.
The style presents a BulletDecorator for each item in the list. In order to do this I need to reference a specific theme assembly such as PresentationFramework.Aero.dll, and then explicitly use that in my style.
xmlns:theme="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
<BulletDecorator.Bullet>
<theme:BulletChrome
Background="{TemplateBinding Control.Background}"
BorderBrush="{TemplateBinding Control.BorderBrush}"
IsRound="True"
RenderMouseOver="{TemplateBinding UIElement.IsMouseOver}"
IsChecked="{TemplateBinding ListBoxItem.IsSelected}" />
</BulletDecorator.Bullet>
Is there a way to create a BulletDecorator which is styled using the current or default theme, so that I don't need to reference an explicit theme?
You don't need to recreate the RadioButton template... why don't you just use a RadioButton in the ItemContainerStyle?
<Style x:Key="RadioButtonGroup" TargetType="ListBox">
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<RadioButton IsChecked="{TemplateBinding IsSelected}" Content="{TemplateBinding Content}" GroupName="ListBoxItems" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
Note that there's an issue with this approach: if you use this style for several ListBoxes in the same window, all RadioButtons will be mutually exclusive, because they will share the same GroupName. See this article for a workaround.

Global button cursor

Is there a way to set the default cursor for a control type at the application level? I'd like to say that all Button controls, regardless of whether or not they have a specific style, have a default cursor of the hand cursor unless it's overridden in that button's individual style specification.
Here's an example of such a button with its own style that I'd like to override the default
<UserControl>
<UserControl.Resources>
<Style x:Key="CloseButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<!-- My button's custom content here -->
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Button x:Name="btnClose" Style="{DynamicResource CloseButtonStyle}"/>
</UserControl>
Put the style below in Application.Resources in your App.xaml file.
<Style TargetType="Button">
<Setter Property="Cursor" Value="Hand"/>
</Style>
UPDATE
In regards to the 3rd comment:
To achieve that, you need to leave just your control template in UserControl.Resources:
<ControlTemplate x:Key="CloseButtonTemplate" TargetType="{x:Type Button}">
<Grid>
<!-- My button's custom content here -->
</Grid>
</ControlTemplate>
Then set Template property for Button:
<Button Template="{DynamicResource CloseButtonTemplate}"/>

Define new ControlTemplate keeping the original events

I want to use the ExtendedWPFToolkits's ColorPicker but with a custom ButtonStyle.
I can create a new look overriding the Template property of the item but the original templates click event is missing.
I want to keep it, but how?
<Controls:ColorPicker >
<Controls:ColorPicker.ButtonStyle>
<Style TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Button Content="ColorPicker"></Button>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Controls:ColorPicker.ButtonStyle>
</Controls:ColorPicker>
What you have is not valid. You are putting a Button in the ControlTemplate of a ToggleButton, so basically a button in a button.
You'd need to do something like:
<Controls:ColorPicker >
<Controls:ColorPicker.ButtonStyle>
<Style TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border Background="Transparent">
<TextBlock Text="ColorPicker" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Controls:ColorPicker.ButtonStyle>
</Controls:ColorPicker>
I added a transparent Border so the button will be able to receive mouse events for areas not covered by the text.

Override general Button style

We have a general style in default.xaml, targeting all buttons in our application. Is there a way to override this style, and creating a new button based on the default button?
if you want to inherit any existing style use BasedOn
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}" x:Key="RedTextBasedOnButton"> <Setter Property="Foreground" Value="Red" /> </Style>
this will inherit the default style and new foreground property will apply to the new style
hope this helps.
You can change the Template of the Button like this
<Style x:Key="{x:Type Button}" TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Padding="10,5,10,5" BorderBrush="Green" BorderThickness="1" CornerRadius="5" Background="#EFEFEF">
<TextBlock Text="{TemplateBinding Content}" />
</Border>
</ControlTemplate>
</Style>
Class Style has property BasedOn and the constructor accepting Style parameter which defines style to be based on.
Style boldStyle = new Style(typeof(Button), generalButtonStyle);

Resources