问题
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