Triggers in WinRT XAML

All the code for WinRTTriggers is available from the CodePlex project site, and is available in binary form in a nuget package.

A few months ago I attended one of Microsoft's "Windows Phone to Windows 8 App" events. The intent was to take Equazorand migrate it to a Windows Store application as much as possible over the space of two days.

On the whole, the event was really useful to just hang out with a bunch of other developers (along with some Microsofties like Mike Taulty and Martin Beeby) and I managed to port all the "business logic" of the app pretty successfully - you would expect so given that I had used the MVVM pattern with MvvmLight, which is now available for WinRT XAML applications.

Where I became unstuck, however, was my reliance on Expression Blend's triggers to manipulate the UI in response to the changing view model and handle the player's interactions; as it turns out these are not currently supported by in the WinRT XAML world.

So what's a was I to do, but write my own implementation?

Introducing WinRTTriggers

The best way to explain the sort of things you can do with WinRTTriggers is to show a quick snippet of XAML from the test app that’s available in the solution:

<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}" 
        DataContext="{StaticResource ViewModel}">
    <Triggers:Interactions.Triggers>
        <Triggers:PropertyChangedTrigger Binding="{Binding Person.Name}">
            <Triggers:ControlStoryboardAction Action="Start" 
                Storyboard="{StaticResource FlashNameChanged}" />
        </Triggers:PropertyChangedTrigger>
        ...
        <Triggers:PropertySetTrigger Binding="{Binding Person.IsHappy}" 
                RequiredValue="false">
            <Triggers:GotoStateAction StateName="Sad" />
        </Triggers:PropertySetTrigger>
    </Triggers:Interactions.Triggers>

    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="HappySad">
            <VisualStateGroup.Transitions>

There are 2 triggers demonstrated above:

  • The first watches the Person.Name property – when it is modified, it reacts by starting the FlashNameChangedstoryboard.
  • The second watches the Person.IsHappy property – when it gets set to false, it reacts by changing the visual state to Sad.

From this it should be relatively obvious that there’s a very simple trigger/action relationship going on here – you configure a trigger and specify the action that should happen as a result of it firing.

Triggers

Currently implemented are:

  • PropertyChangedTrigger - fires when a property changes to any value
  • PropertySetTrigger - like PropertyChangedTrigger, except it will only get fire when a property changes to a specified value.
  • EventTrigger - fires when an event associated to the control is fired, e.g. the Tapped event on a control. 

Actions

The current actions are:

  • GotoStateAction - Instructs the VisualStateManager to change to a named state
  • InvokeCommandAction - Invokes some ICommand implementation, probably located on your view model.
  • ControlStoryboardAction - Starts/Stops/Pauses a storyboard.

Todo…

There are some things that I know are missing, I definitely haven't covered all the triggers and that Expression did - I imagine these will be added over time.

Another big omission is the inability to apply conditions to triggers, i.e. only fire this trigger is some arbitrary value is true. I may (or may not) tackle these soon, depending on how much I need them!

Summary

I still haven’t got the Equazor port done yet, but I’ve had fun getting this framework up and running over some (very) limited free time. I think that even with just these few triggers I think you'll be able to replicate a reasonable amount of the old Blend interaction logic.

Let me know if you encounter any problems or have any requests.

