Click here to Skip to main content
15,867,453 members
Articles / Desktop Programming / WPF

WPF Custom Visualization Part 2 of some: Triggers

Rate me:
Please Sign up or sign in to vote.
5.00/5 (24 votes)
24 Aug 2016CPOL17 min read 42.7K   358   45   9
(Yet Another) Investigation of WPF triggers

Content

Introduction

This series of articles is a deep dive into the possibilities for customization of controls in WPF. Allthough there are allready various articles explaining the possibilities of customizing WPF controls, I wanted this one to provide an extensive explanation on the subject and also to provide examples on what works but also on what doesn't.

Of course, it is up to you to decide if I succeeded.

The article only handles using XAML as a way for customization (with some exceptions if certain things require coding)

Ways of Customization

WPF provides 2 main ways of customizing controls:

  1. Styling: with Styling you can set various properties on the controls.
  2. Templating: with Templating you can completely replace the standard look of your control with something else.

 

For this, I split the series in some parts, for which I currently have the following planned:

  1. WPF Custom Visualization Part 1 of some: Styling
  2. WPF Custom Visualization Part 2 of some: Styling with Triggers (this article)
  3. WPF Custom Visualization Intermezzo: Resources
  4. WPF Custom Visualization Intermezzo 2: Binding
  5. WPF Custom Visualization Part 3 of N: Templating
  6. WPF Custom Visualization Part 4 of N: Addorners

 

It's all about dynamics now

Ok, so let's go one step further. Userinterfaces for todays applications are all about animations, changing colors depending on state, or short: they change the visuals depending on the state of the application. Enter triggers. They allow you to do something when another thing happens: they are the mediator between what happens and what to do.

That "thing which happens" can be the changing of a property of the control, but also of the datacontext, or even an event which happened

Therefore WPF defines three types of triggers:

  1. Trigger: allows to change values of properties or execute actions depending on the value of a property of the control on which they are applied.
  2. DataTrigger: allows to change values of properties or execute actions depending on the value of a property of the datacontext of a control on which they are applied.
  3. EventTrigger: allows to execute actions when an event happens (You cannot set properties with this type of trigger)

Property Triggers

Concepts

What Triggers allow you to do is:

  • change certain properties / execute certain actions
  • depending on the value of other properties

We thus need following pieces of information

  1. The property (or multiple properties) to watch
  2. The value on which to trigger
    • What property (or multiple properties) to set and their target value.
    • Or what action to take when the proeperty gets its value or "loses" its value.

The simplest case is of course when source and target property are on the same object. But there are of course several permutations possible in defining the property to watch and the property to set. Some which immediately come to mind:

  • What if the source property and the targetproperty are not on the same object?
  • What is the scope of the reference? Can we reference properties of objects in different child trees of a scope.
  • Can we reference properties of objects in different windows?
  • How to reference properties of objects across exe and dll boundaries

How to do it?

The simplest implementation is the following

XML
<!-- A most basic case: the value of a property inside a control sets a property on that same control 
	If you want multiple properties to be set, just provide multiple setters -->
<Style x:Key="styleWithTriggerUsingSetter" TargetType="Control">
	<Setter Property="Control.Background" Value="Blue" />
	<Style.Triggers>
		<Trigger Property="IsFocused" Value="True">
			<Setter Property="Foreground" Value="Red" />
			<Setter Property="BorderThickness" Value="5" />
		</Trigger>
	</Style.Triggers>
</Style>

<!-- using the trigger is simply applying the style on a control -->
<TextBox Text="Textbox with simple trigger using setter" Style="{StaticResource ResourceKey=styleWithTriggerUsingSetter}"/>

The above definitions will result in following visuals:

Image 1

As metioned above, it is also possible to specify actions to perform. Those actions are anything derived from TriggerAction. A basic example is following:

XML
<!-- notice how the exit actions animmates to another backgroundcolor than the one defined in the setter
	Conlusion: you are responsible for transitioning back to the desired beginstate-->
<Style x:Key="styleWithTriggerUsingAction" TargetType="TextBox">
	<Setter Property="TextBox.Background" Value="Blue" />
	<Style.Triggers>
		<Trigger Property="IsFocused" Value="True">
			<Trigger.EnterActions>
				<BeginStoryboard>
					<Storyboard>
						<ColorAnimation 
						Storyboard.TargetProperty="(TextBox.Background).(SolidColorBrush.Color)" To="Green" Duration="0:0:0.25" >
						</ColorAnimation>
					</Storyboard>
				</BeginStoryboard>
			</Trigger.EnterActions>
			<Trigger.ExitActions>
				<BeginStoryboard>
					<Storyboard>
						<ColorAnimation 
						Storyboard.TargetProperty="(TextBox.Background).(SolidColorBrush.Color)" To="Red" Duration="0:0:0.25" >
						</ColorAnimation>
					</Storyboard>
				</BeginStoryboard>
			</Trigger.ExitActions>
		</Trigger>
	</Style.Triggers>
</Style>

<TextBox Text="Textbox with simple trigger using actions" Style="{StaticResource ResourceKey=styleWithTriggerUsingAction}"/>

The above definitions will result in following visuals:

Image 2

Notice how in the case of actions we have to specify EnterActions and ExitActions, where for simple Setters a single value will suffice.

If you think about this, it is logical: the first are simple property setters, so a simple assignment wil suffice. WPF under the hood remembers the original value and when the trigger condition is no longer valid, just applies the old value again. For actions however, some code is executed and it may not be immediately clear how to undo the action. That is why we have enter actions and leave actions: you can think of them as do-actions and undo-actions.

In the above case, if we would have supplied no ExitActions, this would have been the result

XML
<!-- notice that when we provide no exit actions our color is not reset -->
<Style x:Key="styleWithTriggerUsingActionNoExit" TargetType="TextBox">
	<Setter Property="TextBox.Background" Value="Blue" />
	<Style.Triggers>
		<Trigger Property="IsFocused" Value="True">
			<Trigger.EnterActions>
				<BeginStoryboard>
					<Storyboard>
						<ColorAnimation 
						Storyboard.TargetProperty="(TextBox.Background).(SolidColorBrush.Color)" To="Green" Duration="0:0:0.25" >
						</ColorAnimation>
					</Storyboard>
				</BeginStoryboard>
			</Trigger.EnterActions>
		</Trigger>
	</Style.Triggers>
</Style>

<TextBox Text="Textbox with simple trigger using actions (no exit)" Style="{StaticResource ResourceKey=styleWithTriggerUsingActionNoExit}"/>

The above definitions will result in following visuals:

Image 3

Of course, there are times when you will want to specify multiple conditions which must be met. For this, there is de MultiTrigger. You can specify multiple conditions which must all be met: the MultiTrigger thus effectively performs an AND operation on the conditions:

XML
<!-- If we want to specify multiple conditions, we must use a MultiTrigger
	All conditions in the MultiTrigger.Conditions collection must be met befor the Property is set -->
