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:

Styling: with Styling you can set various properties on the controls.

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:

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:

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.

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.

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

The property (or multiple properties) to watch

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

<!-- 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 --><Stylex:Key="styleWithTriggerUsingSetter"TargetType="Control"><SetterProperty="Control.Background"Value="Blue"/><Style.Triggers><TriggerProperty="IsFocused"Value="True"><SetterProperty="Foreground"Value="Red"/><SetterProperty="BorderThickness"Value="5"/></Trigger></Style.Triggers></Style><!-- using the trigger is simply applying the style on a control --><TextBoxText="Textbox with simple trigger using setter"Style="{StaticResource ResourceKey=styleWithTriggerUsingSetter}"/>

The above definitions will result in following visuals:

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

<!-- 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--><Stylex:Key="styleWithTriggerUsingAction"TargetType="TextBox"><SetterProperty="TextBox.Background"Value="Blue"/><Style.Triggers><TriggerProperty="IsFocused"Value="True"><Trigger.EnterActions><BeginStoryboard><Storyboard><ColorAnimationStoryboard.TargetProperty="(TextBox.Background).(SolidColorBrush.Color)"To="Green"Duration="0:0:0.25"></ColorAnimation></Storyboard></BeginStoryboard></Trigger.EnterActions><Trigger.ExitActions><BeginStoryboard><Storyboard><ColorAnimationStoryboard.TargetProperty="(TextBox.Background).(SolidColorBrush.Color)"To="Red"Duration="0:0:0.25"></ColorAnimation></Storyboard></BeginStoryboard></Trigger.ExitActions></Trigger></Style.Triggers></Style><TextBoxText="Textbox with simple trigger using actions"Style="{StaticResource ResourceKey=styleWithTriggerUsingAction}"/>

The above definitions will result in following visuals:

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

<!-- notice that when we provide no exit actions our color is not reset --><Stylex:Key="styleWithTriggerUsingActionNoExit"TargetType="TextBox"><SetterProperty="TextBox.Background"Value="Blue"/><Style.Triggers><TriggerProperty="IsFocused"Value="True"><Trigger.EnterActions><BeginStoryboard><Storyboard><ColorAnimationStoryboard.TargetProperty="(TextBox.Background).(SolidColorBrush.Color)"To="Green"Duration="0:0:0.25"></ColorAnimation></Storyboard></BeginStoryboard></Trigger.EnterActions></Trigger></Style.Triggers></Style><TextBoxText="Textbox with simple trigger using actions (no exit)"Style="{StaticResource ResourceKey=styleWithTriggerUsingActionNoExit}"/>

The above definitions will result in following visuals:

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:

<!-- 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 --><Stylex:Key="checkBoxStyleWithMultiTrigger"><Style.Triggers><MultiTrigger><MultiTrigger.Conditions><ConditionProperty="CheckBox.IsMouseOver"Value="True"></Condition><ConditionProperty="CheckBox.IsChecked"Value="True"></Condition></MultiTrigger.Conditions><SetterProperty="Control.Foreground"Value="Red"/></MultiTrigger></Style.Triggers></Style><CheckBoxContent="Checkbox with multitrigger"Style="{StaticResource ResourceKey=checkBoxStyleWithMultiTrigger}"/>

The above definitions will result in following visuals:

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

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:

In case the mouse is NOT over the control, the foreground will be the standard color for that control.

In case the CheckBox IS NOT checked and the mouse is over the control, the foreground will be green

In case the CheckBox IS checked and the mouse is over the control, the foreground will be red

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:

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

