Re-use Animations in VisualStates

吃可爱长大的小学妹 提交于 2019-12-12 23:14:19

问题


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!


回答1:


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;
    }
}


来源:https://stackoverflow.com/questions/22666405/re-use-animations-in-visualstates

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!