<Style x:Key="checkBoxStyleWithMultiTrigger">
	<Style.Triggers>
		<MultiTrigger>
			<MultiTrigger.Conditions>
				<Condition Property="CheckBox.IsMouseOver" Value="True"></Condition>
				<Condition Property="CheckBox.IsChecked" Value="True"></Condition>
			</MultiTrigger.Conditions>
			<Setter Property="Control.Foreground" Value="Red" />
		</MultiTrigger>
	</Style.Triggers>
</Style>

<CheckBox Content="Checkbox with multitrigger" Style="{StaticResource ResourceKey=checkBoxStyleWithMultiTrigger}"/>

The above definitions will result in following visuals:

Image 4

Specifying an OR condition is simply done by having multiple triggers in the Triggers collection of the Style:

XML
<Style x:Key="expliciteMergeStyleWithSomeTriggers">
	<Style.Triggers>
		<!-- condition 1 in truth table -->
		<Trigger Property="CheckBox.IsMouseOver" Value="True">
			<Setter Property="Control.Foreground" Value="Green" />
		</Trigger>
		<MultiTrigger>
			<!-- condition 2 in truth table -->
			<MultiTrigger.Conditions>
				<Condition Property="CheckBox.IsMouseOver" Value="True"></Condition>
				<Condition Property="CheckBox.IsChecked" Value="True"></Condition>
			</MultiTrigger.Conditions>
			<Setter Property="Control.Foreground" Value="Red" />
		</MultiTrigger>
	</Style.Triggers>
</Style>

<CheckBox Content="Checkbox with explicite merged multitrigger" Style="{StaticResource ResourceKey=expliciteMergeStyleWithSomeTriggers}"/>

The above definitions will result in following visuals:

Image 5

There is a caveat here however: because we can have overlapping conditions you must be very carefull with the ordering of your Triggers. If multiple Triggers fire, then the last one in the list will be applied.

Take for example the above combination: it will be clear that the single condition Property="CheckBox.IsMouseOver" Value="True" is True independent of the outcome of the condition <condition property="CheckBox.IsChecked" value="True"></condition>. As a result, the first Trigger will fire from the moment the mousepointer is over the control to which the Style is applied. However, the second MultiTrigger will fire when the mousepointer is over the control AND the checkbox is checked.

The net result will be that:

  1. In case the mouse is NOT over the control, the foreground will be the standard color for that control.
  2. In case the CheckBox IS NOT checked and the mouse is over the control, the foreground will be green
  3. In case the CheckBox IS checked and the mouse is over the control, the foreground will be red

However, let's switch the ordering of the Triggers:

XML
<Style x:Key="expliciteMergeStyleWithSomeTriggersWrong">
	<Style.Triggers>
		<MultiTrigger>
			<MultiTrigger.Conditions>
				<Condition Property="CheckBox.IsMouseOver" Value="True"></Condition>
				<Condition Property="CheckBox.IsChecked" Value="True"></Condition>
			</MultiTrigger.Conditions>
			<Setter Property="Control.Foreground" Value="Red" />
		</MultiTrigger>
		<Trigger Property="CheckBox.IsMouseOver" Value="True">
			<Setter Property="Control.Foreground" Value="Green" />
		</Trigger>
	</Style.Triggers>
</Style>

<CheckBox Content="Checkbox with explicite merged multitrigger (wrong ordering)" Style="{StaticResource ResourceKey=expliciteMergeStyleWithSomeTriggersWrong}"/>

Now, if the mouse is over the control, the foreground will ALWAYS be green, irrespective of the state of the CheckBox. In this case the last evaluated condition is the Trigger which is True from the moment the mouse is over he control, and thus hides the MultiTrigger which can be True at the same time.

This will result in following visuals:

Image 6

This can lead to some unexpected results, like as if Trigger is never being fired:

XML
<Style x:Key="styleWithSomeTriggersOrdering1">
	<Style.Triggers>
		<Trigger Property="CheckBox.IsMouseOver" Value="True">
			<Setter Property="Control.Foreground" Value="Green" />
		</Trigger>
		<Trigger Property="CheckBox.IsChecked" Value="True">
			<Setter Property="Control.Foreground" Value="Red" />
		</Trigger>
	</Style.Triggers>
</Style>
<Style x:Key="styleWithSomeTriggersOrdering2">
	<Style.Triggers>
		<Trigger Property="CheckBox.IsChecked" Value="True">
			<Setter Property="Control.Foreground" Value="Red" />
		</Trigger>
		<Trigger Property="CheckBox.IsMouseOver" Value="True">
			<Setter Property="Control.Foreground" Value="Green" />
		</Trigger>
	</Style.Triggers>
</Style>

<CheckBox Content="Checkbox with trigger and MouseOver/Checked ordering: Checked with green foreground is impossible" Style="{StaticResource ResourceKey=styleWithSomeTriggersOrdering1}"/>
<CheckBox Content="Checkbox with trigger and Checked/MouseOver ordering: Checked with green foreground is possible" Style="{StaticResource ResourceKey=styleWithSomeTriggersOrdering2}"/>

The above definitions will result in following visuals:

 Image 7

Image 8

It is of course also possible to use the BasedOn attribute. The final result of this is the merging of the Triggers of the two Styles, with the Triggers of the base Style comming first and those of the other comming last:

XAML
<Style x:Key="styleWithTriggerUsingSetter" TargetType="Control">
	<Setter Property="Control.Background" Value="Blue" />
	<Style.Triggers>
		<Trigger Property="IsFocused" Value="True">
			<Setter Property="Foreground" Value="Red" />
			<Setter Property="BorderThickness" Value="5" />
		</Trigger>
	</Style.Triggers>
</Style>
<!-- BasedOn merges the setters -->
<Style x:Key="basedOnStyleWithTrigger" BasedOn="{StaticResource styleWithTriggerUsingSetter}" TargetType="Control">
	<Style.Triggers>
		<Trigger Property="Control.IsFocused" Value="True">
			<Setter Property="Control.BorderBrush" Value="Green" />
			<Setter Property="Control.Foreground" Value="Turquoise" />
		</Trigger>
	</Style.Triggers>
</Style>

<TextBox Text="Textbox with based on trigger" Style="{StaticResource ResourceKey=basedOnStyleWithTrigger}"/>

The above definitions will result in following visuals:

Image 9

Notice how the BorderThickness also changes! The Trigger from the BasedOn style is thus not just simply replaced but is effectively merged resulting in 3 Setters being executed when it fires. The above trigger is logically identical to following:

XML
<!-- the above basedOnStyleWithTrigger is logically identical to following -->
<Style x:Key="basedOnStyleWithTriggerExplicite" TargetType="Control">
	<Style.Triggers>
		<Trigger Property="Control.IsFocused" Value="True">
			<Setter Property="BorderThickness" Value="5" />
			<Setter Property="Control.BorderBrush" Value="Green" />
			<Setter Property="Control.Foreground" Value="Yellow" />
		</Trigger>
	</Style.Triggers>
