views:

3395

answers:

4

I'm trying to trigger a progress animation when ever the ViewModel/Presentation Model is Bussy. I have a IsBussy Property, and the ViewModel is set as the DataContext of the UserControl. What is the best way to trigger a "progressAnimation" story board when the IsBussy property is true? Blend only let med add Event-Triggers on a 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 target name is obvisouly NOT what I want.

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

+1  A: 

I would recommend to use RoutedEvent instead of your IsBusy property. Just fire OnBusyStarted and OnBusyStopped event and use Event trigger on the appropriate elements.

Jobi Joy
Well, that was what I was hoping to avoid... But, say I do that: any examples on how to implement a RoutedEvent in a class that does NOT derive from UIElement?
Jonas Follesø
+1  A: 

You can subscribe to the PropertyChanged event of the DataObject class and make a RoutedEvent fire from Usercontrol level.

For RoutedEvent to work we need to have the class derived from DependancyObject

Jobi Joy
I think you're right... Exposing a RoutedEvent from the UserControl seams like the most obvious solution... However, I haven't given up on executing arbitrary storyboards based on data just yet.. But thanks for input!
Jonas Follesø
A: 

You can use Trigger.EnterAction to start an animation when a property is changed.

<Trigger Property="IsBusy" Value="true">
    <Trigger.EnterActions>
        <BeginStoryboard x:Name="BeginBusy" Storyboard="{StaticResource MyStoryboard}" />
    </Trigger.EnterActions>
    <Trigger.ExitActions>
        <StopStoryboard BeginStoryboardName="BeginBusy" />
    </Trigger.ExitActions>
</Trigger>
ligaz
Like I said, this is at the user control level, and I only accepts EventTriggers (not Property- or DataTriggers). Also, the IsBusy is not a property on the UserControl, but on the object set as the DataContext (the ViewModel)
Jonas Follesø
+5  A: 

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!

Dabblernl