Automatically inherit control styles in only one custom window - wpf

I am wondering if it is possible to associate Styles for certain controls with a custom window in WPF.
Here's the scenario - I have created a custom window, and have defined styles for a number of controls that I will use in this window. These are contained in a portable class library.
The catch is that I only want the controls to use the style from my library when they are used in the custom window (there are several different windows in the application).
I understand that I can assign the styles a key, and load them from my portable library in my application's app.xaml using pack syntax, for example:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Custom.Application.Library.Controls;component/Styles/CheckBox.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
And then add and style the control within my custom window as such:
<CheckBox x:Name="checkBox" Style="{StaticResource SpecialCheckBox}"
But what I would really like to do is define they styles in my class library without a key, as in this:
<Style TargetType="{x:Type CheckBox}">
Instead of this:
<Style x:Key="SpecialCheckBox" TargetType="{x:Type CheckBox}">
So that when this checkbox is used in my custom window it automatically inherits the style. If I define the style like this, and load it into my app.xaml, the problem is obviously that ALL checkboxes will inherit this style, not just checkboxes used in my custom window.
So, what I'm trying to find out is if there is any way to associate a style resource explicitly with a custom window, so that I can define the styles without a key, and have them by default inherit the "Special" style when used in my custom window, but use the WPF defaults in any other windows of the application. Does anyone have experience with this?
For clarity here is the code of my custom window:
XAML:
<!-- Window style -->
<Style TargetType="{x:Type Controls:CCTApplicationWindow}">
<Setter Property="WindowStyle" Value="None"/>
<Setter Property="AllowsTransparency" Value="True"/>
<Setter Property="ResizeMode" Value="CanResizeWithGrip"/>
<Setter Property="MinWidth" Value="500"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Controls:CCTApplicationWindow}">
<Border BorderBrush="#FF999999">
<Border.Style>
<Style TargetType="{x:Type Border}">
<Setter Property="BorderThickness" Value="1"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=WindowState}" Value="Maximized">
<Setter Property="BorderThickness" Value="7"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Grid>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="29"/>
<RowDefinition />
</Grid.RowDefinitions>
<Controls:CCTApplicationHeader Grid.Row="0"
Margin="0"
Title="{TemplateBinding Title}"
DragMoveCommand="{TemplateBinding DragMoveCommand}"
MaximizeCommand="{TemplateBinding MaximizeCommand}"
MinimizeCommand="{TemplateBinding MinimizeCommand}"
CloseCommand="{TemplateBinding CloseCommand}"/>
<Grid Background="White" Grid.Row="1" Margin="0">
<AdornerDecorator>
<ContentPresenter/>
</AdornerDecorator>
</Grid>
</Grid>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
CS:
public partial class CCTApplicationWindow : Window
{
public static readonly DependencyProperty MaximizeCommandProperty = DependencyProperty.Register("MaximizeCommand", typeof(DelegateCommand), typeof(CCTApplicationWindow));
public static readonly DependencyProperty MinimizeCommandProperty = DependencyProperty.Register("MinimizeCommand", typeof(DelegateCommand), typeof(CCTApplicationWindow));
public static readonly DependencyProperty CloseCommandProperty = DependencyProperty.Register("CloseCommand", typeof(DelegateCommand), typeof(CCTApplicationWindow));
public static readonly DependencyProperty DragMoveCommandProperty = DependencyProperty.Register("DragMoveCommand", typeof(DelegateCommand), typeof(CCTApplicationWindow));
public CCTApplicationWindow()
{
MaximizeCommand = new DelegateCommand(MaximizeExecute);
MinimizeCommand = new DelegateCommand(MinimizeExecute);
CloseCommand = new DelegateCommand(CloseExecute);
DragMoveCommand = new DelegateCommand(DragMoveExecute);
}
static CCTApplicationWindow()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CCTApplicationWindow), new FrameworkPropertyMetadata(typeof(CCTApplicationWindow)));
}
public DelegateCommand MaximizeCommand
{
get
{
return (DelegateCommand)GetValue(MaximizeCommandProperty);
}
set
{
SetValue(MaximizeCommandProperty, value);
}
}
public DelegateCommand MinimizeCommand
{
get
{
return (DelegateCommand)GetValue(MinimizeCommandProperty);
}
set
{
SetValue(MinimizeCommandProperty, value);
}
}
public DelegateCommand CloseCommand
{
get
{
return (DelegateCommand)GetValue(CloseCommandProperty);
}
set
{
SetValue(CloseCommandProperty, value);
}
}
public DelegateCommand DragMoveCommand
{
get
{
return (DelegateCommand)GetValue(DragMoveCommandProperty);
}
set
{
SetValue(DragMoveCommandProperty, value);
}
}
private void MaximizeExecute(object obj)
{
if (this.WindowState != WindowState.Maximized)
{
this.WindowState = WindowState.Maximized;
}
else
{
SystemCommands.RestoreWindow(this);
}
}
private void MinimizeExecute(object obj)
{
SystemCommands.MinimizeWindow(this);
}
private void CloseExecute(object obj)
{
SystemCommands.CloseWindow(this);
}
private void DragMoveExecute(object obj)
{
DragMove();
}
}