</Style>

You can of course also use MultiTriggers in the Styles:

XML
<Style x:Key="checkBoxStyleWithMultiTrigger">
	<Style.Triggers>
		<MultiTrigger>
			<MultiTrigger.Conditions>
				<Condition Property="CheckBox.IsMouseOver" Value="True"></Condition>
				<Condition Property="CheckBox.IsChecked" Value="True"></Condition>
			</MultiTrigger.Conditions>
			<Setter Property="Control.Foreground" Value="Red" />
		</MultiTrigger>
	</Style.Triggers>
</Style>
<Style x:Key="basedOnMultiStyleWithTrigger" BasedOn="{StaticResource checkBoxStyleWithMultiTrigger}">
	<Style.Triggers>
		<Trigger Property="CheckBox.IsMouseOver" Value="True">
			<Setter Property="Control.Foreground" Value="Green" />
		</Trigger>
	</Style.Triggers>
</Style>

<CheckBox Content="Checkbox with based on multitrigger" Style="{StaticResource ResourceKey=basedOnMultiStyleWithTrigger}"/>

In combination with the above discussion about the ordering of Triggers, this also can lead to unexpected results or subtle bugs. Because BasedOn results in a merger of the Triggers, there is effectively also an ordering of the merged triggers and thus the above discussion holds. The above example with the MultiTrigger results in a merged definition equivalent with the failing ordering example (see the Style with key x:Key = "expliciteMergeStyleWithSomeTriggersWrong").

Ok, you should have a feeling for how these Triggers work by now

So far, we've not been paying any attention as to the kind of properties we can monitor for changes. After all, our trigger must somehow get notified of the changes to the property. It will be no surprise that regular properties will not be sufficient to act as sources of Triggers. However, the Trigger also does NOT support properties backed by INotifyPropertyChanged notification

Following will thus have no result:

C#
public class MyCustomButton : Button, INotifyPropertyChanged
{
	bool mNotifyChangesProperty;
	public bool NotifyChangesProperty
	{
		get { return mNotifyChangesProperty; }
		set
		{
			mNotifyChangesProperty = value;
			OnPropertyChanged("NotifyChangesProperty");
		}
	}

	// This property provides no notification of
	//	any changes made to its value
	public bool MuteProperty
	{
		get;
		set;
	}

	public event PropertyChangedEventHandler PropertyChanged;
	protected virtual void OnPropertyChanged(string propertyName)
	{
		PropertyChangedEventHandler handler = PropertyChanged;
		if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
	}

}
XML
<!-- following will not work: we can only set Property Triggers on Dependency Properties -->
<Style x:Key="triggerFromMuteProperty">
	<Setter Property="Control.Background" Value="Green" />
	<Style.Triggers>
		<Trigger Property="me:MyCustomButton.MuteProperty" Value="True">
			<Setter Property="Control.Foreground" Value="Red" />
		</Trigger>
	</Style.Triggers>
</Style>
<!-- following will not work: we can only set Property Triggers on Dependency Properties -->
<Style x:Key="triggerFromNotifyChangedProperty">
	<Setter Property="Control.Background" Value="Green" />
	<Style.Triggers>
		<Trigger Property="me:MyCustomButton.NotifyChangesProperty" Value="True">
			<Setter Property="Control.Foreground" Value="Red" />
		</Trigger>
	</Style.Triggers>
</Style>

<me:MyCustomButton x:Name="btnMuteProperty" Content="Button with regular property: failed binding"  Style="{StaticResource ResourceKey=triggerFromMuteProperty}" Click="MutePropertyOnClickHandler"/>
<me:MyCustomButton x:Name="btnNotifyChangesProperty" Content="Button with INotifyPropertyChanged property: failed binding"  Style="{StaticResource ResourceKey=triggerFromNotifyChangedProperty}" Click="NotifyChangedPropertyOnClickHandler"/>

The above sections are commented in the sourcecode, just uncomment them to see what happens if you do try this.

OK, so what is the scope of our trigger? Can we get to properties of other elements? Can we get to properties of the types of our properties? Let's find out.

First, let's try to get at some other element: the Triggers Setter has an attribute called TargetName which looks promissing. Unfortunately, it is not useable outside templates. According to the MSDN documentation (and also Stackoverflow):

You can set this property to the name of any element within the scope of where the setter collection (the collection that this setter is part of) is applied. This is typically a named element that is within the template that contains this setter.

While the first part of the sentence might leave an opening for applying this directly to controls, practice quickly gets us dissapointed:

XML
<!-- following will not work. It will give following compilation error:
	TargetName property cannot be set on a Style Setter
	This has nothing to do with the type of trigger but simply the fact where using it in a Style -->
<Style x:Key="styleWithCrossObjectTrigger" TargetType="Control">
	<Style.Triggers>
		<Trigger Property="CheckBox.IsChecked" Value="True">
			<Setter TargetName="txtTarget" Property="Control.Background" Value="Red" />
		</Trigger>
	</Style.Triggers>
</Style>
<Style x:Key="styleWithCrossObjectDataTrigger" TargetType="Control">
	<Style.Triggers>
		<DataTrigger Binding="{Binding Path=MuteProperty}" Value="True">
			<Setter TargetName="txtTarget" Property="Control.Background" Value="Red" />
		</DataTrigger>
	</Style.Triggers>
</Style>

<!-- Well if we cannot use the TargetName attribute in a Style, then let's try it in the Control.
		Nice try ! Unfortunately the Triggers collection of a FrameWorkElement only supports EventTriggers 
		which do not accept Setters-->
<!--<StackPanel.Triggers>
	<Trigger Property="CheckBox.IsChecked" Value="True">
		<Setter TargetName="txtTarget" Property="Control.Background" Value="Red" />
	</Trigger>
</StackPanel.Triggers>-->

The above sections are commented in the sourcecode, just uncomment them to see what happens if you do try this.

So let's go for the second part:

XML
<!-- A ControlTemplate Triggers collection does allow Property Triggers and those are allowed to specify the TargetName attribute -->
<Style x:Key="styleWithTargetNameInTemplate" TargetType="Button">
	<Setter Property="Template">
		<Setter.Value>
			<ControlTemplate TargetType="Button">
				<StackPanel>
					<ContentPresenter Content="{Binding Path=Content, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Button}}}" />
					<Label x:Name="ourTarget" Content="Label inside ControlTemplate" Background="Green" />
				</StackPanel>
				<ControlTemplate.Triggers>
					<Trigger Property="IsFocused" Value="true">
						<Setter TargetName="ourTarget" Property="Background" Value="Red" />
												</Trigger>
				</ControlTemplate.Triggers>
			</ControlTemplate>
		</Setter.Value>
	</Setter>
</Style>
<!-- TargetName only works if it is used in the triggers collection of a controltemplate
	You can still not use it inside ordinary controls in the controltemplate -->
