MultiTriggers And MultiDataTriggers In WPF

Introduction

 
Remember last time when we solved the puzzle of triggers in WPF? Well, guess what?

That puzzle has a boss level too. Yes, we learned how to use triggers effectively in WPF, but there are real bosses they call it MultiTriggers and MultiDataTriggers.

We learned why we used Triggers. To give you an idea let me brief you a bit.
  • Triggers are used for conditional execution, it can be used with control's properties or events or data.  They are an if-else conditional statement of Xaml.
  • To learn more in detail, I highly recommend you to visit my article on triggers.
The real question is why do we need MultiTriggers?
 
If you look closely, you’ll find that triggers only satisfies one if condition. But in a programming language, we could have multiple conditions in an (If block)
  • For e.g., a boy's college would only have 2 conditions. Age should be 18 or more And student has to be male.
    • if ( Age > 18 && isMale)
  • A girl's college, on the other hand, would have conditions like Age should be 18 or more And student has to be female.
    • if ( Age > 18 && isFemale)
If both of the conditions are true then the statements below will execute. To bring this characteristic into XAML, WPF has introduced MultiTriggers.
 
 
First, let's concentrate on the MultiTrigger.
 
Syntax
  1. <MultiTrigger>      
  2.       <MultiTrigger.Conditions>      
  3.           <Condition Property="IsMouseOver" Value="True"/>      
  4.           <Condition Property="IsKeyboardFocused" Value="True"/>      
  5.       </MultiTrigger.Conditions>      
  6.       <MultiTrigger.Setters>      
  7.           <Setter Property="Content" Value="Trigger Applied"/>      
  8.            </MultiTrigger.Setters>      
  9.   </MultiTrigger>    
We are going to apply the trigger on button's properties "IsMouseOver" & "IsKeyboardFocused". So when these 2 properties are true then only we will change the text of a button.
 
So we not only need to hover a mouse on a button, but also need to have the keyboard's focus.
  • Add DataContext through XAML only.
  • Changing the text of a button after the trigger has been applied.
  • Adding one more button: "name: Cancel". Just to change the keyboard's focus.
  • Adding delegate commands for both of the buttons.
  • One TextBlock to show where the focus is.
XAML would be designed as in the following with the given attributes.
  1. <Window x:Class="LearnWPF.MainWindow"    
  2.         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"    
  3.         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"    
  4.         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"    
  5.         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"    
  6.         xmlns:local="clr-namespace:LearnWPF.ViewModel"    
  7.          mc:Ignorable="d"    
  8.         Title="MainWindow" Height="200" Width="400">    
  9.     <Window.Resources>    
  10.         <local:MainWindowViewModel x:Key="VM" />    
  11.         <Style x:Key="SubmitButtonStyle"        
  12.            BasedOn="{StaticResource {x:Type Button}}"        
  13.            TargetType="Button">    
  14.             <Setter Property="Height" Value="25"/>    
  15.             <Setter Property="Width" Value="150"/>    
  16.             <Setter Property="BorderThickness" Value="2"/>    
  17.             <Setter Property="BorderThickness" Value="2"/>    
  18.             <Setter Property="HorizontalAlignment" Value="Center"/>    
  19.             <Setter Property="VerticalAlignment" Value="Center"/>      
  20.             <Setter Property="Content" Value="Trigger Not Applied"/>    
  21.             <Setter Property="BorderBrush" Value="Black"/>    
  22.             <Setter Property="Background" Value="Gray"/>    
  23.             <Setter Property="Foreground" Value="Wheat"/>    
  24.             <Style.Triggers>    
  25.                 <MultiTrigger>    
  26.                     <MultiTrigger.Conditions>    
  27.                         <Condition Property="IsMouseOver" Value="True"/>    
  28.                         <Condition Property="IsKeyboardFocused" Value="True"/>    
  29.                     </MultiTrigger.Conditions>    
  30.                     <MultiTrigger.Setters>    
  31.                         <Setter Property="Content" Value="Trigger Applied"/>    
  32.                         <Setter Property="BorderBrush" Value="White"/>    
  33.                         <Setter Property="Background" Value="SkyBlue"/>    
  34.                         <Setter Property="Foreground" Value="Black"/>    
  35.                     </MultiTrigger.Setters>    
  36.                 </MultiTrigger>    
  37.             </Style.Triggers>    
  38.         </Style>    
  39.     
  40.     </Window.Resources>    
  41.     <Grid  DataContext="{Binding Source={StaticResource VM}}">    
  42.         <Grid.RowDefinitions>    
  43.             <RowDefinition/>    
  44.             <RowDefinition/>    
  45.         </Grid.RowDefinitions>    
  46.     
  47.         <StackPanel x:Name="MainGrid"    
  48.                 Orientation="Horizontal"    
  49.                 HorizontalAlignment="Center">    
  50.             <Button x:Name="ButtonStyleMe"       
  51.                  Style="{StaticResource SubmitButtonStyle}"    
  52.                     Command="{Binding StyledButtonCliked}"/>    
  53.             <Button x:Name="ButtonCancel"       
  54.                 Content="Cancel"    
  55.                 Height="25"    
  56.                 Margin="10 0 0 0"    
  57.                 Width="150"    
  58.                 Command="{Binding CancelButtonCliked}"/>    
  59.         </StackPanel>    
  60.         <TextBlock Text="{Binding FocusText}"    
  61.                    HorizontalAlignment="Center"    
  62.                    Grid.Row="1"/>    
  63.     </Grid>    
  64. </Window>    
