VisualStateManager can't generate transitions for ThicknessAnimations - wpf

I have the following Visual States defined:
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="EditStates">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0:0:2"/>
</VisualStateGroup.Transitions>
<VisualState Name="Editing" />
<VisualState Name="Normal">
<Storyboard>
<ThicknessAnimation Storyboard.TargetName="ViewBorder" Storyboard.TargetProperty="Margin" To="0" Duration="0"/>
<DoubleAnimation Storyboard.TargetName="Header" Storyboard.TargetProperty="(FrameworkElement.LayoutTransform).(ScaleTransform.ScaleY)" To="0" Duration="0"/>
<ColorAnimation Storyboard.TargetName="EditBorder" Storyboard.TargetProperty="Background.Color" To="Red" Duration="0"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
The DoubleAnimation and ColorAnimation work fine, with VisualStateManager generating transition animations over 2 seconds for them.
However, the ThicknessAnimation does not animate. Instead it snaps to the finish value at the end of the transition period.
Is there any way to get VisualStateManager to generate transitions for it, or am I going to be forced to supply manual transitions?

I analyzed the problem and fired up .NET Reflector and found that the VisualStateManager only supports the following animations:
ColorAnimation
DoubleAnimation
PointAnimation
It's kind of crappy, because it's not documented anywhere.
To prove that it cannot generate the animations take a look at the reversed code of the VisualStageManager.GenerateToAnimation method. There is also a VisualStageManager.GenerateFromAnimation that supports the same subset of animations.
private static Timeline GenerateToAnimation(FrameworkElement root, Timeline timeline, IEasingFunction easingFunction, bool isEntering)
{
Timeline destination = null;
if (destination == null)
{
var targetColor = GetTargetColor(timeline, isEntering);
if (targetColor.HasValue)
destination = new ColorAnimation { To = targetColor, EasingFunction = easingFunction };
}
if (destination == null)
{
var targetDouble = GetTargetDouble(timeline, isEntering);
if (targetDouble.HasValue)
destination = new DoubleAnimation { To = targetDouble, EasingFunction = easingFunction };
}
if (destination == null)
{
var targetPoint = GetTargetPoint(timeline, isEntering);
if (targetPoint.HasValue)
destination = new PointAnimation { To = targetPoint, EasingFunction = easingFunction };
}
if (destination != null)
CopyStoryboardTargetProperties(root, timeline, destination);
return destination;
}
Bottomline... You can only use Color, Double or Point animations in the VisualStageManager. Revert to old-fashioned triggers if you need something else...

Maybe your ThicknessAnimation statement is not complete, i search the flowing code sample from MSDN in the topic "ThicknessAnimation Class".
<ThicknessAnimation
Storyboard.TargetProperty="BorderThickness"
Duration="0:0:1.5" FillBehavior="HoldEnd" From="1,1,1,1" To="28,14,28,14" />
Hope this could help you...

Related

Trying to animate the Path StrokeProperty throws Invalid operation exception