Yes, you can do this, but you shouldn't! You've tagged this question as MVVM and yet your architecture design breaks MVVM entirely. The whole point of MVVM is that view logic is contained within the view model layer; your view models are the ones that should be keeping track of the logical hierarchy and they are the ones that should be exposing properties to the views to control their appearance. To put it another way, just because XAML is flexible enough and powerful enough to implement such logic doesn't mean it's the best place to actually do it!
To answer your question though, yes, this can be done with a DataTrigger binding to the parent with ObjectToTypeConverter. Here's an example of setting the TextBlock background to CornflowerBlue, unless its immediate parent is a Grid in which case it should be set to PaleGoldenrod:
<StackPanel Orientation="Vertical">
<StackPanel.Resources>
<converters:ObjectToTypeConverter x:Key="ObjectToTypeConverter" />
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Background" Value="CornflowerBlue" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Parent, RelativeSource={RelativeSource Mode=Self}, Converter={StaticResource ObjectToTypeConverter}}" Value="{x:Type Grid}">
<Setter Property="Background" Value="PaleGoldenrod" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<Grid Width="100" Height="32" HorizontalAlignment="Left">
<TextBlock Text="TextBox A" /> <!-- Gets a PaleGoldenrod background -->
</Grid>
<Canvas Width="100" Height="32" HorizontalAlignment="Left">
<TextBlock Text="TextBox B" /> <!-- Gets a CornflowerBlue background -->
</Canvas>
</StackPanel>
And here's the converter code. It's worth pointing out that if you're happy to simply check that a parent of a given type exists somewhere in the hierarchy (as opposed to the immediate parent) then you don't even need this, you can just attempt to bind to RelativeSource with AncestorType set to the relevant parent type.
// based on http://stackoverflow.com/questions/8244658/binding-to-the-object-gettype
[ValueConversion(typeof(object), typeof(Type))]
public class ObjectToTypeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value == null ? null : value.GetType();
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new InvalidOperationException();
}
}
But again, I implore you, if you really do want to adhere to MVVM then do not do it like this! This is exactly the kind of problem that "proper" MVVM was designed to solve.

Simplest way is to create a separate ResourceDictionary for your Custom window. And use it either using XAML or load it using Code.

Related

How to use depenceny property in the style of tooltip to set the text?

