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".
Related
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>
I have a bomb(element) in the canvas that is moving with doublanimation(vertical only) and I want to see if it hits an airplane(also an element on the canvas).
I wrote this code:
private bool HitCheck()
{
AirPlaneRect.Location = airplane.PointToScreen(new Point(Canvas.GetLeft(airplane), Canvas.GetTop(airplane)));
Rect BombPos = new Rect(bombPoint.X, Canvas.GetTop(this),this.bombImage.Height, this.bombImage.Width);
if (BombPos.IntersectsWith(AirPlaneRect))
{
return true;
}
return false;
}
but for some reason the bomb location is always the starting one.
I'm using a timer that call this method every half a second.
Instead of using a Timer to check periodic if they intersects, it is more accurate to check it when the DoubleAnimation is changed. When that happens, CurrentTimeInvalidated is raised.
I created a little sample to demonstrate what I mean. Here is the XAML:
<Canvas>
<!--be shure to assign Top and Left of all objects-->
<Rectangle Canvas.Left="0" Canvas.Top="0" Name="bomb"
Width="20" Height="100" Fill="gray"/>
<Rectangle Canvas.Left="0" Canvas.Top="200" Name="airPlane"
Width="300" Height="50" Fill="lightblue"/>
<Canvas.Triggers>
<!--Run the animation at startup-->
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard >
<Storyboard >
<DoubleAnimation Name="doubleAnimation"
Storyboard.TargetName="bomb"
Storyboard.TargetProperty="(Canvas.Top)"
From="0" To="400" Duration="0:0:5"
CurrentTimeInvalidated="CurrentTimeInvalidated"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Canvas.Triggers>
</Canvas>
And the code behind:
private void CurrentTimeInvalidated(object sender, EventArgs e)
{
if (HitTest(airPlane, bomb))
{
MessageBox.Show("Hit!");
//unregister the event handler to avoid to see the messagebox more than once
doubleAnimation.CurrentTimeInvalidated -= CurrentTimeInvalidated;
}
}
static bool HitTest(FrameworkElement airPlane, FrameworkElement bomb)
{
var r1 = new Rect(Canvas.GetLeft(airPlane), Canvas.GetTop(airPlane), airPlane.ActualWidth, airPlane.ActualHeight);
var r2 = new Rect(Canvas.GetLeft(bomb), Canvas.GetTop(bomb), bomb.ActualWidth, bomb.ActualHeight);
return r1.IntersectsWith(r2);
}
If you do it similar to me, it should work. Or is your code structure significant different to my?
I'm trying to create a storyboard in XAML that animates a property of one of the child elements of an element which raises an event. But I can't seem to get it to work without using Names, which is something I can't really do in this specific situation.
I'm basically trying something like this (much simplified of course):
<Canvas>
<Canvas.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Children[0].(Canvas.Left)" From="0" To="400" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Canvas.Triggers>
<Button Canvas.Left="20" Canvas.Top="20">A</Button>
<Button Canvas.Left="40" Canvas.Top="20">B</Button>
</Canvas>
Any ideas on how this could be achieved?
Providing that the UIElement you are indexing in the animation exists (i.e. already present on the Canvas) then you can do the following:
<Canvas x:Name="MyCanvas">
<Button x:Name="btn" Canvas.Left="20" Canvas.Top="20">A</Button>
<Button Canvas.Left="40" Canvas.Top="20">B</Button>
<Canvas.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.Target="{Binding ElementName=MyCanvas, Path=Children[0]}"
Storyboard.TargetProperty="(Canvas.Left)" From="0" To="400" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Canvas.Triggers>
</Canvas>
Notice how I have moved the addition of the Buttons above the Trigger. If the Buttons are below the Trigger as in your question, trying to access Children[0] will throw an ArgumentOutOfRangeException because there are no children at this point.
To use the Storyboard.TargetProperty in the animation, it should always be a dependency property. Children property gets a UIElementCollection of child elements of this Panel (Canvas). Therefore, the following construction Children [n] return UIElement, which should lead to a certain type, to access its dependency property.
This can be done in the code as follows:
Button MyButton = (Button)MyCanvas.Children[0];
MessageBox.Show(MyButton.Width.ToString());
All of these actions missing in the animation by default, this is your construction will not work.
I propose to create animations in the code where this conversion possible.
To demonstrate this, I created a Canvas, in the event Loaded having registered animation. Element number is set via an attached dependency property (of course, the example can be implemented in various ways). Below is my example:
XAML
<Grid>
<local:MyCanvas x:Name="MyCanvas" local:ClassForAnimation.Children="1">
<Button Canvas.Left="20" Canvas.Top="20">A</Button>
<Button Canvas.Left="40" Canvas.Top="20">B</Button>
</local:MyCanvas>
</Grid>
Code behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class MyCanvas : Canvas
{
public MyCanvas()
{
this.Loaded += new RoutedEventHandler(MyCanvas_Loaded);
}
private void MyCanvas_Loaded(object sender, RoutedEventArgs e)
{
MyCanvas myCanvas = sender as MyCanvas;
// Get No. of children
int children = ClassForAnimation.GetChildren(myCanvas);
// Get current Button for animation
Button MyButton = (Button)myCanvas.Children[children];
if (myCanvas != null)
{
DoubleAnimation doubleAnimation = new DoubleAnimation();
doubleAnimation.From = 0;
doubleAnimation.To = 400;
MyButton.BeginAnimation(Button.WidthProperty, doubleAnimation);
}
}
}
public class ClassForAnimation : DependencyObject
{
public static readonly DependencyProperty ChildrenProperty;
public static void SetChildren(DependencyObject DepObject, int value)
{
DepObject.SetValue(ChildrenProperty, value);
}
public static int GetChildren(DependencyObject DepObject)
{
return (int)DepObject.GetValue(ChildrenProperty);
}
static ClassForAnimation()
{
PropertyMetadata MyPropertyMetadata = new PropertyMetadata(0);
ChildrenProperty = DependencyProperty.RegisterAttached("Children",
typeof(int),
typeof(ClassForAnimation),
MyPropertyMetadata);
}
}
Note: Access to the items in the Canvas should only be done in the event Loaded, or when it ended. Otherwise, the items are not available because they are not loaded.
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...
I've been struggling with this for hours and I can't find out what I'm doing wrong. Please help me find my mistake.
I created a user control with one custom dependency property and I want to animate this property.
Here is my class:
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public long Val
{
get { return (long)GetValue(ValProperty); }
set {
SetValue(ValProperty, value);
// Update a text block for debug
((Class1)this.Resources["class1"]).Val = value;
}
}
public static readonly DependencyProperty ValProperty =
DependencyProperty.Register("Val", typeof(long), typeof(UserControl1), new UIPropertyMetadata(0L));
}
Here is the code that is supposed to animate it (there's an instance of UserControl1 called usercontrol11):
Int64Animation myAnimation = new Int64Animation();
myAnimation.From = 100;
myAnimation.To = 200;
myAnimation.Duration = new Duration(TimeSpan.FromSeconds(1));
Storyboard.SetTargetName(myAnimation, userControl11.Name);
Storyboard.SetTargetProperty(myAnimation, new PropertyPath(UserControl1.ValProperty));
Storyboard myStoryboard = new Storyboard();
myStoryboard.Children.Add(myAnimation);
myStoryboard.Begin(this);
I also tried the XAML approach, but it didn't work either (in the following XAML, the Width animation works fine, but the Val doesn't):
<my:UserControl1 HorizontalAlignment="Left" Width="150" Margin="72,45,0,0" x:Name="userControl11" VerticalAlignment="Top" Background="#FFFFD100">
<my:UserControl1.Triggers>
<EventTrigger RoutedEvent="MouseEnter">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<Int64Animation To="300" Duration="0:0:1"
Storyboard.TargetProperty="Val" />
<DoubleAnimation To="300" Duration="0:0:1"
Storyboard.TargetProperty="Width" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</my:UserControl1.Triggers>
</my:UserControl1>
Any help will be much appreciated!
Well, it's actually animated. You're just checking in the wrong place, animation won't call Val property set accessor. You can, for example, override OnPropertyChanged method to see changes:
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if(e.Property == ValProperty)
{
MessageBox.Show(e.NewValue.ToString());
}
}