Recently I was doing some WPF work and was trying to customize the display of a UserControl based on a dependency property (DP) of that same UserControl . This post will outline what I learnt about DataTriggers and MultiDataTriggers while completing the task.
The first goal was to adjust the vertical alignment of two elements. I have created a simple UserControl containing two elements to demonstrate this, namely an image and a caption. When creating an instance of this UserControl I want to be able to specify the position of the caption relative to the image (i.e. on top of or below). To achieve this I added the CaptionVerticalAlignment DP to the code behind. The intended use of this property is to describe the vertical alignment of the text (i.e. Top or Bottom). Specifying anything else results in an ArgumentException :
1 |
<w:ImageAndCaptionControl x:Name="ImageAndCaption" CaptionVerticalAlignment="Top" HorizontalAlignment="Center" VerticalAlignment="Center" Width="65" Height="100" /> |
To modify the style of the text box I query the value of the same DP in the xaml through a RelativeSource data binding:
1 2 3 |
<Style x:Key="TextBoxStyle" TargetType="TextBox"> <Setter Property="VerticalAlignment" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=CaptionVerticalAlignment, FallbackValue=Bottom}"/> </Style> |
Specifing RelativeSource as the binding type means that I want to bind to an element that is relative to the target. The target in this case is a TextBox . By specifying AncestorType , the RelativeSource mode defaults to FindAncestor and the ancestor type that it searches for is a UserControl . This binding source is useful in situations where you have nested elements. Carry on reading for an alternative to FindAncestor … (oh the suspense!)
DataTrigger
I could create another DP that specifies the alignment of the image, or I could re-use the caption alignment DP to link the image’s alignment to the that of the caption. A DataTrigger supports this operation. This time I specify ElementName as the binding type. I prefer this type of binding to the RelativeSource binding as it is more readable in my opinion as well as more explicit about the element to bind to. From what I have read online there isn’t much between the two types of binding in terms of performance:
1 2 3 4 5 6 7 8 9 10 |
<Style x:Key="ImageStyle" TargetType="Image"> <!-- Default to Top --> <Setter Property="VerticalAlignment" Value="Top"/> <Style.Triggers> <!-- Change alignment if the caption is meant to be on top --> <DataTrigger Binding="{Binding ElementName=ImageAndCaption, Path=CaptionVerticalAlignment}" Value="Top"> <Setter Property="VerticalAlignment" Value="Bottom"/> </DataTrigger> </Style.Triggers> </Style> |
Depending on what vertical alignment I choose, I get one of the following results:
MultiDataTrigger
DataTriggers are very useful if you want to query a single condition, however if you want to query multiple conditions then you need to use a MultiDataTrigger . The second goal of my task involved modifying an image style based on two conditions. In my contrived example I have two navigation arrows for scrolling left and right. I want to adjust the style of the cursor depending on the arrow that it has highlighted. The two conditions that I need to query are whether the mouse is highlighting the arrow, and which direction the arrow is facing. In this case I use both bindings types from above to do the same binding (sure why not eh?). This time however I specify self as the RelativeSource rather than using AncestorType . Incidentally I could have used the same RelativeSource for the binding in the TextBox style above as well which would have been more concise:
1 2 3 4 5 6 7 8 9 |
<Style x:Key="ArrowButtonStyle" TargetType="{x:Type Button}"> <Style.Triggers> <MultiDataTrigger> <MultiDataTrigger.Conditions> <Condition Binding="{Binding RelativeSource={RelativeSource self}, Path=IsMouseOver}" Value="True"/> <Condition Binding="{Binding ElementName=ArrowButtonControl, Path=Direction}" Value="Left"/> </MultiDataTrigger.Conditions> <Setter Property="Cursor" Value="ScrollW"/> </MultiDataTrigger> |
In the example I hovered the cursor over the left arrow which resulted in the cursor changing to the scroll west cursor because of the direction of the arrow. The arrow image itself also changes once the mouse cursor hovers over it:
MultiDataTriggers support short cutting condition checks. Therefore if the first condition fails, subsequent conditions won’t be checked since the binary AND operation is used when checking the results of each condition. One drawback of using MultiDataTriggers is that they can’t be debugged. If debugging is required you could use a Converter along with a MultiBinding – take a look on MSDN for an example.
Summary
This blog post demonstrates the usefulness of both the DataTrigger and MultiDataTrigger for customising a UI based on user interaction. I have also provided examples of various data binding methods to add to your arsenal.
Source Code
The source code can be found on Bitbucket.