In a button, I am using a dependency property to pass information from the view model to the style of the button, so I can set the color of the color according to some conditions.
The code for the button is this:
The style in my xaml file:
<Style x:Key="BotonesColorEstadosTemplate" TargetType="{x:Type Button}">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="dp:BotonesEstadosAttachedProperty.CodigoEstado" Value="0"/>
<Condition Property="dp:BotonesEstadosAttachedProperty.AccionHabilitada" Value="true"/>
</MultiTrigger.Conditions>
</MultiTrigger>
<ContentTemplate>
</ContentTemplate>
</Style>
The dependency properties:
public static class BotonesEstadosAttachedProperty
{
public static readonly DependencyProperty CodigoEstadoProperty =
DependencyProperty.RegisterAttached(
"CodigoEstado",
typeof(short),
typeof(BotonesEstadosAttachedProperty));
public static short GetCodigoEstado(DependencyObject obj)
{
return (short)obj.GetValue(CodigoEstadoProperty);
}
public static void SetCodigoEstado(DependencyObject obj, short value)
{
obj.SetValue(CodigoEstadoProperty, value);
}
public static readonly DependencyProperty AccionHabilitadaProperty =
DependencyProperty.RegisterAttached(
"AccionHabilitada",
typeof(bool),
typeof(BotonesEstadosAttachedProperty));
public static bool GetAccionhabilitada(DependencyObject obj)
{
return (bool)obj.GetValue(AccionHabilitadaProperty);
}
public static void SetAccionHabilitada(DependencyObject obj, bool value)
{
obj.SetValue(AccionHabilitadaProperty, value);
}
}
How to use in the button:
<Button Name="btnAlmacenesActualizar" Content="..." Height="23" Margin="3,0,0,0" Width="23"
ap:BotonesEstadosDependencyProperty.CodigoEstado="{Binding CodigoEstadoActualizarAlmacenes}"
ap:BotonesEstadosDependencyProperty.AccionHabilitada="{Binding EsAccionActualizarAlmacenesHabilitada}">
With this, I can use a property of my view model and pass to my style, that use the information in the trigger to set the color of the button.
Now, I would like to have a style for the tooltip, to have the default configuration for all the tooltips, and I would like to can pass the text of the tooltip at first, but later I would like to pass another variables. By the moment, to test, I would like to try with the text.
I have this style:
<Style TargetType="ToolTip" x:Key="ToolTipDefaultStyle">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel>
<TextBlock>
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Text" Value="dp:ToolTipAttachedProperty.Texto"/>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
My StackPanel with the tooltip:
<StackPanel Name="spTiposIva" Orientation="Vertical" Margin="5,0,0,0"
ap:ToolTipDependencyProperty.Texto="{Binding TiposIvaTooltip}">
<StackPanel.ToolTip>
<ToolTip Style="{StaticResource ToolTipDefaultStyle}"/>
</StackPanel.ToolTip>
</StackPanel>
But in this case the text that is shown is "dp:ToolTipAttachedProperty.Texto". So I would like if it is possible to do the same than with the button, use a dependency propety to pass information from view model to the style.
Thanks.
You are currently not binding the attached property, you are assigning a string as Value. For binding to attached properties, you need to use the binding markup extension and parentheses, e.g.:
<Setter Property="Text" Value="{Binding (dp:ToolTipAttachedProperty.Texto)"/>
However, in your case you need to refer to the parent control of ToolTip, which your property is attached to. Normally, you would do this with a RelativeSource binding and AncestorType, but this does not work here, because ToolTip is not within the same visual tree as the parent control.
Instead, you can access the control via the PlacementTarget property on the parent ToolTip.
<Setter Property="Text" Value="{Binding PlacementTarget.(dp:ToolTipAttachedProperty.Texto), RelativeSource={RelativeSource AncestorType={x:Type ToolTip}}}"/>
Please also check your XAML for typos. The attached properties type does not match on the StackPanel and in the ToolTip style: ToolTipDependencyProperty or ToolTipAttachedProperty?

Template binding inside a ResourceDictionary Control Template