I have code when a certain value is reached, I want to flash the Stroke of the Path to Red. When I try to apply the animation like this, it says "''System.Windows.Media.Animation.DoubleAnimation' animation object cannot be used to animate property 'Stroke' because it is of incompatible type 'System.Windows.Media.Brush'.'" I also tried ColorAnimation and still a similar exception. Please help.
In the ControlTemplate of xaml I have Path defined as follows.
<ControlTemplate x:Key="FlaskProgressBarTemplate" TargetType="{x:Type local:FlaskProgressBar}">
<Grid>
<Path x:Name="Outline"
StrokeThickness="8"
Stroke="Black">
<Path.Data>
<PathGeometry Figures="M 20,15 L 20,60 0,110 60,110 40,60 40,15 Z"/>
</Path.Data>
</Path>
</Grid>
In the code behind on some method exceution,
private Path _contentPath;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_contentPath = this.GetTemplateChild("Outline") as Path;
SetValue(this, Value);
}
private static void SetValue(FlaskProgressBar instance, double value)
{
if (instance._content != null)
{
var rect = instance._content.Rect;
instance._content.Rect = new Rect(rect.X, rect.Height - value, rect.Width, rect.Height);
if (value == 100)
{
AnimateBorder(instance); //Error in this method.
}
}
}
private static void AnimateBorder(FlaskProgressBar instance)
{
var path = instance._contentPath;
if (path != null)
{
path.Stroke = new SolidColorBrush(Colors.Red);
var switchOnAnimation = new DoubleAnimation
{
To = 1,
Duration = TimeSpan.Zero,
BeginTime = TimeSpan.FromSeconds(0.5)
};
var blinkStoryboard = new Storyboard
{
Duration = TimeSpan.FromSeconds(1),
RepeatBehavior = RepeatBehavior.Forever
};
Storyboard.SetTarget(switchOnAnimation, path);
Storyboard.SetTargetProperty(switchOnAnimation, new PropertyPath(Path.StrokeProperty));
blinkStoryboard.Children.Add(switchOnAnimation);
path.BeginStoryboard(blinkStoryboard); //I get an exception on this line.
}
}
You can achieve a blinking effect by animating the brush's Opacity property (which is of type Double) rather than the brush itself.
This is the style from my BlinkingBorder class that featured in a recent blog post.
<Style TargetType="{x:Type vctrl:perBlinkingBorder}">
<Style.Triggers>
<Trigger Property="IsBlinking" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard TargetProperty="(BlinkingBorderBrush).Opacity">
<DoubleAnimation
AutoReverse="True"
RepeatBehavior="Forever"
From="1"
To="0"
Duration="0:0:0.5">
<DoubleAnimation.EasingFunction>
<SineEase EasingMode="EaseInOut" />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</Style.Triggers>
</Style>

Calling an event for control on an inactive tab results in an exception

There is a control that has animation DoubleAnimationUsingPath, the animation is triggered by the event RoutedEvent = "ui:CheckBoxProgressCircle.Check"
<EventTrigger RoutedEvent="ui:CheckBoxProgressCircle.Check">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimationUsingPath Storyboard.TargetName="AnimatedTranslateTransform"
Storyboard.TargetProperty="X"
PathGeometry="{StaticResource CheckAnimationPath}"
Source="X"
Duration="0:0:.1" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
In the code, I register the event:
static CheckBoxProgressCircle()
{
CheckEvent =
EventManager.RegisterRoutedEvent("Check", RoutingStrategy.Tunnel, typeof(RoutedEventHandler), typeof(CheckBoxProgressCircle));
}
public event RoutedEventHandler Check
{
add
{
AddHandler(CheckEvent, value);
}
remove
{
RemoveHandler(CheckEvent, value);
}
}
Then I call the event:
var newEventArgs = new RoutedEventArgs(CheckEvent);
RaiseEvent(newEventArgs);
If the control is on a tab that is active, everything works without errors, if the control is on a tab that has never become active, I get an error:
''AnimatedTranslateTransform' name cannot be found in the name scope of 'System.Windows.Controls.ControlTemplate'.'
I think this is due to the fact that the control did not render, because was on an inactive tab and does not have AnimatedTranslateTransform, how I can force wpf to render this element before the tab will become active? Or if I'm wrong, what can I do about it?
The solution turned out to be quite simple: you can call the ApplyTemplate () method; for this control.
https://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.applytemplate.aspx

Converting XAML to code for dynamic control