<!--<Style x:Key="styleWithTargetNameInTemplatePermutation1" TargetType="Button">
	<Setter Property="Template">
		<Setter.Value>
			<ControlTemplate TargetType="Button">
				<StackPanel>
					<StackPanel.Style>
						<Style>
							<Style.Triggers>
									<Trigger Property="Button.IsFocused" Value="true">
										<Setter TargetName="ourTarget" Property="Background" Value="Red" />
									</Trigger>
								</Style.Triggers>
							</Style>
						</StackPanel.Style>
						<ContentPresenter Content="{Binding Path=Content, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Button}}}" />
						<Label x:Name="ourTarget" Content="Label inside ControlTemplate" Background="Green" />
					</StackPanel>
				</ControlTemplate>
			</Setter.Value>
		</Setter>
	</Style>-->
	
	<Button Content="Button with Template using TargetName"  Style="{StaticResource ResourceKey=styleWithTargetNameInTemplate}" />
	<!-- the scope of the name is confined to the controltemplate
		as a result, if we use the same controltemplate for another button, then WPF considers this as another name -->
	<Button Content="Other Button with same Template using TargetName"  Style="{StaticResource ResourceKey=styleWithTargetNameInTemplate}" />

The above definitions will result in following visuals:

Image 10

Ok, so much for cross object trigering.

So, we must stay inside the object on which we defined the trigger. But can we instruct WPF to search inside the object of a property?

C#
// We have a type MyCustomButtonWithDataContext with a dependency property TypedDataContextProperty 
//	of a type DependencyPropertyDataClass also having a dependency property MyDependencyProperty:
public class DependencyPropertyDataClass : DependencyObject
{
	public static readonly DependencyProperty MyDependencyPropertyProperty =
		DependencyProperty.Register("MyDependencyProperty", typeof(bool), typeof(DependencyPropertyDataClass), new PropertyMetadata(false));

	public bool MyDependencyProperty
	{
		get { return (bool)GetValue(MyDependencyPropertyProperty); }
		set { SetValue(MyDependencyPropertyProperty, value); }
	}

}

public class MyCustomButtonWithDataContext : Button
{
	public MyCustomButtonWithDataContext()
	{
		this.DataContext = new MyCustomButton();
	}

	public static readonly DependencyProperty TypedDataContextProperty =
		DependencyProperty.Register("MyDependencyProperty", typeof(MyCustomButton), typeof(MyCustomButtonWithDataContext), new PropertyMetadata(null));

	public MyCustomButton TypedDataContext
	{
		get { return (MyCustomButton)GetValue(TypedDataContextProperty); }
		set { SetValue(TypedDataContextProperty, value); }
	}
}

Next, we try to define a trigger on this nested property:

XML
<!-- Unfortunately, we cannot set nested properties in a regular trigger: following gives a compilation error -->
<Style x:Key="styleWithTriggerFromNestedProperty">
	<Style.Triggers>
		<Trigger Property="me:MyCustomButtonWithDataContext.TypedDataContext.MyDependencyProperty" Value="True">
			<Setter Property="Control.Foreground" Value="Green" />
		</Trigger>
	</Style.Triggers>
</Style>

This does not work: we get a compilation error

I've been doubting if I should bother with the following case now allready or should wait till the next kind of trigger. Anyway, I decided to draw your attention to this now: what is the scope of the Trigger? Well, the scope is the Control on which it is applied and NOTHING else. It does not check any containing controls, neither does it search for properties in the DataContext.

As a result, following will do nothing:

C#
// Set the datacontext of the button to a checkbox
btnPropTriggerNotifyChangesProperty.DataContext = new CheckBox();

private void PropTriggerNotifyChangedPropertyOnClickHandler(object sender, RoutedEventArgs e)
{
	// toggle the state of the checkbox
	(btnPropTriggerNotifyChangesProperty.DataContext as CheckBox).IsChecked = !(btnPropTriggerNotifyChangesProperty.DataContext as CheckBox).IsChecked;
}
XML
<!-- Set the property of the trigger to the CheckBox.IsChecked property -->
<Style x:Key="propertyTriggerFromNotifyChangedProperty">
	<Style.Triggers>
		<Trigger Property="CheckBox.IsChecked" Value="True">
			<Setter Property="Control.Foreground" Value="Red" />
		</Trigger>
	</Style.Triggers>
</Style>

<Button x:Name="btnPropTriggerNotifyChangesProperty" Content="Button with INotifyPropertyChanged property using propertytrigger" Style="{StaticResource ResourceKey=propertyTriggerFromNotifyChangedProperty}" Click="PropTriggerNotifyChangedPropertyOnClickHandler"/>

We've set the DataContext of the Button to a CheckBox and define a Trigger to monitor for changes in the IsChecked state of the CheckBox. In the click handler of the Button, we toggle the IsChecked porperty of the DataContext. You migh thave expected the Trigger to fire but it doesn't: the Trigger does not look in the Buttons DataContext.

The above definitions will result in following visuals:

Image 11

Which makes for a nice transition to the following type of Trigger: the DataTrigger.

Data Triggers

Concepts

Why do we need DataTriggers? I've allready given you a hint at the end of the final paragraph in the discussion on Triggers: with regular Triggers we can only monitor changes in properties of the control on which the Trigger is applied. We can not trigger on nested properties, or on changes in the DataContext.

Also, by using DataTriggers, we can fire our trigger when INotifyPropertyChanged backed properties change.

The main difference between a regular Trigger and a DataTrigger is that the source of the trigger is not specified simply by the name of a property, but through a Binding object. It is by using a Binding that we now can use INotifyPropertyChanged backed properties, specify another object as the source of the trigger, etc...

Let's find out how to do all this.

How to do it?

DataTriggers, when using the simplest definition, operate as their name suggests on the DataContext of the Control they are defined on.

Let's try this out:

XML
<Style x:Key="styleWithDataTriggerUsingSetter">
	<Style.Triggers>
		<DataTrigger Binding="{Binding Path=IsChecked}" Value="True">
			<Setter Property="CheckBox.Foreground" Value="Green"></Setter>
		</DataTrigger>
	</Style.Triggers>
</Style>

<me:MyCustomCheckBox x:Name="chkWithDataContext" Content="Press any of the below buttons" Style="{StaticResource ResourceKey=styleWithDataTriggerUsingSetter}" />
<Button Content="Click me to check the above CheckBox" Click="CheckCheckBox"/>
<Button Content="Click me to check the DataContext object of the above CheckBox" Click="CheckCheckBoxDataContext"/>	

When clicking the first button nothing happens and this is to be expected: as stated above, the DataTrigger operates on the DataContext and as such, setting the IsChecked property on the CheckBox itself does NOT fire the DataTrigger.

The second Button however does change the IsChecked property of the DataContext and thus also fires the DataTrigger.

So, above definitions lead to following visuals:

 Image 12

Image 13

And just as we can provide EnterActions and ExitActions instead of Setters on a regular Trigger, we can do the same on DataTriggers.