I have a resource dictionary as follows,
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:uwpControls="using:Microsoft.Toolkit.Uwp.UI.Controls"
xmlns:controls="using:Presentation.Common.Controls">
<Style x:Key="ExpanderStyleSection" TargetType="uwpControls:Expander">
<Setter Property="HeaderStyle" Value="{StaticResource LightExpanderHeaderToggleButtonStyle}"/>
<Setter Property="Margin" Value="4"/>
</Style>
<Style x:Key="LightExpanderHeaderToggleButtonStyle" TargetType="ToggleButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Grid x:Name="RootGrid"
Background="{StaticResource BrushBeckmanAquaA1}"> <!-- I want to change the background from an other View based on some condition -->
</ControlTemplate>
</Style>
</ResourceDictionary>
Now I am using the style inside this Dictionary as follows in an other view.
<uwpControls:Expander x:Name="ExpanderLisSharedSettings" Grid.Row="0" Style="{StaticResource ExpanderStyleSection}">
<!--Some Content here-->
</uwpControls:Expander
Whenever a property in the ViewModel changes, I want to update the RootGrid background in the ControlTemplate to some other color. How can I do it? In other words is there a way to create a dependency property of type Brush color and bind to the RootGrid Background in the ResourceDictionary. Please help.
You can create a ViewModel which contains a SolidColorBrush property and bind it with the Background of RootGrid and you need to declare the DataContext in the page which you use this style. In that case, it applies for Binding.
If you want to use x:bind in ResourceDictionary, you need to create a code behind class for it. Since the x:Bind depends on code generation, so it needs a code-behind file containing a constructor that calls InitializeComponent (to initialize the generated code).
Here we take binding as an example.
Page.xaml:
<ToggleButton Style="{StaticResource LightExpanderHeaderToggleButtonStyle}"></ToggleButton>
Page.xaml.cs:
public BlankPage1()
{
this.InitializeComponent();
VM = new MyViewModel();
VM.Fname = "fleegu";
VM.MyColor = new SolidColorBrush(Colors.Green);
this.DataContext = VM;
}
public MyViewModel VM { get; set; }
ResourceDictionary.xaml:
<Style x:Key="LightExpanderHeaderToggleButtonStyle" TargetType="ToggleButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Grid x:Name="RootGrid" Background="{Binding MyColor}">
<TextBlock Text="{Binding Fname}"></TextBlock>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
MyViewModel:
public class MyViewModel: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private string _fname { get; set; }
private SolidColorBrush myColor { get; set; }
public string Fname
{
get { return _fname; }
set
{
_fname = value;
this.OnPropertyChanged();
}
}
public SolidColorBrush MyColor
{
get { return myColor; }
set
{
myColor = value;
this.OnPropertyChanged();
}
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Use a TemplateBinding in the template. You may use a <Setter> to define the default value:
<Style x:Key="LightExpanderHeaderToggleButtonStyle" TargetType="ToggleButton">
<Setter Property="Background" Value="{StaticResource BrushBeckmanAquaA1}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Grid x:Name="RootGrid" Background="{TemplateBinding Background}">
...
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
You can then set or bind the Background of each instance of the control:
<uwpControls:Expander x:Name="ExpanderLisSharedSettings" Grid.Row="0" Background="Red" />
For example, if the view model defines a Brush property called "TheBackground", you could bind to it as usual (only UWP supports {x:Bind}):
<uwpControls:Expander x:Name="ExpanderLisSharedSettings" Grid.Row="0"
Background="{Binding TheBackground}" />

How to dynamically set style of an element in a User Control

In a WPF project, I have a user control (Valve.xaml) that defines a Polygon shape.
<Grid>
<Polygon Name="pValve" Points="0,50 0,20 50,50 50,20" Style="{StaticResource Valve_Open}"/>
</Grid>
I am displaying the Valve user control in a window xaml (FFG.xaml) file, like such:
<Window
<!-- removed other namespaces for brevity -->
xmlns:cl="clr-namespace:FFG.Controls;assembly=PID.Controls">
<Grid>
<cl:Valve x:Name="valve201A"></cl:Valve>
</Grid>
</Window>
I am setting the DataContext of FFG.xaml to class FFG_ViewModel.cs, and it contains an instance of the Valve_Model class. Valve_Model essentially represents the valve that is drawn on the window in FFG.xaml.
public class FFG_ViewModel : ViewModelBase {
public Valve_Model Valve201A { get; set; }
// There are other properties and methods, but I've removed them for brevity also
}
Here is the Valve_Model class:
public class Valve_Model INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
private bool _isValveOpen { get; set; }
public bool IsValveOpen {
get {
return _isValveOpen;
}
set {
_isValveOpen = value;
OnPropertyChanged("IsValveOpen");
}
}
#region INotifyPropertyChanged
protected virtual void OnPropertyChanged(string propertyName) {
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null) {
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
#endregion
}
QUESTION:
What I want is for the Style property in the Valve.xaml to change when the IsValveOpen property changes.
So if the valve is open then it would be:
<Polygon Name="pValve" Points="0,50 0,20 50,50 50,20" Style="{StaticResource Valve_Open}"/>
and when the property is changed to false then I need the style of the polygon to be changed to:
<Polygon Name="pValve" Points="0,50 0,20 50,50 50,20" Style="{StaticResource Valve_Closed}"/>
How do I go about implementing this exactly?
You could use an IMultiValueConverter.
First, let's simplify the use case. Basicly you want to swap Styles based on a given state object, which I'll represent by a ToggleButton. The fact that you're wrapping everything in a UserControl also has no influence on the underlying concept.
Demo:
Starting a fresh project
Declaring our Resources
Feeding the Window and the state to the Converter.
MainWindow.xaml
<Window
...
>
<Window.Resources>
<local:ToStyleConverter x:Key="ToStyleConverter"/>
<Style x:Key="Valve_Open" TargetType="{x:Type Polygon}">
<Setter Property="Fill" Value="Red"/>
</Style>
<Style x:Key="Valve_Closed" TargetType="{x:Type Polygon}">
<Setter Property="Fill" Value="Green"/>
</Style>
</Window.Resources>
<DockPanel>
<ToggleButton x:Name="butt" DockPanel.Dock="Bottom">Switch</ToggleButton>
<Polygon Name="pValve" Points="0,50 0,20 50,50 50,20" Stretch="Uniform">
<Polygon.Style>
<MultiBinding Converter="{StaticResource ToStyleConverter}">
<Binding RelativeSource="{RelativeSource FindAncestor,
AncestorType={x:Type Window}}"/>
<Binding ElementName="butt" Path="IsChecked"/>
</MultiBinding>
</Polygon.Style>
</Polygon>
</DockPanel>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class ToStyleConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
if (values[0] is Window)
{
Window win = (Window)values[0];
if ((bool)values[1])
return win.FindResource("Valve_Open");
if (!(bool)values[1])
return win.FindResource("Valve_Closed");
}
return DependencyProperty.UnsetValue;
}
public object[] ConvertBack(object values, Type[] targetType,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Changing to any specific use case means:
Pointing the relativesource binding to the Control that holds the Resources (Styles)
Using the second binding to add the state to the Converter (DP/INPC)
Implementing Converter logic
You can (should as much as I know) use a DataTrigger, within a ControlTemplate. Assuming that these two are your Styles:
<Window.Resources>
<Style TargetType="Polygon" x:Key="Valve_Open">
<Setter Property="Fill" Value="Red"/>
</Style>
<Style TargetType="Polygon" x:Key="Valve_Close">
<Setter Property="Fill" Value="Green"/>
</Style>
</Window.Resources>
You should add this style to the resources:
<Style x:Key="changeStyle" TargetType="Control">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Control">
<Grid>
<Polygon Name="pValve" Points="0,50 0,20 50,50 50,20" Style="{StaticResource Valve_Open}" />
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Valve201A.IsValveOpen}" Value="true">
<Setter TargetName="pValve" Property="Style" Value="{StaticResource Valve_Close}" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
and use them in your views:
<Control DataContext="{Binding}" Style="{StaticResource changeStyle}" />
Instead of setting the actual Style property to a new value, you could add a DataTrigger to the Style itself that changes the properties of the Polygon based on the value of the IsValveOpen source property.
Valve.xaml:
<Grid>
<Polygon Name="pValve" Points="0,50 0,20 50,50 50,20">
<Polygon.Style>
<Style TargetType="Polygon">
<!-- Copy the setters from the Valve_Closed style here -->
<Setter Property="Fill" Value="Red" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsValveOpen}" Value="True">
<!-- Copy the setters from the Valve_Open style here -->
<Setter Property="Fill" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</Polygon.Style>
</Polygon>
</Grid>
FFG.xaml:
<Grid>
<cl:Valve x:Name="valve201A" DataContext="{Binding Valve201A}" />
</Grid>

