WPF - Making an animation's execution conditional on a property of the bound data item

Drew Noakes picture Drew Noakes · Aug 18, 2009 · Viewed 9k times · Source

I have a data object -- a custom class called Notification -- that exposes a IsCritical property. The idea being that if a notification will expire, it has a period of validity and the user's attention should be drawn towards it.

Imagine a scenario with this test data:

_source = new[] {
    new Notification { Text = "Just thought you should know" },
    new Notification { Text = "Quick, run!", IsCritical = true },
  };

The second item should appear in the ItemsControl with a pulsing background. Here's a simple data template excerpt that shows the means by which I was thinking of animating the background between grey and yellow.

<DataTemplate DataType="Notification">
  <Border CornerRadius="5" Background="#DDD">
    <Border.Triggers>
      <EventTrigger RoutedEvent="Border.Loaded">
        <BeginStoryboard>
          <Storyboard>
            <ColorAnimation 
              Storyboard.TargetProperty="Background.Color"
              From="#DDD" To="#FF0" Duration="0:0:0.7" 
              AutoReverse="True" RepeatBehavior="Forever" />
          </Storyboard>
        </BeginStoryboard>
      </EventTrigger>
    </Border.Triggers>
    <ContentPresenter Content="{TemplateBinding Content}" />
  </Border>
</DataTemplate>

What I'm unsure about is how to make this animation conditional upon the value of IsCritical. If the bound value is false, then the default background colour of #DDD should be maintained.

Answer

Anvaka picture Anvaka · Aug 31, 2009

The final part of this puzzle is... DataTriggers. All you have to do is add one DataTrigger to your DataTemplate, bind it to IsCritical property, and whenever it's true, in it's EnterAction/ExitAction you start and stop highlighting storyboard. Here is completely working solution with some hard-coded shortcuts (you can definitely do better):

Xaml:

<Window x:Class="WpfTest.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Notification Sample" Height="300" Width="300">
  <Window.Resources>
    <DataTemplate x:Key="NotificationTemplate">
      <Border Name="brd" Background="Transparent">
        <TextBlock Text="{Binding Text}"/>
      </Border>
      <DataTemplate.Triggers>
        <DataTrigger Binding="{Binding IsCritical}" Value="True">
          <DataTrigger.EnterActions>
            <BeginStoryboard Name="highlight">
              <Storyboard>
                <ColorAnimation 
                  Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)"
                  Storyboard.TargetName="brd"
                  From="#DDD" To="#FF0" Duration="0:0:0.5" 
                  AutoReverse="True" RepeatBehavior="Forever" />
              </Storyboard>
            </BeginStoryboard>
          </DataTrigger.EnterActions>
          <DataTrigger.ExitActions>
            <StopStoryboard BeginStoryboardName="highlight"/>
          </DataTrigger.ExitActions>
        </DataTrigger>
      </DataTemplate.Triggers>
    </DataTemplate>
  </Window.Resources>
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="*"/>
      <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <ItemsControl ItemsSource="{Binding Notifications}"
                  ItemTemplate="{StaticResource NotificationTemplate}"/>
    <Button Grid.Row="1"
            Click="ToggleImportance_Click"
            Content="Toggle importance"/>
  </Grid>
</Window>

Code behind:

using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;

namespace WpfTest
{
  public partial class Window1 : Window
  {
    public Window1()
    {
      InitializeComponent();
      DataContext = new NotificationViewModel();
    }

    private void ToggleImportance_Click(object sender, RoutedEventArgs e)
    {
      ((NotificationViewModel)DataContext).ToggleImportance();
    }
  }

  public class NotificationViewModel
  {
    public IList<Notification> Notifications
    {
      get;
      private set;
    }

    public NotificationViewModel()
    {
      Notifications = new List<Notification>
                        {
                          new Notification
                            {
                              Text = "Just thought you should know"
                            },
                          new Notification
                            {
                              Text = "Quick, run!",
                              IsCritical = true
                            },
                        };
    }

    public void ToggleImportance()
    {
      if (Notifications[0].IsCritical)
      {
        Notifications[0].IsCritical = false;
        Notifications[1].IsCritical = true;
      }
      else
      {
        Notifications[0].IsCritical = true;
        Notifications[1].IsCritical = false;
      }
    }
  }

  public class Notification : INotifyPropertyChanged
  {
    private bool _isCritical;

    public string Text { get; set; }

    public bool IsCritical
    {
      get { return _isCritical; }
      set
      {
        _isCritical = value;
        InvokePropertyChanged("IsCritical");
      }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void InvokePropertyChanged(string name)
    {
      var handler = PropertyChanged;
      if (handler != null)
      {
        handler(this, new PropertyChangedEventArgs(name));
      }
    }
  }
}

Hope this helps :).