7 Comments

  • kaki104 said

    hi
    greate work!
    i have one question.

    <DataTemplate x:Key="DataTemplate1">
    <Grid HorizontalAlignment="Left" Width="250" Height="250">
    <Grid.Resources>
    <Storyboard x:Key="storyboard1">
    <ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" Storyboard.TargetName="border">
    <EasingColorKeyFrame KeyTime="0" Value="#FFB8B5B5"/>
    <EasingColorKeyFrame KeyTime="0:0:1" Value="#FF3C3B3B"/>
    </ColorAnimationUsingKeyFrames>
    </Storyboard>
    </Grid.Resources>
    <t:Interactions.Triggers>
    <t:PropertyChangedTrigger Binding="{Binding Title}">
    <t:ControlStoryboardAction Action="Start" Storyboard="{StaticResource storyboard1}"/>
    </t:PropertyChangedTrigger>
    </t:Interactions.Triggers>
    <Border x:Name="border" Background="#FF3D3D3D"/>
    <StackPanel VerticalAlignment="Bottom" Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}">
    <TextBlock Text="{Binding Title}" Foreground="{StaticResource ListViewItemOverlayForegroundThemeBrush}" Style="{StaticResource TitleTextStyle}" Height="60" Margin="15,0,15,0" LayoutUpdated="TextBlock_LayoutUpdated_1" SelectionChanged="TextBlock_SelectionChanged_1"/>
    <TextBlock Text="{Binding Subtitle}" Foreground="{StaticResource ListViewItemOverlaySecondaryForegroundThemeBrush}" Style="{StaticResource CaptionTextStyle}" TextWrapping="NoWrap" Margin="15,0,15,10"/>
    </StackPanel>
    </Grid>
    </DataTemplate>

    i using ControlStoryboardAction in ItemTemplate.
    this source is good work in run-time but, it is not display in design-time
    how can i solve this problem?
    thank you

  • Mike said

    Hi kaki104, sorry for the delay getting back to you!

    What are you using to develop your XAML? If you're using Expression Blend, you can start and stop Storyboards at design time there.

    For what it's worth I'd actually recommend doing most of your design work there.

    Hope that help,
    Mike

  • kaki104 said

    thank you reply

    i open xaml when design time.
    raise error
    Error 1 illegal qualified name character C:\Samples\App1\App1\GroupedItemsPage.xaml 24 9 App1
    in itemtemplate

    this is sample source (App1.zip)
    https://skydrive.live.com/redir?resid=F2F0FC0CB0A58A69!3738

  • Mike said

    Firstly, thanks so much for posting the sample - it's really helpful to see how you're using it.

    Secondly, this answer is going to be a bit vague because it's late, but I want to respond to you promptly this time! I think I'm going to have to put together a separate blog post to fully describe what's going on here.

    The bottom line is that you're not really going to be able to use the triggers in a data template - attached properties don't really work well in that situation.

    I did think that you might be able to use them by setting up a style, but that doesn't work either because you end up with one of TriggerCollection shared across all entries in the grid, which just doesn't work.

    The only think I can think of off the top of my head that *might* work (I've not had time to test it yet) is to put the grid and all its associated elements (e.g. storyboard and triggers) into a separate user control - you should then be able to reference that user control in your data template.

    Anyway, I'll try to get this written up soon, along with all the frustrations of the data binding limitations I keep hitting in WinRT...

  • kaki104 said

    Hi
    Solve old problems.
    Splash portion was added to the template to create a user control.

    <UserControl x:Name="userControl"
    x:Class="UPax.UserConrols.SplashBackUserControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:UPax.UserConrols"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400"
    xmlns:t="using:WinRT.Triggers">

    <UserControl.Resources>
    <Storyboard x:Key="storyboard1">
    <ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" Storyboard.TargetName="border">
    <EasingColorKeyFrame KeyTime="0" Value="#FF555555"/>
    <EasingColorKeyFrame KeyTime="0:0:0.5" Value="#FF333435"/>
    </ColorAnimationUsingKeyFrames>
    </Storyboard>
    </UserControl.Resources>

    <Grid>
    <t:Interactions.Triggers>
    <t:PropertyChangedTrigger x:Name="pcTrigger" Binding="{Binding ChangeValue}">
    <t:ControlStoryboardAction Action="Start" Storyboard="{StaticResource storyboard1}" />
    </t:PropertyChangedTrigger>
    </t:Interactions.Triggers>

    <Border x:Name="border" Background="#FF333435"/>
    </Grid>
    </UserControl>

    Thank you

Comments have been disabled for this content.