Playing Storyboards MVVM

Aug 7, 2009 at 7:38 PM

I am having an issue playing storyboards in my MVVM project. I used the sample provided in the messenger demo that utlizes an attached property to cause storyboards to play.

My view contains two storyboards, one is displayed to expand the control so if lies in from the top right and becomes visible, the other sends it back to the top right to "collape" it. The animations work fine if I start them from a trigger on the control loaded event. Since that is not what I want I created an attached dependency property in a static class to handle the transitions.

The first transition works just fine, when the ViewVisibleState is set to VisibleState.Expand the storyboard plays and the control scales and comes into view. When I change the property to VisibleState.Collapse the property changed event on the StoryboardManager class for the VisibleState property is raised but the oldStoryboard is not being removed and the newStoryboard is not being played as the control just sits on the screen.

I am not sure what the problem is because the property changed event does fire and no errors are raised when attempting to remove the old storyboard or play the new one. Seems like it will play one storyboard once and then any calls are just ignored. I tried changing the HandOffbehavior and FillBehavior, tried removing the SB when the completed event is raised but nothing seems to make a difference.

When looking at the code below I bolded the sections I think are relevant, rather than posting all the code that sets the ViewVisibleState property since the property changed event is being raised when the VisibleState changes from Expand to Collapse or visa versa.

Any suggestions or ideas are welcome...

- Cameron

 

Here is my view's xaml:

 

<UserControl
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
	xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" 
	xmlns:BL_WDE_CVT_Dashboard_Models="clr-namespace:BL.WDE.CVT.Dashboard.Models"
	xmlns:ui="clr-namespace:BL.WDE.CVT.Dashboard.UI;assembly=BL.WDE.CVT.Dashboard.UI" 
    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 
    xmlns:im="clr-namespace:Microsoft.Expression.Interactivity.Media;assembly=Microsoft.Expression.Interactions" 
    x:Class="BL.WDE.CVT.Dashboard.Views.SignInView"
    Height="350" Width="350"
    ui:StoryboardManager.VisibleState="{Binding ViewVisibleState, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
    <UserControl.Resources>
        <Storyboard x:Key="ExpandStoryboard">
            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="grid" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
                <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0.183"/>
                <SplineDoubleKeyFrame KeyTime="00:00:01" Value="1"/>
            </DoubleAnimationUsingKeyFrames>
            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="grid" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
                <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0.091"/>
                <SplineDoubleKeyFrame KeyTime="00:00:01" Value="1"/>
            </DoubleAnimationUsingKeyFrames>
            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="grid" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)">
                <SplineDoubleKeyFrame KeyTime="00:00:00" Value="386.925"/>
                <SplineDoubleKeyFrame KeyTime="00:00:01" Value="0"/>
            </DoubleAnimationUsingKeyFrames>
            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="grid" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.Y)">
                <SplineDoubleKeyFrame KeyTime="00:00:00" Value="-318.775"/>
                <SplineDoubleKeyFrame KeyTime="00:00:01" Value="0"/>
            </DoubleAnimationUsingKeyFrames>
            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="grid" Storyboard.TargetProperty="(UIElement.Opacity)">
                <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
                <SplineDoubleKeyFrame KeyTime="00:00:01" Value="1"/>
            </DoubleAnimationUsingKeyFrames>
        </Storyboard>
        <Storyboard x:Key="CollapseStoryboard">
            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="grid" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
                <SplineDoubleKeyFrame KeyTime="00:00:00" Value="1"/>
                <SplineDoubleKeyFrame KeyTime="00:00:01" Value="0.183"/>
            </DoubleAnimationUsingKeyFrames>
            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="grid" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
                <SplineDoubleKeyFrame KeyTime="00:00:00" Value="1"/>
                <SplineDoubleKeyFrame KeyTime="00:00:01" Value="0.091"/>
            </DoubleAnimationUsingKeyFrames>
            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="grid" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)">
                <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
                <SplineDoubleKeyFrame KeyTime="00:00:01" Value="386.925"/>
            </DoubleAnimationUsingKeyFrames>
            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="grid" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.Y)">
                <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
                <SplineDoubleKeyFrame KeyTime="00:00:01" Value="-318.775"/>
            </DoubleAnimationUsingKeyFrames>
            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="grid" Storyboard.TargetProperty="(UIElement.Opacity)">
                <SplineDoubleKeyFrame KeyTime="00:00:00" Value="1"/>
                <SplineDoubleKeyFrame KeyTime="00:00:01" Value="0"/>
            </DoubleAnimationUsingKeyFrames>
        </Storyboard>
    </UserControl.Resources>
    <Grid VerticalAlignment="Center" HorizontalAlignment="Center" Width="350" Height="350" x:Name="grid" RenderTransformOrigin="0.5,0.5">
        <Grid.RenderTransform>
            <TransformGroup>
                <ScaleTransform/>
                <SkewTransform/>
                <RotateTransform/>
                <TranslateTransform/>
            </TransformGroup>
        </Grid.RenderTransform>
        <TextBlock HorizontalAlignment="Left" VerticalAlignment="Top" FontSize="18.667" FontWeight="Bold" Text="Welcome To Your Dashboard!" TextWrapping="Wrap" Foreground="{DynamicResource H1Brush}"/>
        <TextBlock HorizontalAlignment="Left" Margin="0,30,0,0" VerticalAlignment="Top" Text="{Binding CurrentDate}" TextWrapping="Wrap" Foreground="{DynamicResource InfoBrush}" x:Name="lblDate" FontSize="14.667"/>
        <TextBlock HorizontalAlignment="Left" Margin="0,83,0,0" VerticalAlignment="Top" Text="Employee ID" TextWrapping="Wrap" Foreground="{DynamicResource TextBrush}" FontSize="16"/>
        <TextBlock HorizontalAlignment="Left" Margin="16.196,139.93,0,0" VerticalAlignment="Top" Text="Password" TextWrapping="Wrap" Foreground="{DynamicResource TextBrush}" FontSize="16"/>
        <TextBox Margin="94.237,83,8,0" VerticalAlignment="Top" Text="{Binding EmployeeID, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" TextWrapping="Wrap" Height="30" Style="{DynamicResource TextBoxStyle}" x:Name="txtEmplyeeID" FontSize="14.667"/>
        <PasswordBox Margin="94.237,139.93,8,0" VerticalAlignment="Top" Password="" 
					 ui:PasswordBoxAssistant.BindPassword="true"  
					 ui:PasswordBoxAssistant.BoundPassword="{Binding Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
					 Height="30" 
					 Style="{DynamicResource PasswordBoxStyle}" 
					 x:Name="txtPassword" FontSize="14.667"/>
        <Button Margin="94.237,279.93,115.817,0" x:Name="btnForgotPassword" Command="{Binding ForgotPasswordCommand}" ToolTip="Forgotten Password" VerticalAlignment="Top" Content="Forgotten Password" IsEnabled="True" Height="28" Style="{DynamicResource SubButtonStyle}" FontWeight="Bold" FontSize="12"/>
        <TextBlock Margin="94.237,0,0,0" VerticalAlignment="Bottom" FontSize="12" Text="If you have a problem logging in, call IT Support on XXX-XXX-XXXX" TextWrapping="Wrap" Foreground="{DynamicResource InfoBrush}"/>
        <Button HorizontalAlignment="Left" Margin="94.237,0,0,113.974" Width="129.773" Content="Submit" x:Name="btnSubmit" Command="{Binding AuthenticateCommand}" VerticalAlignment="Bottom" Height="40" Style="{DynamicResource ButtonStyle}" Foreground="{DynamicResource ButtonTextBrush}" FontWeight="Bold" FontSize="16"/>
    </Grid>