The designer created some XAML VisualState code that works fine when built as XAML. It is now my task to convert this XAML into Code Behind that is a custom Template control inheriting from ChildWindow that will build animations to transition between various "windows" that are all child UserControls to this ChildWindow.
I have written all the code, however when I run the any of the Animations, I get an error that Silverlight "cannot resolve targetproperty '(UIElement.Projection).(PlaneProjection.RotationY)' "
This is the origional XAML
<VisualState x:Name="AtRegistration">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="WindowContentPresenter" Storyboard.TargetProperty="(Content).Children[0].(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="00:00:00" Value="Collapsed" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="WindowContentPresenter" Storyboard.TargetProperty="(Content).Children[1].(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="00:00:00" Value="Visible" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="AtLogin">
<Storyboard>
... XAML omitted ...
</Storyboard>
</VisualState>
<VisualStateGroup.Transitions>
<VisualTransition From="AtRegistration" To="AtLogin">
<Storyboard>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="ContentRoot" Storyboard.TargetProperty="(UIElement.Projection).(PlaneProjection.RotationY)">
... XAML OMITTED ...
</DoubleAnimationUsingKeyFrames>
... XAML OMITTED ...
</Storyboard>
</VisualTransition>
<VisualTransition From="AtLogin" To="AtRegistration">
... XAML OMITTED ...
</VisualTransition>
</VisualStateGroup.Transitions>
... XAML OMITTED ...
The above XAML works fine, however when I write the code, this does not. I'm assuming its something I am doing wrong in code, but I cannot find it.
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
var content = (ContentPresenter)GetTemplateChild( "WindowContentPresenter" );
m_contentRoot = (Grid)GetTemplateChild( "ContentRoot" );
var grid = (Grid)GetTemplateChild( "Root" );
if( grid != null )
{
// Create the state group
var animGroup = new VisualStateGroup();
animGroup.SetValue( NameProperty, "AnimationStates" );
var grp = VisualStateManager.GetVisualStateGroups( grid );
grp.Add( animGroup );
for( int i = 0; i < MultiControls.Count; i++ )
{
var state = new VisualState();
state.SetValue( NameProperty, GetControlName( i ) );
animGroup.States.Add( state );
state.Storyboard = new Storyboard();
// create an animation for each of the multi controls
for( int j = 0; j < MultiControls.Count; j ++ )
{
// Create the storyboard
var anim = new ObjectAnimationUsingKeyFrames();
Storyboard.SetTarget( state.Storyboard, content );
anim.SetValue( Storyboard.TargetPropertyProperty, new PropertyPath( string.Format( "(Content).Children[{0}].(UIElement.Visibility)", j ) ) );
anim.KeyFrames.Add( new DiscreteObjectKeyFrame
{
KeyTime = KeyTime.FromTimeSpan( new TimeSpan( 0, 0, 0 ) ),
Value = j == i ? Visibility.Visible : Visibility.Collapsed
} );
state.Storyboard.Children.Add( anim );
// Don't create a transition to and from the other states.
if( i == j )
continue;
// Create the Transition
var trans = new VisualTransition { From = GetControlName( j ), To = GetControlName( i ) };
animGroup.Transitions.Add( trans );
trans.Storyboard = new Storyboard();
var dbl = new DoubleAnimationUsingKeyFrames { BeginTime = TimeSpan.FromSeconds( 0 ) };
Storyboard.SetTarget( trans.Storyboard, m_contentRoot );
dbl.SetValue( Storyboard.TargetPropertyProperty, new PropertyPath( "(UIElement.Projection).(PlaneProjection.RotationY)" ) );
trans.Storyboard.Children.Add( dbl );
... CODE OMITTED ...
}
}
}
}
In my Template XAML I have this:
<Grid x:Name="Overlay" HorizontalAlignment="Stretch" VerticalAlignment="Top" Margin="0" Background="{TemplateBinding OverlayBrush}" Opacity="{TemplateBinding OverlayOpacity}"/>
<Grid x:Name="ContentRoot" HorizontalAlignment="{TemplateBinding HorizontalAlignment}" VerticalAlignment="{TemplateBinding VerticalAlignment}" RenderTransformOrigin="0.5,0.5" Height="{TemplateBinding Height}" Width="{TemplateBinding Width}">
<Grid.Projection>
<PlaneProjection/>
</Grid.Projection>
Do you guys/gals have any ideas?
Thanks.
Second parameter of Storyboard.SetTarget should be the projection and not the m_contentroot. Then all you need to specify in the path is "RotationY".

How to do a code behind for Silverlight Storyboard Begintime instead of xaml