There are of course times when you will want to bind to a property of the control itself. The Binding class provides a bunch of ways to specify the source of the binding. For demonstration purposes, I've provided the below example which provides a Binding to the Control itself:

XML
<Style x:Key="styleWithDataTriggerUsingSetterBindToControl">
	<Style.Triggers>
		<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsChecked}" Value="True">
			<Setter Property="CheckBox.Foreground" Value="Green"></Setter>
		</DataTrigger>
	</Style.Triggers>
</Style>

<me:MyCustomCheckBox x:Name="chkWithDataContextBindToControl" Content="Press the below button" Style="{StaticResource ResourceKey=styleWithDataTriggerUsingSetterBindToControl}" />
<Button Content="Click me to check the above CheckBox with a Control binding" Click="CheckCheckBoxWithControlBinding"/>
C#
private void CheckCheckBoxWithControlBinding(object sender, RoutedEventArgs e)
{
	chkWithDataContextBindToControl.IsChecked = !chkWithDataContextBindToControl.IsChecked;
}

Above definitions lead to following visuals:

Image 14

As mentioned above, the Binding class has several possibilites for specifying its source. I will not mention them all here as not to clutter this article with information, allthough valueable, not specific to DataTriggers but specific to Bindings. I intend to provide a similar discussion as I'm doing on Triggers, but then on Binding in a future post.

Similar to the MultiTrigger, we have the MultiDataTrigger. It is defined in a similar fashion: you specify multiple Conditions which must all be fullfilled before the trigger is fired:

XML
<Style x:Key="styleWithMultiDataTrigger">
	<Style.Triggers>
		<MultiDataTrigger>
			<MultiDataTrigger.Conditions>
				<Condition  Binding="{Binding Path=IsChecked}" Value="True" />
				<Condition  Binding="{Binding Path=PropertyWithOtherName}" Value="True" />
			</MultiDataTrigger.Conditions>
			<Setter Property="CheckBox.Foreground" Value="Green"></Setter>
		</MultiDataTrigger>
	</Style.Triggers>
</Style>

<me:MyCustomCheckBox x:Name="chkWithMultiDataContext" Content="Press any of the below buttons" Style="{StaticResource ResourceKey=styleWithMultiDataTrigger}" />
<!-- the eventhandlers of these buttons each simply set one of the properties the MultiDataTriggers Conditions are bound to -->
<Button Content="MultiData: Click me to toggle the IsChecked of the DataContext object of the above CheckBox" Click="ToggleIsCheckCheckBoxDataContext1"/>
<Button Content="MultiData: Click me to toggle the PropertyWithOtherName of the DataContext object of the above CheckBox" Click="TogglePropertyWithOtherNameCheckBoxDataContext1"/>

It will be no surprise that the trigger only fires when both Conditions are met.

So, above definitions lead to following visuals:

Image 15

There is an alternative to this MultiDataTrigger: the MultiBinding. With a MultiBinding, we can bind to multiple properties at once. However, the problem then is: how do we specify the value on which we want our DataTrigger to fire? After all, we have multiple values now, but we can only specify a single value in our DataTrigger. The solution is to use a class which implements the IMultiValueConverter interface. The single values from your MultiBinding are handed to your converter and then you can merge them into a single return value:

XML
<!-- doing the same with a multibinding: we (mostly) have to provide our own converter -->
<me:MyCustomMultiValueConverter x:Key="myConverter"/>
<Style x:Key="styleWithMultiBindingDataTrigger">
	<Style.Triggers>
		<!-- the string we use for the value must match what we will return from our converter -->
		<DataTrigger Value="V1:True;V2:True">
			<DataTrigger.Binding>
				<MultiBinding Converter="{StaticResource myConverter}">
					<Binding Path="IsChecked" />
					<Binding Path="PropertyWithOtherName" />
				</MultiBinding>
			</DataTrigger.Binding>
			<Setter Property="CheckBox.Foreground" Value="Green"></Setter>
		</DataTrigger>
	</Style.Triggers>
</Style>

<me:MyCustomCheckBox x:Name="chkWithMultiBindingDataContext" Content="Press any of the below buttons" Style="{StaticResource ResourceKey=styleWithMultiBindingDataTrigger}" />
<Button Content="MultiBinding: Click me to toggle the IsChecked of the DataContext object of the above CheckBox" Click="ToggleIsCheckCheckBoxDataContext2"/>
<Button Content="MultiBinding: Click me to toggle the PropertyWithOtherName of the DataContext object of the above CheckBox" Click="TogglePropertyWithOtherNameCheckBoxDataContext2"/>

The code of the valueconverter looks like following:

C#
public class MyCustomMultiValueConverter : IMultiValueConverter
{
	public object Convert(object[] values, Type targetType,
		   object parameter, System.Globalization.CultureInfo culture)
	{
		return "V1:" + values[0].ToString() + ";V2:" + values[1].ToString();
	}
	public object[] ConvertBack(object value, Type[] targetTypes,
		   object parameter, System.Globalization.CultureInfo culture)
	{
		// there is no need to convert back, so we don't implement this
		throw new NotSupportedException("Cannot convert back");
	}
}

So, above definitions lead to following visuals:

Image 16

I think it will be clear from the above discussion that the solution with the MultiBinding is less than ideal: in all but the most simple case, this will require writing a custom class implementing the IMultiValueConverter interface, where the use of a MultiDataTrigger just works out of the box.

You may not have noticed from the above discussion, but in the last samples we have been binding on INotifyPropertyChanged backed properties. The DataTrigger allows notification from Dependency properties as the regular Trigger, but also from INotifyPropertyChanged backed properties. But how exactly do these last work? Let's find out.

The canonical form is pretty straight forward:

sharp
public class NotifyPropertyChangedDataClass : INotifyPropertyChanged
{
	bool mPropertyWithName;
	public bool PropertyWithName
	{
		get { return mPropertyWithName; }
		set
		{
			mPropertyWithName = value;
			OnPropertyChanged("PropertyWithName");
		}
	}

	public event PropertyChangedEventHandler PropertyChanged;
	protected virtual void OnPropertyChanged(string propertyName)
	{
		PropertyChangedEventHandler handler = PropertyChanged;
		if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
	}
}

private void SwitchByPropertyName(object sender, RoutedEventArgs e)
{
	// We have a binding on PropertyWithName and will use this correct name 
	(chkWithDataContextPropertyWithName.DataContext as NotifyPropertyChangedDataClass).PropertyWithName = !(chkWithDataContextPropertyWithName.DataContext as NotifyPropertyChangedDataClass).PropertyWithName;
}
XML
<Style x:Key="styleWithDataTriggerBindByPropertyName">
	<Style.Triggers>
		<DataTrigger Binding="{Binding Path=PropertyWithName}" Value="True">
			<Setter Property="CheckBox.Foreground" Value="Green"></Setter>
		</DataTrigger>
	</Style.Triggers>
</Style>

