wpfanimationexpression-blend

WPF Data Triggers and Story Boards


I'm trying to trigger a progress animation when ever the ViewModel/Presentation Model is Busy. I have an IsBusy Property, and the ViewModel is set as the DataContext of the UserControl. What is the best way to trigger a "progressAnimation" storyboard when the IsBusy property is true? Blend only lets me add event triggers at the UserControl level, and I can only create property triggers in my data templates.

The "progressAnimation" is defined as a resource in the user control.

I tried adding the DataTriggers as a Style on the UserControl, but when I try to start the StoryBoard I get the following error:

'System.Windows.Style' value cannot be assigned to property 'Style' of object'Colorful.Control.SearchPanel'. A Storyboard tree in a Style cannot specify a TargetName. Remove TargetName 'progressWheel'.

ProgressWheel is the name of the object I'm trying to animate, so removing the target name is obviously NOT what I want.

I was hoping to solve this in XAML using data binding techniques, instead of having to expose events and start/stop the animation through code.


Solution

  • What you want is possible by declaring the animation on the progressWheel itself: The XAML:

    <UserControl x:Class="TriggerSpike.UserControl1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Height="300" Width="300">
    <UserControl.Resources>
        <DoubleAnimation x:Key="SearchAnimation" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:4"/>
        <DoubleAnimation x:Key="StopSearchAnimation" Storyboard.TargetProperty="Opacity" To="0" Duration="0:0:4"/>
    </UserControl.Resources>
    <StackPanel>
        <TextBlock Name="progressWheel" TextAlignment="Center" Opacity="0">
            <TextBlock.Style>
                <Style>
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding IsBusy}" Value="True">
                            <DataTrigger.EnterActions>
                                <BeginStoryboard>
                                    <Storyboard>
                                        <StaticResource ResourceKey="SearchAnimation"/>
                                    </Storyboard>
                                </BeginStoryboard>
                            </DataTrigger.EnterActions>
                            <DataTrigger.ExitActions>
                                <BeginStoryboard>
                                    <Storyboard>
                                       <StaticResource ResourceKey="StopSearchAnimation"/> 
                                    </Storyboard>
                                </BeginStoryboard>
                            </DataTrigger.ExitActions>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </TextBlock.Style>
            Searching
        </TextBlock>
        <Label Content="Here your search query"/>
        <TextBox Text="{Binding SearchClause}"/>
        <Button Click="Button_Click">Search!</Button>
        <TextBlock Text="{Binding Result}"/>
    </StackPanel>
    

    Code behind:

        using System.Windows;
    using System.Windows.Controls;
    
    namespace TriggerSpike
    {
        public partial class UserControl1 : UserControl
        {
            private MyViewModel myModel;
    
            public UserControl1()
            {
                myModel=new MyViewModel();
                DataContext = myModel;
                InitializeComponent();
            }
    
            private void Button_Click(object sender, RoutedEventArgs e)
            {
                myModel.Search(myModel.SearchClause);
            }
        }
    }
    

    The viewmodel:

        using System.ComponentModel;
    using System.Threading;
    using System.Windows;
    
    namespace TriggerSpike
    {
        class MyViewModel:DependencyObject
        {
    
            public string SearchClause{ get;set;}
    
            public bool IsBusy
            {
                get { return (bool)GetValue(IsBusyProperty); }
                set { SetValue(IsBusyProperty, value); }
            }
    
            public static readonly DependencyProperty IsBusyProperty =
                DependencyProperty.Register("IsBusy", typeof(bool), typeof(MyViewModel), new UIPropertyMetadata(false));
    
    
    
            public string Result
            {
                get { return (string)GetValue(ResultProperty); }
                set { SetValue(ResultProperty, value); }
            }
    
            public static readonly DependencyProperty ResultProperty =
                DependencyProperty.Register("Result", typeof(string), typeof(MyViewModel), new UIPropertyMetadata(string.Empty));
    
            public void Search(string search_clause)
            {
                Result = string.Empty;
                SearchClause = search_clause;
                var worker = new BackgroundWorker();
                worker.DoWork += worker_DoWork;
                worker.RunWorkerCompleted += worker_RunWorkerCompleted;
                IsBusy = true;
                worker.RunWorkerAsync();
            }
    
            void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
            {
                IsBusy=false;
                Result = "Sorry, no results found for: " + SearchClause;
            }
    
            void worker_DoWork(object sender, DoWorkEventArgs e)
            {
                Thread.Sleep(5000);
            }
        }
    }
    

    Hope this helps!