Selecting User Control for Data Template based on an Enum

I am working on a WPF app and currently I have an ItemsControl bound up to my View Model ObservableCollection and I have a DataTemplate that uses a UserControl to render the items on canvas. Can you use multiple User Controls and then switch which one is used based on an Enum? Another way to look it is to either create a Button or a TextBox for the item in the ObservableCollection based on an Enum.
You can select the data template for an item using a custom DataTemplateSelector. Assume we have the following:
public enum Kind
{
Button, TextBox,
}
public class Data
{
public Kind Kind { get; set; }
public string Value { get; set; }
}
Your data template selector might then look like this:
public class MyTemplateSelector : DataTemplateSelector
{
public DataTemplate ButtonTemplate { get; set; }
public DataTemplate TextBoxTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
Data data = (Data)item;
switch (data.Kind)
{
case Kind.Button:
return ButtonTemplate;
case Kind.TextBox:
return TextBoxTemplate;
}
return base.SelectTemplate(item, container);
}
}
In XAML, declare templates for all the cases you want to cover, in this case buttons and text boxes:
<Window.Resources>
<ResourceDictionary>
<DataTemplate x:Key="ButtonTemplate" DataType="local:Data">
<Button Content="{Binding Value}" />
</DataTemplate>
<DataTemplate x:Key="TextBoxTemplate" DataType="local:Data">
<TextBox Text="{Binding Value}" />
</DataTemplate>
</ResourceDictionary>
</Window.Resources>
Finally, have your ItemsControl create an instance of your custom template selector, initializing its two DataTemplateproperties from the above data templates:
<ItemsControl>
<ItemsControl.ItemTemplateSelector>
<local:MyTemplateSelector
ButtonTemplate="{StaticResource ButtonTemplate}"
TextBoxTemplate="{StaticResource TextBoxTemplate}"/>
</ItemsControl.ItemTemplateSelector>
<ItemsControl.Items>
<local:Data Kind="Button" Value="1. Button" />
<local:Data Kind="TextBox" Value="2. TextBox" />
<local:Data Kind="TextBox" Value="3. TextBox" />
<local:Data Kind="Button" Value="4. Button" />
</ItemsControl.Items>
</ItemsControl>
(In real life, set the ItemsSource instead of declaring the items inline, as I did.)
For completeness: To access your C# classes you need to set up the namespace, e.g.,
xmlns:local="clr-namespace:WPF"
Another possible quick solution is to use Data Triggers:
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="Content"
Value="{StaticResource YourDefaultLayout}" />
<Style.Triggers>
<DataTrigger Binding="{Binding YourEnumVMProperty}"
Value="{x:Static local:YourEnum.EnumValue1}">
<Setter Property="Content"
Value="{StaticResource ContentForEnumValue1}" />
</DataTrigger>
<DataTrigger Binding="{Binding YourEnumVMProperty}"
Value="{x:Static local:YourEnum.EnumValue2}">
<Setter Property="Content"
Value="{StaticResource ContentForEnumValue2}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
You could also define the template of a whole control using a trigger setter.
I prefer this because there is no need to define all the DataTemplateSelector stuff etc.

