1

What I want to do (in pseudo);

<!-- As a resource -->
<Storyboard x:Key="RepetitiveAnimation">
   <DoubleAnimation .../>
</Storyboard>

<!-- In the templates -->
<VisualStateManager>
   <VisualState x:Name="blah" Storyboard="{StaticResource RepetitiveAnimation}"/>
</VisualStateManager>

or even like;

<VisualStateManager>
   <VisualState x:Name="blah">
      <BeginStoryboard Storyboard="{StaticResource RepetitiveAnimation}"/>
   </VisualState>
</VisualStateManager>

What doesnt work; See above.

You can do this in WPF, but in SL there's no joy. Is there an equivalent I'm missing?

Cheers!

Chris W.
  • 22,835
  • 3
  • 60
  • 94
  • Ah crap, I posted a duplicate. I'll leave it up for a little while since the answer on the other one is not close to satisfactory but will del this thing soon (and yes I've marked it as such.) - edit, it wont let me mark it as duplicate since the other one isnt marked as answered or upvoted but it's [here](http://stackoverflow.com/questions/10954024/how-to-reuse-storyboard-with-visualstatemanager-in-silverlight-5) and I'll throw a bounty at it later if this one doesn't get some attention. – Chris W. Mar 26 '14 at 16:13

1 Answers1

0

I tried reusing Storyboards in my VisualStates as well, but to no avail. But there is a thing you can do that may mitigate the problem: Define your own AttachedProperties (attachable to instances of VisualState) for frequently used Storyboard tasks, for example:

  • setting Visibility on named Elements
  • applying Opacity values to named Elements
  • even setting Focus via a Storyboard.

The xaml would look like this:

<VisualStateGroup x:Name="TravelPlanningSteps">
    <VisualState x:Name="DateSelection"
        utils:VisualStateUtils.VisibleElements="DateSelector"
        utils:VisualStateUtils.FocusedElement="DateSelector"/>
    <VisualState x:Name="HotelSelection"
        utils:VisualStateUtils.VisibleElements="HotelSelector, BreakfastSelector"
        utils:VisualStateUtils.FocusedElement="HotelSelector"/>
</VisualStateGroup>