<CheckBox x:Name="chkWithDataContextPropertyWithName" Content="Press the below button" Style="{StaticResource ResourceKey=styleWithDataTriggerBindByPropertyName}" />
<Button Content="Click me to check the topmost CheckBox using the actual propertyname" Click="SwitchByPropertyName"/>

The binding registers event PropertyChangedEventHandler PropertyChanged to be notified of any changes in properties and by the provided name in the PropertyChangedEventArgs argument knows which property to check.

Above definitions lead to following visuals:

Image 17

It is of course still not possible to have regular properties as sources for the DataTrigger:

C#
private void DataContextMutePropertyOnClickHandler(object sender, RoutedEventArgs e)
{
	(btnDataContextNotifyChangesProperty.DataContext as MyCustomButton).MuteProperty = !(btnDataContextNotifyChangesProperty.DataContext as MyCustomButton).MuteProperty;
}
XML
<!-- altough this will compile and run, it won't work: binding needs a dependency property or an
	INotifyPropertyChanged backed property.
	How is it to know otherwise that the property changed?-->
<Style x:Key="dataTriggerFromMuteProperty">
	<Style.Triggers>
		<DataTrigger Binding="{Binding Path=MuteProperty}" Value="True">
			<Setter Property="Control.Foreground" Value="Red" />
		</DataTrigger>
	</Style.Triggers>
</Style>

<me:MyCustomButtonWithDataContext x:Name="btnDataContextMuteProperty" Content="Button with DataContext regular property"  Style="{StaticResource ResourceKey=dataTriggerFromMuteProperty}" Click="DataContextMutePropertyOnClickHandler"/>

Next are some permutations on the used propertyname, to see how things work. They are somewhat theoretical in nature, so you're free to skip forward.

What happens if we bind a non existing property, but also use this non-existing propertyname in the event PropertyChangedEventHandler PropertyChanged?

sharp
public class NotifyPropertyChangedDataClass : INotifyPropertyChanged
{

	public bool UseCorrectPropertyName
	{
		get;
		set;
	}

	public bool UseOtherPropertyName
	{
		get;
		set;
	}

	bool mPropertyWithName;
	public bool PropertyWithName
	{
		get { return mPropertyWithName; }
		set
		{
			mPropertyWithName = value;
			OnPropertyChanged(UseOtherPropertyName?
				"PropertyWithOtherName"
				:(UseCorrectPropertyName ? "PropertyWithName" : "PropertyWithWrongName"));
		}
	}

	public event PropertyChangedEventHandler PropertyChanged;
	protected virtual void OnPropertyChanged(string propertyName)
	{
		PropertyChangedEventHandler handler = PropertyChanged;
		if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
	}

}


private void SwitchByPropertyWrongName(object sender, RoutedEventArgs e)
{
	// We have a binding on PropertyWithWrongName and will use this wrong name 
	(chkWithDataContextPropertyWithName.DataContext as NotifyPropertyChangedDataClass).UseOtherPropertyName = false;
	(chkWithDataContextPropertyWithName.DataContext as NotifyPropertyChangedDataClass).UseCorrectPropertyName = false;
	(chkWithDataContextPropertyWithName.DataContext as NotifyPropertyChangedDataClass).PropertyWithName = !(chkWithDataContextPropertyWithName.DataContext as NotifyPropertyChangedDataClass).PropertyWithName;
}
XML
<Style x:Key="styleWithDataTriggerBindByNotificationName">
	<Style.Triggers>
		<DataTrigger Binding="{Binding Path=PropertyWithWrongName}" Value="True">
			<Setter Property="CheckBox.Foreground" Value="Blue"></Setter>
		</DataTrigger>
	</Style.Triggers>
</Style>

<CheckBox x:Name="chkWithDataContextOtherPropertyWithWrongName" Content="Press the below button" Style="{StaticResource ResourceKey=styleWithDataTriggerBindByNotificationName}" />
<Button Content="Click me to check the topmost CheckBox using the incorrect propertyname" Click="SwitchByPropertyWrongName"/>

Well, you can't fool .NET this easily: nothing is happening. Apparently, it looks like the Binding checks to see if the propertyname used effecively exists. Thus, you cannot make phantom properties. And because the propertyname used in the above example does not exist, nothing happens when we click the button. It is logical because of the property does not exist, how is the Binding to know the final value?

Another possible permutation is to use in one property, the name of another property

C#
// We're using the same NotifyPropertyChangedDataClass class as above

private void SwitchWronglyByPropertyOtherName(object sender, RoutedEventArgs e)
{
	(chkWithDataContextPropertyWithName.DataContext as NotifyPropertyChangedDataClass).UseOtherPropertyName = true;
	(chkWithDataContextPropertyWithName.DataContext as NotifyPropertyChangedDataClass).UseCorrectPropertyName = false;
	(chkWithDataContextPropertyWithName.DataContext as NotifyPropertyChangedDataClass).PropertyWithName = !(chkWithDataContextPropertyWithName.DataContext as NotifyPropertyChangedDataClass).PropertyWithName;
}

private void SwitchCorrectByPropertyOtherName(object sender, RoutedEventArgs e)
{
	(chkWithDataContextPropertyWithName.DataContext as NotifyPropertyChangedDataClass).PropertyWithOtherName = !(chkWithDataContextPropertyWithName.DataContext as NotifyPropertyChangedDataClass).PropertyWithOtherName;
}
XML
<Style x:Key="styleWithDataTriggerBindByNotificationName">
	<Style.Triggers>
		<DataTrigger Binding="{Binding Path=PropertyWithWrongName}" Value="True">
			<Setter Property="CheckBox.Foreground" Value="Blue"></Setter>
		</DataTrigger>
	</Style.Triggers>
</Style>
<Style x:Key="styleWithDataTriggerBindByOtherName">
	<Style.Triggers>
		<DataTrigger Binding="{Binding Path=PropertyWithOtherName}" Value="True">
			<Setter Property="Control.Foreground" Value="Red" />
		</DataTrigger>
	</Style.Triggers>
</Style>

<CheckBox x:Name="chkWithDataContextOtherPropertyWithWrongName" Content="Press the below button" Style="{StaticResource ResourceKey=styleWithDataTriggerBindByNotificationName}" />
<CheckBox x:Name="chkWithDataContextOtherPropertyWithName" Content="Press the below button" Style="{StaticResource ResourceKey=styleWithDataTriggerBindByOtherName}" />
<Button Content="Click me to check the middle/bottom CheckBox using the wrongly used other propertyname" Click="SwitchWronglyByPropertyOtherName"/>
<Button Content="Click me to check the bottom CheckBox using the correctly used other propertyname" Click="SwitchCorrectByPropertyOtherName"/>

Again, nothing has changed. Allthough you specify the name of the PropertyWithOtherName property in the notification, the actual value of it didn't change. So WPF asks the value of the property, sees that it is still false and does nothing. Of course, using the correct name will fire our DataTrigger as is done with the last button.