Let's have ViewModel as well.
  1. using System;  
  2. using Prism.Commands;  
  3. using Prism.Mvvm;  
  4.   
  5. namespace LearnWPF.ViewModel  
  6. {  
  7.     public class MainWindowViewModel : BindableBase  
  8.     {  
  9.         private string _focusText;  
  10.   
  11.         public string FocusText  
  12.         {  
  13.             get { return _focusText; }  
  14.             set { SetProperty(ref _focusText , value); }  
  15.         }  
  16.   
  17.         public DelegateCommand StyledButtonCliked { getset; }  
  18.         public DelegateCommand CancelButtonCliked { getset; }  
  19.         public MainWindowViewModel()  
  20.         {  
  21.             StyledButtonCliked = new DelegateCommand(ShowStyleText);  
  22.             CancelButtonCliked = new DelegateCommand(ShowCancelText);  
  23.         }  
  24.   
  25.         private void ShowCancelText()  
  26.         {  
  27.             FocusText = "Cancel Button is Focused " + Environment.NewLine + "IsKeyboardFocused in on Cancel Button " +Environment.NewLine + "MultiTigger will not be applicable on IsMouseOver!";  
  28.         }  
  29.   
  30.         private void ShowStyleText()  
  31.         {  
  32.             FocusText = "Trigger Button is Focused " + Environment.NewLine + "IsKeyboardFocused in on Trigger Button " + Environment.NewLine + "MultiTigger will be applicable on IsMouseOver!";  
  33.         }  
  34.     }  
  35. }   
Now run the WPF application.
 
 
And it is working like a charm.
 
Before we jump into MultiDataTrigger. Let's first ask ourself why do we need MultiDataTrigger?
  • There is one backdrop in MultiTrigger, you can only apply MultiTrigger on multiple properties of the same control. OK, cool. And why this could be a barrier. Say you need to have conditions on two different controls rather than one single control. Oh, now that's a hurdle.
  • You can use MultiDataTrigger in this case. It works with the bound value from ViewModel or code-behind rather than dependent on control's properties. Which makes them loosely coupled to control.
  • Conditions are bounded with data from View-Model.
Syntax
  1. <MultiDataTrigger>    
  2.      <MultiDataTrigger.Conditions>    
  3.          <Condition Binding="{Binding IsAge18}" Value="True"/>    
  4.          <Condition Binding="{Binding IsMale}" Value="True"/>    
  5.      </MultiDataTrigger.Conditions>    
  6.      <MultiDataTrigger.Setters>    
  7.          <Setter Property="Text" Value="Trigger applied!"/>    
  8.       </MultiDataTrigger.Setters>    
  9.  </MultiDataTrigger>    
Take the following example: Say there are 2 colleges, a Boy's College & Girl's College.
  • Boy's college has a condition that Gender should be Male & student should be at least 18.
  • Girl's college has a condition that Gender should be Female & students should be at least 18.