I need to translate this xaml code into code behind for begintime.
<Storyboard BeginTime="0:0:10" x:Name="sbEllipse1">
<DoubleAnimation
Storyboard.TargetName="myBrush1"
Storyboard.TargetProperty="RadiusX"
From="0" To="1"
Duration="0:0:20"
/>
<DoubleAnimation
Storyboard.TargetName="myBrush1"
Storyboard.TargetProperty="RadiusY"
From="0" To="1"
Duration="0:0:20"
/>
</Storyboard>
Storyboard sb = new Storyboard();
sb.BeginTime = TimeSpan.FromSeconds(10);
sb.Children.Add(new DoubleAnimation());
sb.Children.Add(new DoubleAnimation());
Storyboard sb = new Storyboard();
sb.BeginTime = TimeSpan.FromSeconds(10);
<DoubleAnimation
Storyboard.TargetName="myBrush1"
Storyboard.TargetProperty="RadiusX"
From="0" To="1"
Duration="0:0:20"
/>
//Equivalent code for the above is :
DoubleAnimation db = new DoubleAnimation();
db.From = 0;
db.To = 1;
db.Duration = new Duration(TimeSpan.FromSeconds(20));
Storyboard.SetTarget(db, myBrush1);
Storyboard.SetTargetProperty(db, RadiusX);
<DoubleAnimation
Storyboard.TargetName="myBrush1"
Storyboard.TargetProperty="RadiusY"
From="0" To="1"
Duration="0:0:20"
/>
//Equivalent code for the above is :
DoubleAnimation db1 = new DoubleAnimation();
db1.From = 0;
db1.To = 1;
db1.Duration = new Duration(TimeSpan.FromSeconds(20));
Storyboard.SetTarget(db, myBrush1);
Storyboard.SetTargetProperty(db, RadiusY);
//assigning both double animation to main Storyboard
sb.Children.Add(db);
sb.Children.Add(db1);
myBrush1.Resources.Add(storyboard);
sb.Begin();

WPF Animation with datatriggers

I have a rectangle which I am animating the backround colour of.
It should change to green each time a particular number goes up. And red when it goes dowm. If the number doesn't change for a while it slowly fades back to its default colour
So the animation changes th background from grey to red very quickly and then takes several seconds to fade back to grey.
I have added as DataTrigger which is bound to 1 or -1 depending on how the number has changed
The problem is that if the number keeps going up the animation does not get restarted.
e.g. if the sequence of numbers went 1, 2, 3, 4, 5. Then I would like the animation to restart at each number change
the code I am using is below
<Rectangle.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding BidChangeDirectionIndicator}"
Value="-1">
<DataTrigger.EnterActions>
<BeginStoryboard x:Name="bidDownStory">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[1].(GradientStop.Color)">
<EasingColorKeyFrame KeyTime="0"
Value="#FF79797A" />
<EasingColorKeyFrame KeyTime="0:0:0.2"
Value="#FFF13B29" />
<EasingColorKeyFrame KeyTime="0:0:10.0"
Value="#FF79797A" />
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[0].(GradientStop.Color)">
<EasingColorKeyFrame KeyTime="0"
Value="#FF2B2B2B" />
<EasingColorKeyFrame KeyTime="0:0:0.2"
Value="#FF3F0606" />
<EasingColorKeyFrame KeyTime="0:0:10.0"
Value="#FF2B2B2B" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<StopStoryboard BeginStoryboardName="bidDownStory" />
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
and the ViewModel I am binding to looks like this
private int _bidChangeDirectionIndicator;
public int BidChangeDirectionIndicator
{
get { return _bidChangeDirectionIndicator; }
set
{
_bidChangeDirectionIndicator = value;
RaisePropertyChanged("BidChangeDirectionIndicator");
}
}
....
public void Update(RateInfo rateInfo)
{
if (rateInfo.Bid != Bid)
{
BidChangeDirectionIndicator = Math.Sign(rateInfo.Bid - Bid);
}
}
The method gets called each time the number changes (this is done by a class which is listening to an external feed)
[Caveat: I have not had time to recreate and test this. I just thought it may help.]
The reason that the trigger does not fire is that the value does not change. When you, for instance, keep going up, the value of BidChangeDirectionIndicator remains 1.
Perhaps the simplest thing to do is to set it to another value (0), and then to the value you want.
public void Update(RateInfo rateInfo)
{
if (rateInfo.Bid != Bid)
{
BidChangeDirectionIndicator = Math.Sign(rateInfo.Bid - Bid);
}
else
{
BidChangeDirectionIndicator = 0;
BidChangeDirectionIndicator = rateInfo.Bid;
}
}

Resources