"Tag" ... Special functionality in WPF?

MSDN says "Gets or sets an arbitrary object value that can be used to store custom information about this element." which means I can store anything I want in this property.
But if you bind to this property (with property of type String having a value say "XYZ") and use it in Trigger conditions it doesn't work!
<Trigger Property="Tag" Value="XYZ">
<Setter Property="Background" Value="Red" />
</Trigger>
It does not set the background red. You can try and assume myElement to be a TextBlock! Why is it like this?
Tag has no special functionality in WPF.
This works for me:
<TextBlock Tag="{Binding Data}"
x:Name="tb">
<TextBlock.Style>
<Style>
<Style.Triggers>
<Trigger Property="TextBlock.Tag"
Value="XYZ">
<Setter Property="TextBlock.Background"
Value="Lime" />
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
And setting the Data object property to "XYZ" in an event.
The Tag is a construct held over from Winforms days (and possibly there from before that!). It was used as a convenient place to associate an object with a UI element, such as a FileInfo with a Button, so in the Button's event handler you could simply take the event sender, cast it to a Button, then cast the Tag value to a FileInfo and you have everything you need about the file you want to open.
There is one situation, however, where I've found the Tag is useful in WPF. I've used it as a holding spot that can be accessed by a ContextMenu MenuItem, which can't use the normal RelativeSource bindings you'd use to traverse the visual tree.
<ListBox.ItemContainerStyle>
<Style
TargetType="ListBoxItem">
<Setter
Property="Tag"
Value="{Binding ElementName=TheUserControlRootElement}" />
<Setter
Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem
Header="_Remove"
ToolTip="Remove this from this list"
CommandParameter="{Binding DataContext, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
Command="{Binding PlacementTarget.Tag.Remove, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
From the ContextMenu, I cannot access the Remove command which is defined in the UserControl class where this snippet is defined. But I can bind the root to the Tag of the ListBoxItem, which I can access via the ContextMenu.PlacementTarget property. The same trick can be used when binding within a ToolTip, as the same limitations apply.
MainWindow.xaml:
<Window x:Class="wpftest.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">
<Grid>
<TextBlock x:Name="test" MouseDown="test_MouseDown"
Tag="{Binding TestProperty}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<Trigger Property="Tag" Value="XYZ">
<Setter Property="Background" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
</Window>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new TestViewModel();
}
private void test_MouseDown(object sender, MouseButtonEventArgs e)
{
((TestViewModel)DataContext).TestProperty = "XYZ";
}
private sealed class TestViewModel : INotifyPropertyChanged
{
private string _testPropertyValue;
public string TestProperty
{
get { return _testPropertyValue; }
set
{
_testPropertyValue = value;
var handler = PropertyChanged;
if(handler != null)
handler(this, new PropertyChangedEventArgs("TestProperty"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Updated: Tag property now is bound to TestProperty.

Resources