Let's design XAML for these requirements:
  • One radio button group: Gender
    • RadioButton Male
    • RadioButton Female
  • CheckBox to check if the student's age is greater than 18
  • Textblock at the bottom to show the final result.
  1. <Window x:Class="LearnWPF.MainWindow"  
  2.         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
  3.         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
  4.         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  
  5.         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
  6.         xmlns:local="clr-namespace:LearnWPF.ViewModel"  
  7.          mc:Ignorable="d"  
  8.         Title="MainWindow" Height="200" Width="400">  
  9.     <Window.Resources>  
  10.         <local:MainWindowViewModel x:Key="VM" />  
  11.         <Style x:Key="SubmitButtonStyle"      
  12.            BasedOn="{StaticResource {x:Type Button}}"      
  13.            TargetType="Button">  
  14.             <Setter Property="Height" Value="25"/>  
  15.             <Setter Property="Width" Value="150"/>  
  16.             <Setter Property="BorderThickness" Value="2"/>  
  17.             <Setter Property="BorderThickness" Value="2"/>  
  18.             <Setter Property="HorizontalAlignment" Value="Center"/>  
  19.             <Setter Property="VerticalAlignment" Value="Center"/>  
  20.   
  21.             <Setter Property="Content" Value="Trigger Not Applied"/>  
  22.             <Setter Property="BorderBrush" Value="Black"/>  
  23.             <Setter Property="Background" Value="Gray"/>  
  24.             <Setter Property="Foreground" Value="Wheat"/>  
  25.             <Style.Triggers>  
  26.                 <MultiDataTrigger>  
  27.                     <MultiDataTrigger.Conditions>  
  28.                         <Condition Binding="{Binding IsAge18}" Value="True"/>  
  29.                         <Condition Binding="{Binding IsMale}" Value="True"/>  
  30.                     </MultiDataTrigger.Conditions>  
  31.                     <MultiDataTrigger.Setters>  
  32.                         <Setter Property="BorderBrush" Value="White"/>  
  33.                         <Setter Property="Background" Value="SkyBlue"/>  
  34.                         <Setter Property="Foreground" Value="Black"/>  
  35.                     </MultiDataTrigger.Setters>  
  36.                 </MultiDataTrigger>  
  37.             </Style.Triggers>  
  38.         </Style>  
  39.   
  40.     </Window.Resources>  
  41.     <Grid  DataContext="{Binding Source={StaticResource VM}}">  
  42.         <Grid.RowDefinitions>  
  43.             <RowDefinition Height="Auto"/>  
  44.             <RowDefinition Height="Auto"/>  
  45.             <RowDefinition/>  
  46.         </Grid.RowDefinitions>  
  47.   
  48.         <TextBlock x:Name="TextBlockGender"  
  49.                    Text="Gender:"  
  50.                    Margin="-160 20 0 0"  
  51.                    HorizontalAlignment="Center"  
  52.                    VerticalAlignment="Center"/>  
  53.         <RadioButton x:Name="RadioButtonGenderMale"  
  54.                      IsChecked="{Binding IsMale}"  
  55.                      Content="Male"  
  56.                      Margin="-45 20 0 0"  
  57.                      GroupName="Gender"  
  58.                      HorizontalAlignment="Center"  
  59.                      VerticalAlignment="Center"/>  
  60.         <RadioButton x:Name="RadioButtonGenderFeMale"  
  61.                      IsChecked="{Binding IsFemale}"  
  62.                      Content="Female"  
  63.                      Margin="70 20 0 0"  
  64.                      GroupName="Gender"  
  65.                      HorizontalAlignment="Center"  
  66.                      VerticalAlignment="Center"/>  
  67.         <CheckBox x:Name="CheckBoxIsAgeGt18"  
  68.                   IsChecked="{Binding IsAge18}"  
  69.                   Margin="0 20 0 0"  
  70.                   Content="Age > 18"  
  71.                   HorizontalAlignment="Center"  
  72.                   VerticalAlignment="Top"  
  73.                   Grid.Row="1"/>  
  74.           
  75.         <TextBlock HorizontalAlignment="Center"  
  76.                    Margin="0 20 0 0"  
  77.                    VerticalAlignment="Top"  
  78.                    Grid.Row="2">  
  79.             <TextBlock.Style>  
  80.                 <Style TargetType="TextBlock">  
  81.                     <Setter Property="Text" Value="Trigger is not applied!"/>  
  82.                     <Style.Triggers>  
  83.                         <MultiDataTrigger>  
  84.                             <MultiDataTrigger.Conditions>  
  85.                                 <Condition Binding="{Binding IsAge18}" Value="True"/>  
  86.                                 <Condition Binding="{Binding IsMale}" Value="True"/>  
  87.                             </MultiDataTrigger.Conditions>  
  88.                             <MultiDataTrigger.Setters>  
  89.                                 <Setter Property="Text" Value="Trigger applied! Welcome to Boy's Jr. College"/>  
  90.                              </MultiDataTrigger.Setters>  
  91.                         </MultiDataTrigger>  
  92.                         <MultiDataTrigger>  
  93.                             <MultiDataTrigger.Conditions>  
  94.                                 <Condition Binding="{Binding IsAge18}" Value="True"/>  
  95.                                 <Condition Binding="{Binding IsFemale}" Value="True"/>  
  96.                             </MultiDataTrigger.Conditions>  
  97.                             <MultiDataTrigger.Setters>  
  98.                                 <Setter Property="Text" Value="Trigger applied! Welcome to Girl's Jr. College"/>  
  99.                             </MultiDataTrigger.Setters>  
  100.                         </MultiDataTrigger>  
  101.                     </Style.Triggers>  
  102.                 </Style>  
  103.             </TextBlock.Style>  
  104.         </TextBlock>  
  105.     </Grid>  
  106. </Window> 
 ViewModel for the same:
  1. using System;  
  2. using Prism.Commands;  
  3. using Prism.Mvvm;  
  4.   
  5. namespace LearnWPF.ViewModel  
  6. {  
  7.     public class MainWindowViewModel : BindableBase  
  8.     {  
  9.         private bool _isAge18;  
  10.   
  11.         public bool IsAge18  
  12.         {  
  13.             get { return _isAge18; }  
  14.             set { SetProperty(ref _isAge18, value); }  
  15.         }  
  16.         private bool _isMale = false;  
  17.   
  18.         public bool IsMale  
  19.         {  
  20.             get { return _isMale; }  
  21.             set  
  22.             {  
  23.                 SetProperty(ref _isMale, value);                  
  24.             }  
  25.         }  
  26.         private bool _isFeMale = false;  
  27.   
  28.         public bool IsFemale  
  29.         {  
  30.             get { return _isFeMale; }  
  31.             set  
  32.             {  
  33.                 SetProperty(ref _isFeMale, value);                  
  34.             }  
  35.         }  
  36.     }  
  37. }   
