Expression SDK in Silverlight–DataTrigger Example Wednesday, May 11 2011
One type of Trigger available in the Expression SDK is the DataTrigger. This a trigger that uses a Binding to determine when to execute its actions. If you are using MVVM (or any other pattern that relies heavily on view models or presentation models), this class is a perfect way to interact with the UI without needing to write any custom code in the model or codebehind.
For the purpose of this post, I’m going to be creating a simple header/button control that is powered by a view model and alters the user interface of the control based on data changes.
The view model representing the 3 header controls:
1: using System;
2: using System.ComponentModel;
3:
4: namespace UsefulCode {
5: public class HeaderViewModel : INotifyPropertyChanged {
6: public event PropertyChangedEventHandler PropertyChanged = delegate { };
7:
8: private bool _isEnabled = true;
9:
10: public Uri Image { get; set; }
11:
12: public string HeaderText { get; set; }
13:
14: public bool IsEnabled {
15: get { return _isEnabled; }
16: set {
17: if( _isEnabled != value ) {
18: _isEnabled = value;
19: PropertyChanged( this, new PropertyChangedEventArgs( "IsEnabled" ) );
20: }
21: }
22: }
23: }
24: }
Nothing special here, it’s a simple class that notifies on property changed and provides an image, text and an enabled Boolean property. The concept here is the application will have one or more headers that can be enabled or disabled and are intended to give the user visual feedback when the change happens.
Next we need to create a UserControl to provide a visual representation for our header:
1: <UserControl x:Class="UsefulCode.HeaderButton"
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:i="http://schemas.microsoft.com/expression/2010/interactivity"
6: xmlns:ia="http://schemas.microsoft.com/expression/2010/interactions"
7: xmlns:e="http://schemas.microsoft.com/expression/2010/effects"
8: xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
9: <Grid x:Name="LayoutRoot" Background="White" VerticalAlignment="Top">
10: <Grid.ColumnDefinitions>
11: <ColumnDefinition Width="96"></ColumnDefinition>
12: <ColumnDefinition Width="Auto"></ColumnDefinition>
13: </Grid.ColumnDefinitions>
14: <Image x:Name="img"
15: Grid.Column="0"
16: Source="{Binding Image}"
17: Width="96" Height="96" Margin="4"
18: Stretch="Uniform">
19: </Image>
20: <TextBlock x:Name="tb"
21: VerticalAlignment="Center"
22: Text="{Binding HeaderText}" Grid.Column="1" Margin="4"
23: FontSize="18" FontWeight="Bold"></TextBlock>
24: </Grid>
25: </UserControl>
As it stands like this, the images and text appear but when the application disables a header, the control doesn’t know. One solution to this would be to find a way to bind the IsEnabled property to one or more visual properties inside this control and create one or more converters to convert from true/false to the appropriate values. For instance, suppose we had a converter that converted from Boolean to Colors. We could change the color of the TextBlock easily:
1: <TextBlock x:Name="tb"
2: VerticalAlignment="Center"
3: Text="{Binding HeaderText}" Grid.Column="1" Margin="4"
4: Foreground="{Binding IsEnabled, Converter={StaticResource BooleanColorConverter}}"
5: FontSize="18" FontWeight="Bold"></TextBlock>
While this is easy to do (and I’ve done it numerous times), if there are multiple visual properties you have to deal with you’ll need to create more bindings and more converters. It’s not the best solution if you need to change a lot of properties. So what can we do better? A storyboard with a DataTrigger:
1: <i:Interaction.Triggers>
2: <ia:DataTrigger Binding="{Binding IsEnabled}" Comparison="Equal" Value="false">
3: <ia:ControlStoryboardAction Storyboard="{StaticResource DisableStoryboard}"></ia:ControlStoryboardAction>
4: </ia:DataTrigger>
5: <ia:DataTrigger Binding="{Binding IsEnabled}" Comparison="Equal" Value="true">
6: <ia:ControlStoryboardAction Storyboard="{StaticResource EnableStoryboard}"></ia:ControlStoryboardAction>
7: </ia:DataTrigger>
8: </i:Interaction.Triggers>
The DataTrigger uses a Binding to the IsEnabled property and when it’s a specific value (false or true), we use a ControlStoryboardAction (also from the Expression SDK) to begin a storyboard to perform our desired animations. When our header is disabled, we’ll change the image to black & white, change the text color to light gray and shrink the text. Here are the storyboards to do this:
1: <UserControl.Resources>
2: <Storyboard x:Key="DisableStoryboard" Duration="00:00:00.5" FillBehavior="HoldEnd">
3: <ObjectAnimationUsingKeyFrames Storyboard.TargetName="img" Storyboard.TargetProperty="Effect">
4: <DiscreteObjectKeyFrame KeyTime="00:00:00.0">
5: <DiscreteObjectKeyFrame.Value>
6: <e:ColorToneEffect DarkColor="Black" LightColor="White"></e:ColorToneEffect>
7: </DiscreteObjectKeyFrame.Value>
8: </DiscreteObjectKeyFrame>
9: </ObjectAnimationUsingKeyFrames>
10: <ColorAnimation Storyboard.TargetName="tb"
11: Storyboard.TargetProperty="(TextBox.Foreground).(SolidColorBrush.Color)"
12: To="LightGray"></ColorAnimation>
13: <DoubleAnimation Storyboard.TargetName="tb"
14: Storyboard.TargetProperty="FontSize"
15: To="12"></DoubleAnimation>
16: </Storyboard>
17: <Storyboard x:Key="EnableStoryboard" Duration="00:00:00.5" FillBehavior="HoldEnd">
18: <ObjectAnimationUsingKeyFrames Storyboard.TargetName="img" Storyboard.TargetProperty="Effect">
19: <DiscreteObjectKeyFrame KeyTime="00:00:00.0" Value="{x:Null}"></DiscreteObjectKeyFrame>
20: </ObjectAnimationUsingKeyFrames>
21: <ColorAnimation Storyboard.TargetName="tb"
22: Storyboard.TargetProperty="(TextBlock.Foreground).(SolidColorBrush.Color)"
23: To="Black"></ColorAnimation>
24: <DoubleAnimation Storyboard.TargetName="tb"
25: Storyboard.TargetProperty="FontSize"
26: To="18"></DoubleAnimation>
27: </Storyboard>
28: </UserControl.Resources>
In DisableStoryboard we use the ColorToneEffect bitmap effect to convert the image to black and white (this effect is also provided by the Expression SDK). After that we use a standard ColorAnimation and DoubleAnimation to perform the rest of the animations (text color and font size).
With these added, clicking the toggle buttons disables our headers and performs the desired animations:
For reference here is my MainPage.xaml:
1: <UserControl x:Class="UsefulCode.MainPage"
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: mc:Ignorable="d"
7: xmlns:local="clr-namespace:UsefulCode"
8: d:DesignHeight="300" d:DesignWidth="400">
9:
10: <Grid x:Name="LayoutRoot" Background="White">
11: <StackPanel Orientation="Vertical">
12: <ItemsControl ItemsSource="{Binding Headers}">
13: <ItemsControl.ItemsPanel>
14: <ItemsPanelTemplate>
15: <StackPanel Orientation="Horizontal"></StackPanel>
16: </ItemsPanelTemplate>
17: </ItemsControl.ItemsPanel>
18: <ItemsControl.ItemTemplate>
19: <DataTemplate>
20: <local:HeaderButton></local:HeaderButton>
21: </DataTemplate>
22: </ItemsControl.ItemTemplate>
23: </ItemsControl>
24:
25: <StackPanel Orientation="Horizontal">
26: <Button Content="Toggle Captain Planet" Click="ToggleButton1"></Button>
27: <Button Content="Toggle Ninja Turtles" Click="ToggleButton2"></Button>
28: <Button Content="Toggle Spider Dog" Click="ToggleButton3"></Button>
29: </StackPanel>
30: </StackPanel>
31: </Grid>
32: </UserControl>
And the codebehind for it:
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Windows;
5: using System.Windows.Controls;
6:
7: namespace UsefulCode {
8: public partial class MainPage : UserControl {
9: public IEnumerable<HeaderViewModel> Headers { get; private set; }
10:
11: public MainPage() {
12: InitializeComponent();
13:
14: Headers = new[] {
15: new HeaderViewModel() {
16: HeaderText = "Captain Planet",
17: IsEnabled = true,
18: Image = new Uri("captainplanet.jpg", UriKind.Relative)
19: },
20: new HeaderViewModel() {
21: HeaderText = "Ninja Turtles",
22: IsEnabled = true,
23: Image = new Uri("ninjaturtles.jpg", UriKind.Relative)
24: },
25: new HeaderViewModel() {
26: HeaderText = "Spider Dog",
27: IsEnabled = true,
28: Image = new Uri("spiderdog.jpg", UriKind.Relative)
29: }
30: };
31:
32: this.DataContext = this;
33: }
34:
35: private void ToggleButton1( object sender, RoutedEventArgs e ) {
36: HeaderViewModel vm = Headers.Skip( 0 ).First();
37: vm.IsEnabled = !vm.IsEnabled;
38: }
39:
40: private void ToggleButton2( object sender, RoutedEventArgs e ) {
41: HeaderViewModel vm = Headers.Skip( 1 ).First();
42: vm.IsEnabled = !vm.IsEnabled;
43: }
44:
45: private void ToggleButton3( object sender, RoutedEventArgs e ) {
46: HeaderViewModel vm = Headers.Skip( 2 ).First();
47: vm.IsEnabled = !vm.IsEnabled;
48: }
49: }
50: }
And the full header control:
1: <UserControl x:Class="UsefulCode.HeaderButton"
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:i="http://schemas.microsoft.com/expression/2010/interactivity"
6: xmlns:ia="http://schemas.microsoft.com/expression/2010/interactions"
7: xmlns:e="http://schemas.microsoft.com/expression/2010/effects"
8: xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
9: <UserControl.Resources>
10: <Storyboard x:Key="DisableStoryboard" Duration="00:00:00.5" FillBehavior="HoldEnd">
11: <ObjectAnimationUsingKeyFrames Storyboard.TargetName="img" Storyboard.TargetProperty="Effect">
12: <DiscreteObjectKeyFrame KeyTime="00:00:00.0">
13: <DiscreteObjectKeyFrame.Value>
14: <e:ColorToneEffect DarkColor="Black" LightColor="White"></e:ColorToneEffect>
15: </DiscreteObjectKeyFrame.Value>
16: </DiscreteObjectKeyFrame>
17: </ObjectAnimationUsingKeyFrames>
18: <ColorAnimation Storyboard.TargetName="tb"
19: Storyboard.TargetProperty="(TextBox.Foreground).(SolidColorBrush.Color)"
20: To="LightGray"></ColorAnimation>
21: <DoubleAnimation Storyboard.TargetName="tb"
22: Storyboard.TargetProperty="FontSize"
23: To="12"></DoubleAnimation>
24: </Storyboard>
25: <Storyboard x:Key="EnableStoryboard" Duration="00:00:00.5" FillBehavior="HoldEnd">
26: <ObjectAnimationUsingKeyFrames Storyboard.TargetName="img" Storyboard.TargetProperty="Effect">
27: <DiscreteObjectKeyFrame KeyTime="00:00:00.0" Value="{x:Null}"></DiscreteObjectKeyFrame>
28: </ObjectAnimationUsingKeyFrames>
29: <ColorAnimation Storyboard.TargetName="tb"
30: Storyboard.TargetProperty="(TextBlock.Foreground).(SolidColorBrush.Color)"
31: To="Black"></ColorAnimation>
32: <DoubleAnimation Storyboard.TargetName="tb"
33: Storyboard.TargetProperty="FontSize"
34: To="18"></DoubleAnimation>
35: </Storyboard>
36: </UserControl.Resources>
37: <i:Interaction.Triggers>
38: <ia:DataTrigger Binding="{Binding IsEnabled}" Comparison="Equal" Value="false">
39: <ia:ControlStoryboardAction Storyboard="{StaticResource DisableStoryboard}"></ia:ControlStoryboardAction>
40: </ia:DataTrigger>
41: <ia:DataTrigger Binding="{Binding IsEnabled}" Comparison="Equal" Value="true">
42: <ia:ControlStoryboardAction Storyboard="{StaticResource EnableStoryboard}"></ia:ControlStoryboardAction>
43: </ia:DataTrigger>
44: </i:Interaction.Triggers>
45: <Grid x:Name="LayoutRoot" Background="White" VerticalAlignment="Top">
46: <Grid.ColumnDefinitions>
47: <ColumnDefinition Width="96"></ColumnDefinition>
48: <ColumnDefinition Width="Auto"></ColumnDefinition>
49: </Grid.ColumnDefinitions>
50: <Image x:Name="img"
51: Grid.Column="0"
52: Source="{Binding Image}"
53: Width="96" Height="96" Margin="4"
54: Stretch="Uniform">
55: </Image>
56: <TextBlock x:Name="tb"
57: VerticalAlignment="Center"
58: Text="{Binding HeaderText}" Grid.Column="1" Margin="4"
59: FontSize="18" FontWeight="Bold"></TextBlock>
60: </Grid>
61: </UserControl>


Great Job!!! So many examples with eventTrigger but nothing for dataTrigger.