<Stylex:Key="styleWithSomeTriggersOrdering1"><Style.Triggers><TriggerProperty="CheckBox.IsMouseOver"Value="True"><SetterProperty="Control.Foreground"Value="Green"/></Trigger><TriggerProperty="CheckBox.IsChecked"Value="True"><SetterProperty="Control.Foreground"Value="Red"/></Trigger></Style.Triggers></Style><Stylex:Key="styleWithSomeTriggersOrdering2"><Style.Triggers><TriggerProperty="CheckBox.IsChecked"Value="True"><SetterProperty="Control.Foreground"Value="Red"/></Trigger><TriggerProperty="CheckBox.IsMouseOver"Value="True"><SetterProperty="Control.Foreground"Value="Green"/></Trigger></Style.Triggers></Style><CheckBoxContent="Checkbox with trigger and MouseOver/Checked ordering: Checked with green foreground is impossible"Style="{StaticResource ResourceKey=styleWithSomeTriggersOrdering1}"/><CheckBoxContent="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:

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:

<Stylex:Key="styleWithTriggerUsingSetter"TargetType="Control"><SetterProperty="Control.Background"Value="Blue"/><Style.Triggers><TriggerProperty="IsFocused"Value="True"><SetterProperty="Foreground"Value="Red"/><SetterProperty="BorderThickness"Value="5"/></Trigger></Style.Triggers></Style><!-- BasedOn merges the setters --><Stylex:Key="basedOnStyleWithTrigger"BasedOn="{StaticResource styleWithTriggerUsingSetter}"TargetType="Control"><Style.Triggers><TriggerProperty="Control.IsFocused"Value="True"><SetterProperty="Control.BorderBrush"Value="Green"/><SetterProperty="Control.Foreground"Value="Turquoise"/></Trigger></Style.Triggers></Style><TextBoxText="Textbox with based on trigger"Style="{StaticResource ResourceKey=basedOnStyleWithTrigger}"/>

The above definitions will result in following visuals:

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:

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

You can of course also use MultiTriggers in the Styles:

<Stylex:Key="checkBoxStyleWithMultiTrigger"><Style.Triggers><MultiTrigger><MultiTrigger.Conditions><ConditionProperty="CheckBox.IsMouseOver"Value="True"></Condition><ConditionProperty="CheckBox.IsChecked"Value="True"></Condition></MultiTrigger.Conditions><SetterProperty="Control.Foreground"Value="Red"/></MultiTrigger></Style.Triggers></Style><Stylex:Key="basedOnMultiStyleWithTrigger"BasedOn="{StaticResource checkBoxStyleWithMultiTrigger}"><Style.Triggers><TriggerProperty="CheckBox.IsMouseOver"Value="True"><SetterProperty="Control.Foreground"Value="Green"/></Trigger></Style.Triggers></Style><CheckBoxContent="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 not work: we can only set Property Triggers on Dependency Properties --><Stylex:Key="triggerFromMuteProperty"><SetterProperty="Control.Background"Value="Green"/><Style.Triggers><TriggerProperty="me:MyCustomButton.MuteProperty"Value="True"><SetterProperty="Control.Foreground"Value="Red"/></Trigger></Style.Triggers></Style><!-- following will not work: we can only set Property Triggers on Dependency Properties --><Stylex:Key="triggerFromNotifyChangedProperty"><SetterProperty="Control.Background"Value="Green"/><Style.Triggers><TriggerProperty="me:MyCustomButton.NotifyChangesProperty"Value="True"><SetterProperty="Control.Foreground"Value="Red"/></Trigger></Style.Triggers></Style><me:MyCustomButtonx:Name="btnMuteProperty"Content="Button with regular property: failed binding"Style="{StaticResource ResourceKey=triggerFromMuteProperty}"Click="MutePropertyOnClickHandler"/><me:MyCustomButtonx: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:

<!-- 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 --><Stylex:Key="styleWithCrossObjectTrigger"TargetType="Control"><Style.Triggers><TriggerProperty="CheckBox.IsChecked"Value="True"><SetterTargetName="txtTarget"Property="Control.Background"Value="Red"/></Trigger></Style.Triggers></Style><Stylex:Key="styleWithCrossObjectDataTrigger"TargetType="Control"><Style.Triggers><DataTriggerBinding="{Binding Path=MuteProperty}"Value="True"><SetterTargetName="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:

<!-- A ControlTemplate Triggers collection does allow Property Triggers and those are allowed to specify the TargetName attribute --><Stylex:Key="styleWithTargetNameInTemplate"TargetType="Button"><SetterProperty="Template"><Setter.Value><ControlTemplateTargetType="Button"><StackPanel><ContentPresenterContent="{Binding Path=Content, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Button}}}"/><Labelx:Name="ourTarget"Content="Label inside ControlTemplate"Background="Green"/></StackPanel><ControlTemplate.Triggers><TriggerProperty="IsFocused"Value="true"><SetterTargetName="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>--><ButtonContent="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 --><ButtonContent="Other Button with same Template using TargetName"Style="{StaticResource ResourceKey=styleWithTargetNameInTemplate}"/>

The above definitions will result in following visuals:

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?

<!-- Unfortunately, we cannot set nested properties in a regular trigger: following gives a compilation error --><Stylex:Key="styleWithTriggerFromNestedProperty"><Style.Triggers><TriggerProperty="me:MyCustomButtonWithDataContext.TypedDataContext.MyDependencyProperty"Value="True"><SetterProperty="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:

// Set the datacontext of the button to a checkbox
btnPropTriggerNotifyChangesProperty.DataContext = new CheckBox();
privatevoid PropTriggerNotifyChangedPropertyOnClickHandler(object sender, RoutedEventArgs e)
{
// toggle the state of the checkbox
(btnPropTriggerNotifyChangesProperty.DataContext as CheckBox).IsChecked = !(btnPropTriggerNotifyChangesProperty.DataContext as CheckBox).IsChecked;
}

<!-- Set the property of the trigger to the CheckBox.IsChecked property --><Stylex:Key="propertyTriggerFromNotifyChangedProperty"><Style.Triggers><TriggerProperty="CheckBox.IsChecked"Value="True"><SetterProperty="Control.Foreground"Value="Red"/></Trigger></Style.Triggers></Style><Buttonx: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:

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:

<Stylex:Key="styleWithDataTriggerUsingSetter"><Style.Triggers><DataTriggerBinding="{Binding Path=IsChecked}"Value="True"><SetterProperty="CheckBox.Foreground"Value="Green"></Setter></DataTrigger></Style.Triggers></Style><me:MyCustomCheckBoxx:Name="chkWithDataContext"Content="Press any of the below buttons"Style="{StaticResource ResourceKey=styleWithDataTriggerUsingSetter}"/><ButtonContent="Click me to check the above CheckBox"Click="CheckCheckBox"/><ButtonContent="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:

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:

<Stylex:Key="styleWithDataTriggerUsingSetterBindToControl"><Style.Triggers><DataTriggerBinding="{Binding RelativeSource={RelativeSource Self}, Path=IsChecked}"Value="True"><SetterProperty="CheckBox.Foreground"Value="Green"></Setter></DataTrigger></Style.Triggers></Style><me:MyCustomCheckBoxx:Name="chkWithDataContextBindToControl"Content="Press the below button"Style="{StaticResource ResourceKey=styleWithDataTriggerUsingSetterBindToControl}"/><ButtonContent="Click me to check the above CheckBox with a Control binding"Click="CheckCheckBoxWithControlBinding"/>

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:

<Stylex:Key="styleWithMultiDataTrigger"><Style.Triggers><MultiDataTrigger><MultiDataTrigger.Conditions><ConditionBinding="{Binding Path=IsChecked}"Value="True"/><ConditionBinding="{Binding Path=PropertyWithOtherName}"Value="True"/></MultiDataTrigger.Conditions><SetterProperty="CheckBox.Foreground"Value="Green"></Setter></MultiDataTrigger></Style.Triggers></Style><me:MyCustomCheckBoxx: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 --><ButtonContent="MultiData: Click me to toggle the IsChecked of the DataContext object of the above CheckBox"Click="ToggleIsCheckCheckBoxDataContext1"/><ButtonContent="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:

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:

<!-- doing the same with a multibinding: we (mostly) have to provide our own converter --><me:MyCustomMultiValueConverterx:Key="myConverter"/><Stylex:Key="styleWithMultiBindingDataTrigger"><Style.Triggers><!-- the string we use for the value must match what we will return from our converter --><DataTriggerValue="V1:True;V2:True"><DataTrigger.Binding><MultiBindingConverter="{StaticResource myConverter}"><BindingPath="IsChecked"/><BindingPath="PropertyWithOtherName"/></MultiBinding></DataTrigger.Binding><SetterProperty="CheckBox.Foreground"Value="Green"></Setter></DataTrigger></Style.Triggers></Style><me:MyCustomCheckBoxx:Name="chkWithMultiBindingDataContext"Content="Press any of the below buttons"Style="{StaticResource ResourceKey=styleWithMultiBindingDataTrigger}"/><ButtonContent="MultiBinding: Click me to toggle the IsChecked of the DataContext object of the above CheckBox"Click="ToggleIsCheckCheckBoxDataContext2"/><ButtonContent="MultiBinding: Click me to toggle the PropertyWithOtherName of the DataContext object of the above CheckBox"Click="TogglePropertyWithOtherNameCheckBoxDataContext2"/>

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.

<Stylex:Key="styleWithDataTriggerBindByPropertyName"><Style.Triggers><DataTriggerBinding="{Binding Path=PropertyWithName}"Value="True"><SetterProperty="CheckBox.Foreground"Value="Green"></Setter></DataTrigger></Style.Triggers></Style><CheckBoxx:Name="chkWithDataContextPropertyWithName"Content="Press the below button"Style="{StaticResource ResourceKey=styleWithDataTriggerBindByPropertyName}"/><ButtonContent="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:

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

<!-- 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?--><Stylex:Key="dataTriggerFromMuteProperty"><Style.Triggers><DataTriggerBinding="{Binding Path=MuteProperty}"Value="True"><SetterProperty="Control.Foreground"Value="Red"/></DataTrigger></Style.Triggers></Style><me:MyCustomButtonWithDataContextx: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?

<Stylex:Key="styleWithDataTriggerBindByNotificationName"><Style.Triggers><DataTriggerBinding="{Binding Path=PropertyWithWrongName}"Value="True"><SetterProperty="CheckBox.Foreground"Value="Blue"></Setter></DataTrigger></Style.Triggers></Style><CheckBoxx:Name="chkWithDataContextOtherPropertyWithWrongName"Content="Press the below button"Style="{StaticResource ResourceKey=styleWithDataTriggerBindByNotificationName}"/><ButtonContent="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

<Stylex:Key="styleWithDataTriggerBindByNotificationName"><Style.Triggers><DataTriggerBinding="{Binding Path=PropertyWithWrongName}"Value="True"><SetterProperty="CheckBox.Foreground"Value="Blue"></Setter></DataTrigger></Style.Triggers></Style><Stylex:Key="styleWithDataTriggerBindByOtherName"><Style.Triggers><DataTriggerBinding="{Binding Path=PropertyWithOtherName}"Value="True"><SetterProperty="Control.Foreground"Value="Red"/></DataTrigger></Style.Triggers></Style><CheckBoxx:Name="chkWithDataContextOtherPropertyWithWrongName"Content="Press the below button"Style="{StaticResource ResourceKey=styleWithDataTriggerBindByNotificationName}"/><CheckBoxx:Name="chkWithDataContextOtherPropertyWithName"Content="Press the below button"Style="{StaticResource ResourceKey=styleWithDataTriggerBindByOtherName}"/><ButtonContent="Click me to check the middle/bottom CheckBox using the wrongly used other propertyname"Click="SwitchWronglyByPropertyOtherName"/><ButtonContent="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:

<!-- 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 --><Stylex:Key="styleWithCrossObjectDataTrigger"TargetType="Control"><Style.Triggers><DataTriggerBinding="{Binding Path=MuteProperty}"Value="True"><SetterTargetName="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:

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:

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?

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:

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

<Stylex:Key="styleWithMultipleEventTriggers"TargetType="TextBox"><Style.Triggers><EventTriggerRoutedEvent="MouseEnter"><!-- eventtriggers only support actions and not setters like the regular trigger and the datatrigger --><EventTrigger.Actions><BeginStoryboard><Storyboard><ColorAnimationStoryboard.TargetProperty="(TextBox.Background).(SolidColorBrush.Color)"To="Green"Duration="0:0:0.25"></ColorAnimation></Storyboard></BeginStoryboard></EventTrigger.Actions></EventTrigger><EventTriggerRoutedEvent="GotFocus"><EventTrigger.Actions><BeginStoryboard><Storyboard><ColorAnimationStoryboard.TargetProperty="(TextBox.Background).(SolidColorBrush.Color)"To="Blue"Duration="0:0:0.25"></ColorAnimation></Storyboard></BeginStoryboard></EventTrigger.Actions></EventTrigger></Style.Triggers></Style><TextBoxText="TextBox with multiple eventtriggers"Background="Red"Style="{StaticResource ResourceKey=styleWithMultipleEventTriggers}"/>

Above definitions lead to following visuals:

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:

<!-- allthough the following does compile, it will not work: you can only use routed events
You get a runtime exception from the xaml parser--><Stylex:Key="styleWithEventTriggerNonRoutedEvent"TargetType="TextBlock"><Style.Triggers><EventTriggerRoutedEvent="IsEnabledChanged"><EventTrigger.Actions><BeginStoryboard><Storyboard><ColorAnimationStoryboard.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:

<Stylex:Key="myItemsStyle"><!-- see http://stackoverflow.com/questions/15103129/wpf-textblock-background-not-being-set-using-style
for why i'm using TextBlock.Background--><SetterProperty="TextBlock.Background"Value="#FF00C1FF"/><SetterProperty="Control.Foreground"Value="Red"/><Style.Triggers><TriggerProperty="Control.IsMouseOver"Value="True"><SetterProperty="TextBlock.Background"Value="Red"/><SetterProperty="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. --><ItemsControlGrid.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.

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:

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

<Stylex:Key="myTextBlockStyle2"><Style.Resources><StyleTargetType="TextBlock"><Style.Triggers><TriggerProperty="IsMouseOver"Value="True"><SetterProperty="Background"Value="Red"/></Trigger></Style.Triggers></Style></Style.Resources></Style><Stylex:Key="myListBoxItemsStyle"><SetterProperty="Control.Background"Value="Red"/><Style.Triggers><TriggerProperty="Control.IsMouseOver"Value="True"><SetterProperty="Control.Foreground"Value="Blue"/><!--<Setter Property="Control.BorderThickness" Value="5" />--></Trigger></Style.Triggers></Style><Stylex:Key="myItemContainerStyle"TargetType="ListBoxItem"><SetterProperty="Template"><Setter.Value><ControlTemplate><StackPanel><ContentPresenter/><Labelx:Name="ourTarget"Content="Label inside ControlTemplate"Background="Green"/></StackPanel><ControlTemplate.Triggers><TriggerProperty="IsMouseOver"Value="true"><SetterTargetName="ourTarget"Property="Background"Value="Red"/></Trigger></ControlTemplate.Triggers></ControlTemplate></Setter.Value></Setter></Style><ItemsControlGrid.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 --><ListBoxGrid.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 --><ListBoxGrid.Row="4"ItemContainerStyle="{StaticResource myItemContainerStyle}"><ItemsControl.ItemsSource><Int32Collection>101,201,301,401,501</Int32Collection></ItemsControl.ItemsSource></ListBox>

Above definitions lead to following visuals:

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.