</UserControl>

Here is the StoryboardManager class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;
using System.Windows;
using System.Windows.Media.Animation;

namespace BL.WDE.CVT.Dashboard.UI
{
	/// <summary>
	/// Manages a collection of storyboards and allows a storyboard to play and execute a command upon completion.
	/// </summary>
	public static class StoryboardManager
	{
		#region VisibleState
		public static readonly DependencyProperty VisibleStateProperty = DependencyProperty.RegisterAttached("VisibleState", typeof(VisibleState), typeof(StoryboardManager), new FrameworkPropertyMetadata(VisibleState.Collapse, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnVisibleStatePropertyChanged));
		private static void OnVisibleStatePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			FrameworkElement element = d as FrameworkElement;
			if (element == null) return;
			string oldStoryboardName = String.Concat(e.OldValue, "Storyboard");
			string newStoryboardName = String.Concat(e.NewValue, "Storyboard");

			if (element.Resources.Contains(oldStoryboardName))
			{
				Storyboard oldStoryboard = element.Resources[oldStoryboardName] as Storyboard;
				if (oldStoryboard != null)
					oldStoryboard.Remove(element);
			}

			if (element.Resources.Contains(newStoryboardName))
			{
				Storyboard newStoryboard = element.Resources[newStoryboardName] as Storyboard;
				EventHandler handler = null;
				handler = delegate
				{
					newStoryboard.Completed -= handler;

					var command = element.GetValue(StoryboardManager.CommandProperty) as ICommand;
					var param = element.GetValue(StoryboardManager.CommandParameterProperty);
					if (command != null)
					{
						if (command.CanExecute(param))
							command.Execute(param);
					}
				};
				newStoryboard.Completed += handler;
				newStoryboard.Begin(element, HandoffBehavior.Compose, true);
			}
		}

		public static VisibleState GetVisibleState(DependencyObject obj)
		{
			return (VisibleState)obj.GetValue(VisibleStateProperty);
		}
		public static void SetVisibleState(DependencyObject obj, VisibleState value)
		{
			obj.SetValue(VisibleStateProperty, value);
		}
		#endregion

		#region CommandParameter

		/// <summary>
		/// CommandParameter Attached Dependency Property
		/// </summary>
		public static readonly DependencyProperty CommandParameterProperty =
			DependencyProperty.RegisterAttached("CommandParameter", typeof(object), typeof(StoryboardManager),
				new FrameworkPropertyMetadata((object)null));

		/// <summary>
		/// Gets the CommandParameter property.
		/// </summary>
		public static object GetCommandParameter(DependencyObject d)
		{
			return d.GetValue(CommandParameterProperty);
		}

		/// <summary>
		/// Sets the CommandParameter property.
		/// </summary>
		public static void SetCommandParameter(DependencyObject d, object value)
		{
			d.SetValue(CommandParameterProperty, value);
		}

		#endregion

		#region Command

		/// <summary>
		/// Command Attached Dependency Property
		/// </summary>
		public static readonly DependencyProperty CommandProperty =
			DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(StoryboardManager),
				new FrameworkPropertyMetadata((ICommand)null));

		/// <summary>
		/// Gets the Command property.
		/// </summary>
		public static ICommand GetCommand(DependencyObject d)
		{
			return (ICommand)d.GetValue(CommandProperty);
		}

		/// <summary>
		/// Sets the Command property.
		/// </summary>
		public static void SetCommand(DependencyObject d, ICommand value)
		{
			d.SetValue(CommandProperty, value);
		}

		#endregion

	}
}