And the code (sorry, it's a bit lengthy):

public static class VisualStateUtils
{
    #region FocusedElement

    public static string GetFocusedElement( VisualState obj )
    {
        return (string) obj.GetValue( FocusedElementProperty );
    }

    public static void SetFocusedElement( VisualState obj, string value )
    {
        obj.SetValue( FocusedElementProperty, value );
    }

    public static readonly DependencyProperty FocusedElementProperty =
        DependencyProperty.RegisterAttached( "FocusedElement", typeof( string ), typeof( VisualStateUtils ), new PropertyMetadata( HandleFocusedElementChanged ) );

    private static void HandleFocusedElementChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
    {
        var state = d as VisualState;
        if (state == null) return;

        string elementName = e.NewValue as string;
        if (elementName == null) return;

        var storyBoard = state.Storyboard;
        if (storyBoard == null)
        {
            storyBoard = new Storyboard();
            state.Storyboard = storyBoard;
        }

        ClearAutoDefinedFocusClaim( storyBoard );

        var ani = new ObjectAnimationUsingKeyFrames();
        Storyboard.SetTargetName( ani, elementName );
        Storyboard.SetTargetProperty( ani, new PropertyPath( VisualStateUtils.FocusClaimProperty ) );
        VisualStateUtils.SetIsAutoDefinedFocusClaim( ani, true );
        ani.KeyFrames.Add( new DiscreteObjectKeyFrame { Value = true, KeyTime = KeyTime.FromTimeSpan( TimeSpan.FromMilliseconds( 0 ) ) } );
        storyBoard.Children.Add( ani );
    }

    public static bool GetFocusClaim( Control focusTarget )
    {
        return (bool) focusTarget.GetValue( FocusClaimProperty );
    }

    public static void SetFocusClaim( Control focusTarget, bool value )
    {
        focusTarget.SetValue( FocusClaimProperty, value );
    }

    public static readonly DependencyProperty FocusClaimProperty =
        DependencyProperty.RegisterAttached( "FocusClaim", typeof( bool ), typeof( VisualStateUtils ), new PropertyMetadata( false, HandleFocusClaimChanged ) );

    private static void HandleFocusClaimChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var focusTarget = d as Control;
        if(focusTarget==null) return;

        var shouldReceiveFocusNow = (bool) e.NewValue;

        if (shouldReceiveFocusNow)
        {
            if (!focusTarget.Focus()) CheckFocusability( focusTarget );
        }
    }

    private static void CheckFocusability(Control focusTarget)
    {
        //so the focus() call was not successful, what's the problem? lets see...
        //the control may still be collapsed
        //(and another animation will turn it visible anytime soon, remember: we are part of ongoing VisualState switching)
        if (!focusTarget.IsLoaded())
            focusTarget.Loaded += HandleFocusTargetLoaded;

        //it may be disabled (and another animation will enable it)
        else if (!focusTarget.IsEnabled)
            focusTarget.IsEnabledChanged += HandleFocusTargetEnabled;
    }

    private static void HandleFocusTargetLoaded( object sender, RoutedEventArgs routedEventArgs )
    {
        var focusTarget = (Control) sender;
        focusTarget.Loaded -= HandleFocusTargetLoaded;
        focusTarget.Focus();
    }

    private static void HandleFocusTargetEnabled(object sender, DependencyPropertyChangedEventArgs e)
    {
        var focusTarget = (Control) sender;
        focusTarget.IsEnabledChanged -= HandleFocusTargetEnabled;
        focusTarget.Focus();
    }

    public static bool GetIsAutoDefinedFocusClaim( DependencyObject obj )
    {
        return (bool) obj.GetValue( IsAutoDefinedFocusClaimProperty );
    }

    public static void SetIsAutoDefinedFocusClaim( DependencyObject obj, bool value )
    {
        obj.SetValue( IsAutoDefinedFocusClaimProperty, value );
    }

    public static readonly DependencyProperty IsAutoDefinedFocusClaimProperty =
        DependencyProperty.RegisterAttached( "IsAutoDefinedFocusClaim", typeof( bool ), typeof( VisualStateUtils ), new PropertyMetadata( false ) );

    private static void ClearAutoDefinedFocusClaim( Storyboard storyBoard )
    {
        var toDelete = storyBoard.Children.Where( VisualStateUtils.GetIsAutoDefinedFocusClaim ).ToList();
        toDelete.ForEach( animation => storyBoard.Children.Remove( animation ) );
    }

    #endregion

    #region CollapsedElements

    public static readonly DependencyProperty IsAutoCreatedCollapsedElementProperty =
        DependencyProperty.RegisterAttached( "IsAutoCreatedCollapsedElement", typeof( bool ), typeof( VisualStateUtils ), new PropertyMetadata( default( bool ) ) );

    private static void SetIsAutoCreatedCollapsedElement( DependencyObject element, bool value )
    {
        element.SetValue( IsAutoCreatedCollapsedElementProperty, value );
    }

    private static bool GetIsAutoCreatedCollapsedElement( DependencyObject element )
    {
        return (bool) element.GetValue( IsAutoCreatedCollapsedElementProperty );
    }

    public static readonly DependencyProperty CollapsedElementsProperty =
        DependencyProperty.RegisterAttached( "CollapsedElements", typeof( string ), typeof( VisualStateUtils ), new PropertyMetadata( default( string ), HandleCollapsedElementsChanged ) );

    private static void HandleCollapsedElementsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var state = d as VisualState;
        if (state == null) return;

        string elementNames = e.NewValue as string;
        if (elementNames == null) return;

        CreateAutoDefinedAnimationsForVisibility( Visibility.Collapsed, state, elementNames );
    }

    public static void SetCollapsedElements( VisualState state, string value )
    {
        state.SetValue( CollapsedElementsProperty, value );
    }

    public static string GetCollapsedElements( VisualState state )
    {
        return (string) state.GetValue( CollapsedElementsProperty );
    }

    #endregion

    #region VisibleElements

    public static readonly DependencyProperty VisibleElementsProperty =
        DependencyProperty.RegisterAttached( "VisibleElements", typeof( string ), typeof( VisualStateUtils ), new PropertyMetadata( default( string ), HandleVisibleElementsChanged ) );

    private static void HandleVisibleElementsChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
    {
        var state = d as VisualState;
        if (state == null) return;

        string elementNames = e.NewValue as string;
        if (elementNames == null) return;

        CreateAutoDefinedAnimationsForVisibility( Visibility.Visible, state, elementNames );
    }

    public static void SetVisibleElements( VisualState state, string value )
    {
        state.SetValue( VisibleElementsProperty, value );
    }

    public static string GetVisibleElements( VisualState state )
    {
        return (string) state.GetValue( VisibleElementsProperty );
    }

    public static readonly DependencyProperty IsAutoCreatedVisibleElementProperty =
        DependencyProperty.RegisterAttached( "IsAutoCreatedVisibleElement", typeof( bool ), typeof( VisualStateUtils ), new PropertyMetadata( default( bool ) ) );

    private static void SetIsAutoCreatedVisibleElement( DependencyObject element, bool value )
    {
        element.SetValue( IsAutoCreatedVisibleElementProperty, value );
    }

    private static bool GetIsAutoCreatedVisibleElement( DependencyObject element )
    {
        return (bool) element.GetValue( IsAutoCreatedVisibleElementProperty );
    }

    #endregion

    private static void CreateAutoDefinedAnimationsForVisibility( Visibility visibility, VisualState state, string elementNames )
    {
        var storyBoard = state.Storyboard;
        if (storyBoard == null)
        {
            storyBoard = new Storyboard();
            state.Storyboard = storyBoard;
        }

        ClearAutoDefinedElementAnimations( visibility, storyBoard );

        string[] namesOfManipulatedElements = (elementNames ?? string.Empty).Split( ',' );
        namesOfManipulatedElements = namesOfManipulatedElements.Select( name => name.Trim() ).ToArray();
        foreach (var elementName in namesOfManipulatedElements)
        {
            var ani = new ObjectAnimationUsingKeyFrames();
            Storyboard.SetTargetName( ani, elementName );
            Storyboard.SetTargetProperty( ani, new PropertyPath( "Visibility" ) );
            MarkAutoDefinedElementAnimation( visibility, ani );
            ani.KeyFrames.Add( new DiscreteObjectKeyFrame { Value = visibility, KeyTime = KeyTime.FromTimeSpan( TimeSpan.FromMilliseconds( 0 ) ) } );
            storyBoard.Children.Add( ani );
        }
    }

    private static void ClearAutoDefinedElementAnimations( Visibility visibility, Storyboard storyBoard )
    {
        if (visibility == Visibility.Visible)
        {
            var toDelete = storyBoard.Children.Where( VisualStateUtils.GetIsAutoCreatedVisibleElement ).ToList();
            toDelete.ForEach( ani => storyBoard.Children.Remove( ani ) );
        }
        else
        {
            var toDelete = storyBoard.Children.Where( VisualStateUtils.GetIsAutoCreatedCollapsedElement ).ToList();
            toDelete.ForEach( ani => storyBoard.Children.Remove( ani ) );
        }
    }

    private static void MarkAutoDefinedElementAnimation( Visibility visibility, ObjectAnimationUsingKeyFrames animation )
    {
        if (visibility == Visibility.Visible)
            VisualStateUtils.SetIsAutoCreatedVisibleElement( animation, true );
        else
            VisualStateUtils.SetIsAutoCreatedCollapsedElement( animation, true );
    }
}