What is the scope of the DataTriger and how can we reference other objects as sources, if possible at all?

The discussion on the TargetName property of a Setter still holds: you cannot use it in regular <codestyle< code="">s, but only in <code>Templates:

XML
<!-- following will not work. It will give following compilation error:
	TargetName property cannot be set on a Style Setter
	This has nothing to do with the type of trigger but simply the fact where using it in a Style -->
<Style x:Key="styleWithCrossObjectDataTrigger" TargetType="Control">
	<Style.Triggers>
		<DataTrigger Binding="{Binding Path=MuteProperty}" Value="True">
			<Setter TargetName="txtTarget" Property="Control.Background" Value="Red" />
		</DataTrigger>
	</Style.Triggers>
</Style>

Fortunately, the Binding class has a much richer interface for defining its source then the simple Property property of a regular Trigger. As allready stated above, the Binding class itself deserves an article by itself, which I intend to write. But for the discussion at hand I will show you the simplest syntax for referencing another object by its name:

XML
<Style x:Key="styleWithCrossObjectDataTrigger">
	<Style.Triggers>
		<DataTrigger Binding="{Binding ElementName=chkTarget, Path=IsChecked}" Value="True">
			<Setter Property="Control.Foreground" Value="Red" />
		</DataTrigger>
	</Style.Triggers>
</Style>

<TextBox x:Name="txtTarget" Text="Textbox with trigger source from checkbox"  Style="{StaticResource ResourceKey=styleWithCrossObjectDataTrigger}" />
<CheckBox x:Name="chkTarget" Content="Checkbox wich is the trigger source" >
</CheckBox>

Mind that the Binding references the source of the trigger. Thus, the Style defining the target property to change, must be defined on the object having that property. Or put another way: the Style defining the trigger must be applied on the target of the trigger and NOT the source.

Above definitions lead to following visuals:

Image 18

Event Triggers

Concepts

In the above code we where able to take some actions or set a property when for example the mouse is over a control because many controls provide something like conveniance properties which change their value when a certain event happens. But what if there is no such property?

Enter EventTriggers

How to do it?

EventTriggers allow you to take an action when an event happens.

Following is an example of its use:

XML
<Style x:Key="styleWithEventTrigger" TargetType="TextBlock">
	<Style.Triggers>
		<EventTrigger RoutedEvent="MouseEnter">
			<EventTrigger.Actions>
				<BeginStoryboard>
					<Storyboard>
						<ColorAnimation 
						Storyboard.TargetProperty="(TextBlock.Background).(SolidColorBrush.Color)" To="Green" Duration="0:0:0.25" >
						</ColorAnimation>
					</Storyboard>
				</BeginStoryboard>
			</EventTrigger.Actions>
		</EventTrigger>
	</Style.Triggers>
</Style>

<TextBlock Text="TextBlock with eventtrigger" Background="Red" Style="{StaticResource ResourceKey=styleWithEventTrigger}" />

Above definitions lead to following visuals:

Image 19

Now, read the above sentence once again please. Yes, you can take a TriggerAction but you cannot simply set a property as with the other type of Triggers. If you look around on the internet however you can find workarounds. Thus, following will not work:

XML
<!-- following will not work: you cannot use setters inside an eventtrigger -->
<!--<Style x:Key="styleWithEventTriggerAndSetter" TargetType="TextBlock">
	<Style.Triggers>
		<EventTrigger RoutedEvent="MouseEnter">
			<Setter Property="Control.Foreground" Value="Red" />
		</EventTrigger>
	</Style.Triggers>
</Style>-->

Just as you can use multiple regular Trigger, you can also use multiple EventTriggers:

XML
<Style x:Key="styleWithMultipleEventTriggers" TargetType="TextBox">
	<Style.Triggers>
		<EventTrigger RoutedEvent="MouseEnter">
			<!-- eventtriggers only support actions and not setters like the regular trigger and the datatrigger -->
			<EventTrigger.Actions>
				<BeginStoryboard>
					<Storyboard>
						<ColorAnimation 
						Storyboard.TargetProperty="(TextBox.Background).(SolidColorBrush.Color)" To="Green" Duration="0:0:0.25" >
						</ColorAnimation>
					</Storyboard>
				</BeginStoryboard>
			</EventTrigger.Actions>
		</EventTrigger>
		<EventTrigger RoutedEvent="GotFocus">
			<EventTrigger.Actions>
				<BeginStoryboard>
					<Storyboard>
						<ColorAnimation 
						Storyboard.TargetProperty="(TextBox.Background).(SolidColorBrush.Color)" To="Blue" Duration="0:0:0.25" >
						</ColorAnimation>
					</Storyboard>
				</BeginStoryboard>
			</EventTrigger.Actions>
		</EventTrigger>
	</Style.Triggers>
</Style>

<TextBox Text="TextBox with multiple eventtriggers" Background="Red" Style="{StaticResource ResourceKey=styleWithMultipleEventTriggers}" />

Above definitions lead to following visuals:

Image 20

There is something to be aware of here: the actions are triggered by events and their result remains active. After all, there is no undo of an event. A property can be set to a certain value and then be kind of unset by setting it to another value. An event just happens. That is why in the above definition, once you hover over the control, and by doing so have triggered the MouseEnter event, the Background color will change and remain like that, until another event changes it, like in the above the GotFocus event.

There is no such thing as a MultiEventTrigger. If you think about this its is logical: after all events will always / most of the time happen in a serial manner and not exactly at the same time. And how would you then define "multi"? Two events happening in succession? But how do you define "succession"? Take the above example: MouseEnter and GotFocus. There will have happened MouseMoves inbetween. And what if I cross the control: MouseEnter, MouseMove and MouseLeave and then set the Focus in code. Is this still in succession?

What are the type of events you can use?

Just as the other types of triggers where restricted on the kind of properties they could monitor, the EventTrigger can only monitor RoutedEvents. Thus, following will not work:

XML
<!-- allthough the following does compile, it will not work: you can only use routed events 
You get a runtime exception from the xaml parser-->
<Style x:Key="styleWithEventTriggerNonRoutedEvent" TargetType="TextBlock">
	<Style.Triggers>
		<EventTrigger RoutedEvent="IsEnabledChanged">
			<EventTrigger.Actions>
				<BeginStoryboard>
					<Storyboard>
						<ColorAnimation 
						Storyboard.TargetProperty="(TextBlock.Background).(SolidColorBrush.Color)" To="Green" Duration="0:0:0.25" >
						</ColorAnimation>
					</Storyboard>
				</BeginStoryboard>
			</EventTrigger.Actions>
		</EventTrigger>
	</Style.Triggers>
</Style>

A somewhat special case: the ItemsControl

Concepts

For those who have read the first part in this series of articles, then this probably will not be such a special case. Most of what has been said there is also valid here. That is because the rules used to select and apply a Style are the same. It is only what is defined inside the Style that has changed.

