- WPF property triggers, DataTriggers, MultiTriggers, and EventTriggers all derive from
TriggerBaseinSystem.Windows. None of them exist in WinUI 3 or Uno Platform. - WinUI 3 retains
EventTriggeronly for backward compatibility, and only for theLoadedevent - Microsoft's official migration guidance: replace DataTrigger/MultiTrigger with XAML Behaviors, replace property triggers with VisualStateManager
StateTriggeractivates aVisualStatewhen itsIsActiveproperty is true, giving you a markup-driven replacement for boolean DataTrigger scenarios- The
VisualStateGroupmust be declared under the first child of the root of a control template for state triggers to work
If you are moving a WPF project to Uno Platform or WinUI 3, the word "trigger" will betray you. WinUI keeps the name but drops most of the behavior. This guide maps each of the four WPF trigger flavors to its sanctioned replacement using VisualStateManager, StateTrigger, and XAML Behaviors.
WinUI Triggers Are Not WPF Triggers
WPF's TriggerBase class is the base type for Trigger, MultiTrigger, EventTrigger, DataTrigger, and MultiDataTrigger, all in System.Windows inside PresentationFramework.dll. WinUI 3 and Uno Platform do not ship any of those classes.
The Microsoft.UI.Xaml.EventTrigger class is retained, but it exists mainly for Silverlight compatibility. The only event that invokes it is FrameworkElement.Loaded.
| WPF Trigger | Lives In | WinUI 3 / Uno Replacement |
|---|---|---|
| Trigger (property trigger) | Style.Triggers, ControlTemplate.Triggers | VisualState inside a VisualStateGroup |
| DataTrigger | Style.Triggers, DataTemplate.Triggers | StateTrigger with IsActive bound to a VM boolean, or DataTriggerBehavior |
| MultiTrigger | Style.Triggers, ControlTemplate.Triggers | Single StateTrigger bound to a composed VM boolean |
| EventTrigger | FrameworkElement.Triggers | EventTriggerBehavior (Community Toolkit) or code-behind Storyboard.Begin() |
Property Trigger Becomes VisualState
<Style TargetType="Button">
<Setter Property="Opacity" Value="0.5" />
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Opacity" Value="1.0" />
</Trigger>
</Style.Triggers>
</Style><ControlTemplate TargetType="Button">
<Grid x:Name="RootGrid" Opacity="0.5">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="PointerOver">
<VisualState.Setters>
<Setter Target="RootGrid.Opacity" Value="1.0" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ContentPresenter />
</Grid>
</ControlTemplate>Two things to notice. The state is named PointerOver, not IsMouseOver, because the WinUI common states contract uses pointer vocabulary. The VisualStateGroup is attached to RootGrid (the first child of the template), which is a documented requirement for state triggers to work.
DataTrigger Becomes a StateTrigger or a Behavior
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding IsBusy}" Value="True">
<Setter Property="Foreground" Value="Gray" />
</DataTrigger>
</Style.Triggers>
</Style>Option 1: StateTrigger
<Grid x:Name="RootGrid">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="BusyStates">
<VisualState x:Name="Busy">
<VisualState.StateTriggers>
<StateTrigger IsActive="{x:Bind ViewModel.IsBusy, Mode=OneWay}" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="StatusText.Foreground" Value="Gray" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<TextBlock x:Name="StatusText" Text="Loading" />
</Grid>Option 2: XAML Behaviors
Microsoft's documented migration for DataTrigger is XAML Behaviors via the Microsoft.Xaml.Behaviors.* packages. These give you DataTriggerBehavior, EventTriggerBehavior, and composable actions.
StateTrigger is the right call when the data condition drives a visual layout change that belongs next to the other states of a page or template. DataTriggerBehavior is a closer one-to-one port of the WPF mental model when you want an action (command invocation, property change on another element) rather than a state transition.
MultiTrigger Becomes a Combined StateTrigger
OR vs. AND: A VisualState can hold more than one StateTrigger. But if any (not all) of the triggers are active, the VisualState will be applied. That is OR semantics, not WPF MultiTrigger's AND semantics. A direct port of a MultiTrigger with two conditions into two StateTrigger instances changes the meaning.
The safe port is to combine the conditions in a view model boolean and bind a single StateTrigger:
<VisualState x:Name="HoverAndEnabled">
<VisualState.StateTriggers>
<StateTrigger IsActive="{x:Bind ViewModel.IsHoverAndEnabled, Mode=OneWay}" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="RootGrid.Background" Value="LightBlue" />
</VisualState.Setters>
</VisualState>Expose IsHoverAndEnabled as a computed boolean in the view model. This is more portable across WinUI, Uno Platform targets, and MVUX-style reactive feeds than trying to replicate MultiTrigger condition arithmetic in XAML.
EventTrigger Becomes a Behavior or Code-Behind
<Button Content="Go">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
From="1.0" To="0.0" Duration="0:0:0.4" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>Option 1: Code-Behind
private void GoButton_Click(object sender, RoutedEventArgs e)
{
var storyboard = (Storyboard)Resources["FadeOut"];
storyboard.Begin();
}Option 2: EventTriggerBehavior
<Button Content="Click to navigate"
xmlns:behaviors="using:CommunityToolkit.WinUI.Behaviors"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity">
<interactivity:Interaction.Behaviors>
<interactivity:EventTriggerBehavior EventName="Click">
<behaviors:NavigateToUriAction
NavigateUri="https://aka.ms/toolkit/windows" />
</interactivity:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Button>Substitute CallMethodAction, ChangePropertyAction, or any other action the Community Toolkit Behaviors page documents for the inner action element.
Gotchas: Inheritance, Ordering, and Storyboards
Three WPF habits that do not survive the trip:
- Style BasedOn inheritance works in WinUI 3. However, WPF
Style.Triggersdo not have a direct equivalent that inherits down style chains. Visual states live in templates, not styles. - Trigger ordering changes. WPF gives the last matching trigger precedence within a set of property setters. VisualStateManager has a simpler mental model: only one
VisualStateperVisualStateGroupis current at a time, and transitioning to a new state reverts the previous state's setters automatically. - WPF allows Storyboard inside BeginStoryboard under an EventTrigger. WinUI supports
StoryboardinsideVisualStateas the content property, but for event-driven animations outside of state changes, start the storyboard from code-behind withStoryboard.Begin()or use a Behavior.
VisualStateGroup placement: The VisualStateGroup must be declared under the first child of the root of a control template. If your root is a Grid, attach VisualStateManager.VisualStateGroups to that Grid, not to the Page or a sibling StackPanel.
FAQ
Can I still use Trigger in Uno Platform or WinUI 3?
No. System.Windows.Trigger lives in PresentationFramework.dll and is not part of WinUI 3 or Uno Platform. WinUI 3 does expose a Microsoft.UI.Xaml.EventTrigger class, but it is the Silverlight-compatibility type, not the WPF property trigger.
Do I need MVUX, or is x:Bind enough?
For most StateTrigger.IsActive scenarios, a OneWay x:Bind to a view model boolean is enough. MVUX becomes valuable when you want the underlying state to come from a reactive feed rather than a plain property.
How do I handle a DataTrigger that fires on a collection property?
For boolean derivations such as HasItems or Count > 0, compute the boolean in the view model and bind StateTrigger.IsActive to it. For more elaborate behaviors such as "run an animation when an item is added," use a Behavior or a code-behind handler attached to the collection's change notifications.
What about Interaction.Triggers from the Blend SDK?
The successor path is the Microsoft.Xaml.Behaviors.* family, which is what Microsoft recommends for WPF DataTrigger and MultiTrigger migration. In practice, use CommunityToolkit.WinUI.Behaviors with the xmlns:interactivity="using:Microsoft.Xaml.Interactivity" namespace.
Wire the first VisualStateGroup on a real template. If you are driving the conversion with an agent, Uno Platform publishes guidance for Claude Code with Uno Platform and for the Uno Platform MCP servers that feed the agent source-verified snippets for VisualStateManager, StateTrigger, and Behaviors.
- TriggerBase Class (Microsoft Learn) →
- Trigger Class (Microsoft Learn) →
- DataTrigger Class (Microsoft Learn) →
- MultiTrigger Class (Microsoft Learn) →
- EventTrigger Class (WinUI) →
- VisualState Class (WinUI) →
- StateTrigger.IsActive (WinUI) →
- Migrate WPF Patterns to WinUI 3 →
- Community Toolkit Behaviors →
- WPF to WinUI: Complete Control Map →
- Get Started with Claude + Uno Platform →
- Uno Platform MCP Servers →
Subscribe to Our Blog
Subscribe via RSS
Back to Top