This should work. Let's find out!
 
As per the gif, we are getting an output as per our design.
 
We have learned the advantages of both of these, And it would be unfair if I wouldn't tell you it's shortcomings.
 
If you see in both of these triggers, conditions always have AND operator. Meaning,
 
Student's age > 18 && IsMale                             
  1. <MultiDataTrigger.Conditions>    
  2.           <Condition Binding="{Binding IsAge18}" Value="True"/>    
  3.           <Condition Binding="{Binding IsMale}" Value="True"/>    
  4.  </MultiDataTrigger.Conditions>   
What if I want OR operator instead of AND. well, in that case. you will have to use multiple MultiDataTrigger or MultiTrigger tags.
 
For the above example:
 
If a student is male: set text to "Male student" OR if the student's age is greater than 18 then set text to "Age is greater than 18"
  1. <MultiDataTrigger>    
  2.     <MultiDataTrigger.Conditions>    
  3.         <Condition Binding="{Binding IsAge18}" Value="True"/>    
  4.     </MultiDataTrigger.Conditions>    
  5.     <MultiDataTrigger.Setters>    
  6.         <Setter Property="Text" Value="Age is greater than 18"/>    
  7.      </MultiDataTrigger.Setters>    
  8. </MultiDataTrigger>    
  9.   
  10. <MultiDataTrigger>    
  11.     <MultiDataTrigger.Conditions>    
  12.          <Condition Binding="{Binding IsMale}" Value="True"/>    
  13.     </MultiDataTrigger.Conditions>    
  14.     <MultiDataTrigger.Setters>    
  15.         <Setter Property="Text" Value="Male student"/>    
  16.     </MultiDataTrigger.Setters>    
  17. </MultiDataTrigger>    
Same way for MultiTrigger, just replace MultiDataTrigger's tag with MultiTrigger.
 

Conclusion

 
In this article, we learned:
  • What are MultiTriggers & MultiDataTriggers are
  • How to use them
  • How different are they with respect to normal triggers
  • Difference between MultiTrigger & MultiDataTrigger
  • Shortcomings & workaround for those shortcomings.
I hope this article has an answer to your question. Thank you for being here. I wish you all the very best.
 
Find the attached source code for your reference.

If you have any further queries, you can connect with me @