For the others: some controls do have multiple styling properties. An example of these are the ItemsControl derived controls: they have on top of their regular Style also a ItemContainerStyle property. But don't be fooled here: this style assigned to this property is applied to the ItemsContainer and ONLY to the ItemsContainer and NOT to any of it's created children.

How to do it?

Let's play dumb and try the following:

XML
<Style x:Key="myItemsStyle">
	<!-- see http://stackoverflow.com/questions/15103129/wpf-textblock-background-not-being-set-using-style
		for why i'm using TextBlock.Background-->
	<Setter Property="TextBlock.Background" Value="#FF00C1FF" />
	<Setter Property="Control.Foreground" Value="Red" />
	<Style.Triggers>
		<Trigger Property="Control.IsMouseOver" Value="True">
			<Setter Property="TextBlock.Background" Value="Red" />
			<Setter Property="Control.Foreground" Value="#FF00C1FF" />
		</Trigger>
	</Style.Triggers>
</Style>

<!-- There is no background or foreground coloring here: the itemstyle is applied to the
	container which in this case is a ContentPresenter. -->
<ItemsControl Grid.Row="0" ItemContainerStyle="{StaticResource myItemsStyle}" >
	<ItemsControl.ItemsSource>
		<Int32Collection >1,2,3,4,5</Int32Collection>
	</ItemsControl.ItemsSource>
</ItemsControl>

If you try the above example you will notice that nothing is happening. The reason is in the comments of the xml.

Image 21

But, because no itemcontainers are created when using Controls in the ItemsSource, following does have the desired effact, allthough we are using the same Style:

XML
<Style x:Key="myItemsStyle">
	<!-- see http://stackoverflow.com/questions/15103129/wpf-textblock-background-not-being-set-using-style
		for why i'm using TextBlock.Background-->
	<Setter Property="TextBlock.Background" Value="#FF00C1FF" />
	<Setter Property="Control.Foreground" Value="Red" />
	<Style.Triggers>
		<Trigger Property="Control.IsMouseOver" Value="True">
			<Setter Property="TextBlock.Background" Value="Red" />
			<Setter Property="Control.Foreground" Value="#FF00C1FF" />
		</Trigger>
	</Style.Triggers>
</Style>

<ItemsControl Grid.Row="1" ItemContainerStyle="{StaticResource myItemsStyle}" >
	<TextBlock Text="A textBlock" />
	<TextBlock Text="A textBlock" />
	<TextBlock Text="A textBlock" />
</ItemsControl>

Above definitions lead to following visuals:

Image 22

Following also does work. For an explanation I suggest you take a look at the first article.

XML
<Style x:Key="myTextBlockStyle2">
	<Style.Resources>
		<Style TargetType="TextBlock">
			<Style.Triggers>
				<Trigger Property="IsMouseOver" Value="True">
					<Setter Property="Background" Value="Red" />
				</Trigger>
			</Style.Triggers>
		</Style>
	</Style.Resources>
</Style>
<Style x:Key="myListBoxItemsStyle">
	<Setter Property="Control.Background" Value="Red" />
	<Style.Triggers>
		<Trigger Property="Control.IsMouseOver" Value="True">
			<Setter Property="Control.Foreground" Value="Blue" />
			<!--<Setter Property="Control.BorderThickness" Value="5" />-->
		</Trigger>
	</Style.Triggers>
</Style>
<Style x:Key="myItemContainerStyle" TargetType="ListBoxItem">
	<Setter Property="Template">
		<Setter.Value>
			<ControlTemplate>
				<StackPanel>
					<ContentPresenter />
					<Label x:Name="ourTarget" Content="Label inside ControlTemplate" Background="Green" />
				</StackPanel>
				<ControlTemplate.Triggers>
					<Trigger Property="IsMouseOver" Value="true">
						<Setter TargetName="ourTarget" Property="Background" Value="Red" />
					</Trigger>
				</ControlTemplate.Triggers>
			</ControlTemplate>
		</Setter.Value>
	</Setter>
</Style>

<ItemsControl Grid.Row="2" ItemContainerStyle="{StaticResource myTextBlockStyle2}" >
	<ItemsControl.ItemsSource>
		<Int32Collection>10,20,30,40,50</Int32Collection>
	</ItemsControl.ItemsSource>
</ItemsControl>
<!-- A ListBox wraps its data inside a ListBoxItem which does have a background property -->
<ListBox Grid.Row="3" ItemContainerStyle="{StaticResource myListBoxItemsStyle}">
	<ItemsControl.ItemsSource>
		<Int32Collection>100,200,300,400,500</Int32Collection>
	</ItemsControl.ItemsSource>
</ListBox>
<!-- eacht instance of the controltemplate has its own scope 
	thus, if one triggers it does not affect the setters of others -->
<ListBox Grid.Row="4" ItemContainerStyle="{StaticResource myItemContainerStyle}" >
	<ItemsControl.ItemsSource>
		<Int32Collection>101,201,301,401,501</Int32Collection>
	</ItemsControl.ItemsSource>
</ListBox>

Above definitions lead to following visuals:

Image 23

Conclusion

That's it for triggers. I hope you got something from it, I know I have in writing this article. Next will be on Templates, but possible first an intermezzo on Resources and Bindings.

Version history

  • Version 1.0: Initial version
  • Version 1.1: Following changes:
    • Added reference to other articles the series

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Belgium Belgium
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionMessage Closed Pin
2-Nov-20 14:05
Member 149778142-Nov-20 14:05 
QuestionWhat about setting collections? Pin
Member 115938081-Oct-15 6:45
Member 115938081-Oct-15 6:45 
AnswerRe: What about setting collections? Pin
Serge Desmedt1-Oct-15 7:07
professionalSerge Desmedt1-Oct-15 7:07 
GeneralRe: What about setting collections? Pin
Member 115938081-Oct-15 12:38
Member 115938081-Oct-15 12:38 
GeneralRe: What about setting collections? Pin
Serge Desmedt2-Oct-15 19:57
professionalSerge Desmedt2-Oct-15 19:57 
I suppose you just set it then.
Allthough I think I know what you are getting at: you probably want to set your list property to some value from you datacontext.

The above examples do indeed set the proeprty to some fixed value. In your case that would be a list created in the resources.

It is an interesting case which I will surely investigate when I have some time.

Thanks for sharing your experience.

GeneralGood Article Pin
lakshmana_19807-Jul-15 7:05
lakshmana_19807-Jul-15 7:05 
GeneralRe: Good Article Pin
Serge Desmedt7-Jul-15 7:28
professionalSerge Desmedt7-Jul-15 7:28 
GeneralMy vote of 5 Pin
Carsten V2.025-Jun-15 8:52
Carsten V2.025-Jun-15 8:52 
GeneralThank you! Pin
tal_segal13-Apr-15 20:18
tal_segal13-Apr-15 20:18 
GeneralRe: Thank you! Pin
Serge Desmedt14-Apr-15 0:09
professionalSerge Desmedt14-Apr-15 0:09 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.