And not to forget the handy Extension to check whether a control is loaded or not:

public static class ControlExtensions
{
    public static bool IsLoaded(this FrameworkElement element)
    {
        return element.GetVisualChildren().Any();
        //or just check the parent ...not sure what's better
        //return System.Windows.Media.VisualTreeHelper.GetParent(element) != null;
    }
}
Martin
  • 5,714
  • 2
  • 21
  • 41
  • It's clever, I'll give it that. Except it kind of breaks the mvvm style I'm going for and seems like some potentially chunky overhead considering how many instances it would get used against. I'm hoping there's a simpler/cleaner way but I'll keep this in mind. Cheers! – Chris W. Mar 26 '14 at 18:40
  • I'm using it with a strict MVVM approach too. The code is entirely contained in the view layer and only doing "view stuff" (visibility, styling, input focus). Regarding the overhead: it programatically adds animations to visualState storyboards, and that is only happening once, when the xaml is parsed. But I can understand your objection: it really looks chunky at first sight. – Martin Mar 27 '14 at 08:57
  • Ya I see your point, my main qualm is how it's piggy backing the focused event and dynamically throwing the changes at it. The comment "we are part of ongoing VisualState switching" throws chills at me haha, if I get some free time later I'll give it a shot and throw a profiler at it to judge a little closer. It's still more clever than what I've come up with so far as an alternative, which is nothing lol :) – Chris W. Mar 27 '14 at 14:12