Triggers in .NET MAUI allow changing of UI elements appearance or behaviour depending on conditions or events. They provide you abilities to react to property adjusts as well as particular activities immediately in XAML without composing more code in the code behind. Triggers help to keep the UI dynamic and make it quick to implement specific interactive features.
Types of Triggers in .NET MAUI
- Property Triggers: Fired when a property changes. Trigger an action.
- Data Triggers: Make it so once data binding changes, you can trigger an action.
- Event Triggers: What happens when a certain event happens?
- Multi Triggers: Inform action only when several conditions are met.
Property Trigger
Property Triggers will change the visual properties of a control based on the property of the control itself. Say, you’d like to change the color of a button once it’s pressed.
Button Background Change with Property Trigger
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiAppExample.MainPage">
<StackLayout Padding="20" VerticalOptions="Center" HorizontalOptions="Center">
<Button Text="Press Me"
WidthRequest="200"
HeightRequest="60">
<Button.Triggers>
<Trigger TargetType="Button" Property="IsPressed" Value="True">
<Setter Property="BackgroundColor" Value="LightGreen" />
<Setter Property="TextColor" Value="White" />
</Trigger>
</Button.Triggers>
</Button>
</StackLayout>
</ContentPage>
In this case, only IsPressed property of the button needs to be listened for by the Trigger. When IsPressed is True, the background color of the button turns to LightGreen and the color of the text inside it then becomes white. It provides great immediate visual feedback to the user when the button is pressed.
Global property triggers
Global Property Triggers in .NET MAUI are reusable styles with property triggers that can be used across different controls within the application. Instead of recognizing a property trigger for each control separately, you can define them once in a ResourceDictionary and use them in global manner.
This makes code reusable, reduces duplication and increases the ability to keep a consistent look and feel in the app.
Creating Global Property Triggers in .NET MAUI
To create global property triggers, you will:
- Style is defined in ResourceDictionary.
- You can apply the style to controls in different pages or on the whole application.
I will walk you below through the definition of a global property trigger to change the background color of buttons on press.
Defining a Global Style with Property Triggers
The first will be defining a style in the App.xaml file or in a separate ResourceDictionary to use globally within the entire application.
<?xml version="1.0" encoding="utf-8" ?>
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiAppExample.App">
<Application.Resources>
<ResourceDictionary>
<!-- Global Style for Button with Property Trigger -->
<Style TargetType="Button" x:Key="PressableButtonStyle">
<Setter Property="BackgroundColor" Value="LightBlue" />
<Setter Property="TextColor" Value="Black" />
<Style.Triggers>
<Trigger TargetType="Button" Property="IsPressed" Value="True">
<Setter Property="BackgroundColor" Value="DarkBlue" />
<Setter Property="TextColor" Value="White" />
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</Application.Resources>
</Application>
ResourceDictionary
: All the global styles and resources holds.
Style Definition:
TargetType="Button"
: They tell us that it applies to Button controls.x:Key="PressableButtonStyle"
: Assigns a key to the style so it can be used again anytime.
Property Triggers
:
- It listens for when IsPressed property changes.
- The BackgroundColor changes to DarkBlue and the TextColor to White when IsPressed equals true, .
Using the Global Style in Different Pages
Now that Style property can be applied to any button in your application.
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiAppExample.MainPage">
<StackLayout Padding="20" VerticalOptions="Center" HorizontalOptions="Center">
<!-- Apply the global style to the button -->
<Button Text="Click Me"
Style="{StaticResource PressableButtonStyle}" />
<!-- Another button using the same style -->
<Button Text="Another Button"
Style="{StaticResource PressableButtonStyle}" />
</StackLayout>
</ContentPage>
Style="{StaticResource PressableButtonStyle}"
: Globally defined style applied to the button. This style can be used on any button through the app with consistency.
Defining Global Styles in a Separate Resource Dictionary
You can write your global styles in a dedicated ResourceDictionary file to have it cleaner and more modular.
<?xml version="1.0" encoding="utf-8" ?>
<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
<!-- Global Button Style with Property Trigger -->
<Style TargetType="Button" x:Key="PressableButtonStyle">
<Setter Property="BackgroundColor" Value="LightBlue" />
<Setter Property="TextColor" Value="Black" />
<Style.Triggers>
<Trigger TargetType="Button" Property="IsPressed" Value="True">
<Setter Property="BackgroundColor" Value="DarkBlue" />
<Setter Property="TextColor" Value="White" />
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
<?xml version="1.0" encoding="utf-8" ?>
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiAppExample.App">
<Application.Resources>
<ResourceDictionary>
<!-- Merging ButtonStyles into Application Resources -->
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ButtonStyles.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
MergedDictionaries
: It will help you see styles separately into different files and therefore your project looks more modular.
ResourceDictionary Source="ButtonStyles.xaml"
: In it we import the ButtonStyles file so that any styles defined in it can be used by us in any part of the application.
Global Property Triggers Key Points
- Reusability: By defining property triggers in a global style you can apply the same behavior to multiple controls in your application without requiring the same trigger logic to be duplicated.
- Centralized Management: This makes the maintainability of your app improve because when things do break or you need to make changes to the style or behavior, you can do it in one place (the ResourceDictionary), and your changes will be reflected throughout the app.
- Avoid Duplication: This has the effect that global styles, and global triggers reduce code duplication in XAML files, which makes them easier to read and manage.
Data Trigger
Data Triggers change the properties of a control in response to changes of data. When you want to change the UI depending on the value of data that is bound to a control, they’re useful.
Change Label Color Based on a ViewModel Property
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace MauiAppExample
{
public class MainViewModel : INotifyPropertyChanged
{
private bool _isImportant;
public bool IsImportant
{
get => _isImportant;
set
{
_isImportant = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MauiAppExample"
x:Class="MauiAppExample.MainPage">
<ContentPage.BindingContext>
<local:MainViewModel />
</ContentPage.BindingContext>
<StackLayout Padding="20" VerticalOptions="Center" HorizontalOptions="Center">
<Label Text="This is an important message!"
FontSize="Large">
<Label.Triggers>
<DataTrigger TargetType="Label" Binding="{Binding IsImportant}" Value="True">
<Setter Property="TextColor" Value="Red" />
</DataTrigger>
</Label.Triggers>
</Label>
<Button Text="Toggle Importance"
Command="{Binding ToggleImportantCommand}" />
</StackLayout>
</ContentPage>
The DataTrigger is bound to the ViewModel’s property that is IsImportant.
If IsImportant is True, we change it to Red.
Event Triggers
Event Triggers are triggered actions you can perform when certain events are fired. Tap, for instance, would trigger an animation or some method.
Scaling an Image on Tap Using Event Trigger
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiAppExample.MainPage">
<StackLayout Padding="20" VerticalOptions="Center" HorizontalOptions="Center">
<Image Source="dotnet_bot.png"
WidthRequest="100"
HeightRequest="100">
<Image.Triggers>
<EventTrigger Event="Tapped">
<EventTrigger.Actions>
<ScaleToAction Scale="1.5" Length="250" />
</EventTrigger.Actions>
</EventTrigger>
</Image.Triggers>
</Image>
</StackLayout>
</ContentPage>
- Tapped event is listened by the Image and dispatched using EventTrigger.
- In 250 milliseconds, ScaleToAction scales the image to 1.5 times its size.
Note: In order for this to work you need to make sure the Image has GestureRecognizers for Tapping. Below is how to add it:
<Image Source="dotnet_bot.png"
WidthRequest="100"
HeightRequest="100">
<Image.GestureRecognizers>
<TapGestureRecognizer />
</Image.GestureRecognizers>
<Image.Triggers>
<EventTrigger Event="Tapped">
<EventTrigger.Actions>
<ScaleToAction Scale="1.5" Length="250" />
</EventTrigger.Actions>
</EventTrigger>
</Image.Triggers>
</Image>
Multi Trigger
Using Multi Triggers, you can change properties based on multiple conditions. It’s particularly useful for combining several states to determine the appearance or behavior of a control.
Change Background Color of an Entry Based on Focus and Text Length
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiAppExample.MainPage">
<StackLayout Padding="20" VerticalOptions="Center" HorizontalOptions="Center">
<Entry Placeholder="Enter some text">
<Entry.Triggers>
<MultiTrigger TargetType="Entry">
<MultiTrigger.Conditions>
<Condition Property="IsFocused" Value="True" />
<Condition Property="Text.Length" Value="0" />
</MultiTrigger.Conditions>
<Setter Property="BackgroundColor" Value="LightPink" />
</MultiTrigger>
</Entry.Triggers>
</Entry>
</StackLayout>
</ContentPage>
An Entry control gets MultiTrigger applied to it.
The conditions are that the Entry has IsFocused = True and that the Text.Length = 0.
If these conditions are met, the Entry Background changes to LightPink.
State Triggers Based on Custom Properties
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiAppExample.MainPage">
<StackLayout Padding="20" VerticalOptions="Center" HorizontalOptions="Center">
<!-- Slider control to demonstrate state trigger -->
<Slider x:Name="ValueSlider" Minimum="0" Maximum="100" />
<!-- Label that changes color based on the slider value -->
<Label Text="Adjust the slider to change my color!"
FontSize="Large"
HorizontalOptions="Center">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="Normal">
<VisualState.Setters>
<Setter Property="TextColor" Value="Black" />
</VisualState.Setters>
</VisualState>
<!-- Change text color to red when slider value is greater than 50 -->
<VisualState x:Name="HighValue">
<VisualState.StateTriggers>
<StateTrigger IsActive="{Binding Source={x:Reference ValueSlider}, Path=Value, Converter={StaticResource GreaterThanConverter}, ConverterParameter=50}" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="TextColor" Value="Red" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Label>
</StackLayout>
</ContentPage>
Adaptive UI Design with State Triggers
State triggers are commonly used to create an adaptive UI that adjusts based on different conditions:
- Screen Size: AdaptiveTrigger can change layouts and controls properties based on the device width or height.
- Control States: When the control on the screen, for example a button or a checkbox, enters a certain state, we change the visual properties.
- Device Orientation: Layout switching between layouts or changing UI properties dependent of the portrait or landscape orientation.
Comparison Between State Triggers and Property Triggers
Feature | Property Trigger | State Trigger |
---|---|---|
Scope | Works with changes to a specific property value of the control itself. | Applies visual states based on conditions or states of the control or environment. |
Target | Typically used for changing a few properties like color, visibility. | More comprehensive, managing complex visual states involving multiple properties. |
Application | Simple visual changes in direct response to a property. | Adaptive UIs, responsive layouts, or state-based control appearance. |
EnterActions and ExitActions Triggers
EnterActions and ExitActions are part of VisualStateManager functionality that allows you to perform some actions when the element enters or exits a visual state, in .NET MAUI. A powerful way to trigger an animation, or other behavior, when the control’s state changes is provided by this feature.
EnterActions & ExitActions let you define custom animations, or other UI changes depending on when a control changes states (e.g. when it’s pressed, focused…)
EnterActions and ExitActions Key Concepts
- EnterActions: In action when a control takes on a particular visual state.
- ExitActions: Executes when exiting a specific visual state of a control.
These are pretty much always defined in XAML that make your UI more interactive by providing transitions or visual cues when controls change state.
Understanding EnterActions and ExitActions
- EnterActions: A set of actions to run based on a set of conditions (the trigger condition).
- ExitActions: A set of activities to perform when the trigger condition isn’t true anymore.
They are often used with:
- DataTriggers: Triggers that react to changes of bound data.
- EventTriggers: Event triggers that respond to events.
Key Points
- Separation of Concerns: It allows you to keep your UI logic in XAML, and thus reduces your code behind complexity.
- Reusability: You can define your actions in resources and use them multiple times within different controls.
- Animation and Interaction: It’s perfect to animate properties or to invoke commands when a state changes.
Let’s create an example where we:
- When a bound property IsActive changes, change the opacity of an image.
- We use EnterActions and ExitActions to animate the opacity.
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
namespace MauiAppExample
{
public class MainViewModel : INotifyPropertyChanged
{
private bool _isActive;
public bool IsActive
{
get => _isActive;
set
{
if(_isActive != value)
{
_isActive = value;
OnPropertyChanged();
}
}
}
public ICommand ToggleActiveCommand { get; }
public MainViewModel()
{
ToggleActiveCommand = new Command(() => IsActive = !IsActive);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
IsActive
: Created a boolean property which will fire the actions when it changes.
ToggleActiveCommand
: A toggle command for IsActive property.
<?xml version="1.0" encoding="UTF-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
x:Class="MauiAppExample.MainPage"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MauiAppExample">
<ContentPage.BindingContext>
<local:MainViewModel />
</ContentPage.BindingContext>
<StackLayout Padding="20" Spacing="20" VerticalOptions="Center" HorizontalOptions="Center">
<!-- Button to Toggle IsActive -->
<Button Text="Toggle IsActive"
Command="{Binding ToggleActiveCommand}" />
<!-- Image that responds to IsActive changes -->
<Image Source="dotnet_bot.png"
WidthRequest="200"
HeightRequest="200">
<Image.Triggers>
<DataTrigger TargetType="Image"
Binding="{Binding IsActive}"
Value="True">
<DataTrigger.EnterActions>
<BeginAnimation TargetProperty="Opacity"
To="1.0"
Duration="0:0:1" />
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginAnimation TargetProperty="Opacity"
To="0.3"
Duration="0:0:1" />
</DataTrigger.ExitActions>
</DataTrigger>
</Image.Triggers>
</Image>
</StackLayout>
</ContentPage>
Binding Context: We set this to MainViewModel so we can bind to IsActive.
Button: Toggles the IsActive property.
Image:
- DataTrigger: Tracks the IsActive property.
- EnterActions:
- The image’s opacity animates to 1.0 in one second when IsActive = true.
- ExitActions:
- If IsActive becomes False, the image’s opacity animates (puff) to 0.3 during one second.
When using EnterActions and ExitActions with EventTriggers
You can also use EnterActions
and ExitActions
with EventTriggers
. However, since EventTriggers
only have an event occurrence (they don’t have a condition becoming false), ExitActions
are not typically used with them.
Changing Properties Based on a Condition
We have a Label that is supposed to change color when a Slider value is exceeded beyond a particular threshold.
public class MainViewModel : INotifyPropertyChanged
{
// ... existing code ...
private double _sliderValue;
public double SliderValue
{
get => _sliderValue;
set
{
if (_sliderValue != value)
{
_sliderValue = value;
OnPropertyChanged();
}
}
}
// ... existing code ...
}
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
x:Class="MauiAppExample.MainPage"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MauiAppExample">
<ContentPage.BindingContext>
<local:MainViewModel />
</ContentPage.BindingContext>
<StackLayout Padding="20" Spacing="20" VerticalOptions="Center" HorizontalOptions="Center">
<!-- Slider to adjust value -->
<Slider Minimum="0" Maximum="100"
Value="{Binding SliderValue}" />
<!-- Label that changes color based on SliderValue -->
<Label Text="Adjust the slider!"
FontSize="Large">
<Label.Triggers>
<DataTrigger TargetType="Label"
Binding="{Binding SliderValue}"
Value="50"
ComparisonType="GreaterThanOrEqual">
<DataTrigger.EnterActions>
<BeginAnimation TargetProperty="TextColor"
To="Red"
Duration="0:0:0.5" />
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginAnimation TargetProperty="TextColor"
To="Black"
Duration="0:0:0.5" />
</DataTrigger.ExitActions>
</DataTrigger>
</Label.Triggers>
</Label>
</StackLayout>
</ContentPage>
Slider: It will bind its Value to SliderValue in ViewModel.
Label:
- DataTrigger:
- Binding: Monitors
SliderValue
. - Value: Fires when SliderValue is equal to or greater than 50.
- ComparisonType: You’ll want to specify GreaterThanOrEqual (you may need a converter or custom trigger for this functionality).
- Binding: Monitors
- EnterActions: When condition is met change TextColor to Red.
- ExitActions: Sets TextColor to Black if the condition is no more valid.
Note: Currently, the .NET MAUI built in DataTrigger does not support ComparisonType. Sometimes you’ll need to use a custom trigger, or a converter, to do comparisons apart from equality.
Create a Value Converter
ValueComparisonConverter.cs
using System;
using System.Globalization;
using Microsoft.Maui.Controls;
namespace MauiAppExample
{
public class ValueComparisonConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null || parameter == null)
return false;
if (double.TryParse(value.ToString(), out double numericValue) &&
double.TryParse(parameter.ToString(), out double threshold))
{
return numericValue >= threshold;
}
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Update the XAML to Use the Converter
First, declare the converter in your resources.
<ContentPage.Resources>
<ResourceDictionary>
<local:ValueComparisonConverter x:Key="ValueComparisonConverter" />
</ResourceDictionary>
</ContentPage.Resources>
Then update the DataTrigger
:
<DataTrigger TargetType="Label"
Binding="{Binding SliderValue, Converter={StaticResource ValueComparisonConverter}, ConverterParameter=50}"
Value="True">
<DataTrigger.EnterActions>
<BeginAnimation TargetProperty="TextColor"
To="Red"
Duration="0:0:0.5" />
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginAnimation TargetProperty="TextColor"
To="Black"
Duration="0:0:0.5" />
</DataTrigger.ExitActions>
</DataTrigger>
In the sliderWhenChanged method check if SliderValue is greater than 50 using Converter.
DataTrigger will be invoked on the basis on whether the_converter returns True.
Key Takeaways
- EnterActions and ExitActions let you perform actions when a triggers condition is true or false.
- Any TriggerAction is an action, which can be an animation, a method invocation, etc.
- Surrounding those data changes with EnterActions and ExitActions along with DataTriggers is a powerful way to react to data changes without code-behind.
Available Actions
You can use built-in actions or create custom ones:
- BeginAnimation: Animates a property.
- Custom TriggerActions: There are TriggerAction<T> subclasses that allows you to create custom actions.
Creating a Custom TriggerAction
Custom TriggerAction to Display an Alert
DisplayAlertAction.cs
using System.Threading.Tasks;
using Microsoft.Maui.Controls;
namespace MauiAppExample
{
public class DisplayAlertAction : TriggerAction<VisualElement>
{
public string Title { get; set; }
public string Message { get; set; }
public string Cancel { get; set; }
protected override void Invoke(VisualElement sender)
{
Application.Current.MainPage.DisplayAlert(Title, Message, Cancel);
}
}
}
<DataTrigger TargetType="Label"
Binding="{Binding IsActive}"
Value="True">
<DataTrigger.EnterActions>
<local:DisplayAlertAction Title="Activated"
Message="IsActive is now True."
Cancel="OK" />
</DataTrigger.EnterActions>
</DataTrigger>
Custom Action: It displays an alert when IsActive becomes True.