WPF TranslateTransform Error - wpf

I have a StackPanel that I am sliding left and right to simulate moving pages using TranslateTransform.
If I call the slide method once it works well. If I call my slide method twice in quick succession (in code), the second transform has the wrong start position and ends up in the wrong place.
How do I get my second translate to "refresh" its starting position?
Here is the StackPanel
<StackPanel
x:Name="MainPanel"
Orientation="Horizontal">
<StackPanel.RenderTransform>
<TranslateTransform
x:Name="MainPanelTransform"
/>
</StackPanel.RenderTransform>
</StackPanel>
Here is the slide code:
private void SlidePage(int pagesToMove)
{
Storyboard sb = SlideEffect(MainPanel, (-PageWidth * pagesToMove));
sb.Completed += SlideCompleted;
sb.Begin();
}
private Storyboard SlideEffect(UIElement controlToAnimate, double positionToMove)
{
//Get position of stackpanel
var gt = controlToAnimate.TransformToVisual(MainGrid);
var p = gt.Transform(new Point(0, 0));
//add new storyboard and animation
var sb = new Storyboard();
var da = new DoubleAnimation { To = p.X + positionToMove };
Storyboard.SetTarget(da, controlToAnimate);
Storyboard.SetTargetProperty(da, new PropertyPath("RenderTransform.(TranslateTransform.X)"));
//Storyboard.SetTargetProperty(da, new PropertyPath("RenderTransform.Children[0].X"));
var ee = new ExponentialEase { Exponent = 6.0, EasingMode = EasingMode.EaseOut };
da.EasingFunction = ee;
sb.Children.Add(da);
return sb;
}
The offending line of code is this:
var p = gt.Transform(new Point(0, 0));
The second time I call the SlideEffect method it has the same value as the first time. It seems like the animations are getting buffered and run together. Is there any way to stop the buffering?

Did you try to change a FillBehavior property of Storyboard?
UPDATE:
I have a bug in VS, so I couldn't reproduce your situation. But I may recommend you:
Try to disable the EasingFunction. Maybe Storyboard.Completed fires before the Storyboard actually completed;
Are you sure, that transform changes actual coordinates? I think it is the solution, but not sure.
In the Storyboard.Completed you could manually update transform coordinates:
MainPanelTransform.BeginAnimation(TranslateTransform.XProperty, null);
MainPanelTransform.X = -100;

Related

Running multiple animations in Windows Store App

The following code is was originally part of a WPF app that I'm converting over to WinRT; however, when trying to run the animations, they appear to be mutually exclusive (that is, they both work, but only one at a time - whereas in WPF I could run them together):
private void SpinAndDisappear_Click(object sender, RoutedEventArgs e)
{
UIElement obj = (UIElement)SpinAndDisappear;
obj.RenderTransform = new CompositeTransform();
var story = new Storyboard();
var xAnim = new DoubleAnimation();
var yAnim = new DoubleAnimation();
xAnim.Duration = TimeSpan.FromSeconds(2);
yAnim.Duration = TimeSpan.FromSeconds(2);
xAnim.To = 0;
yAnim.To = 0;
story.Children.Add(xAnim);
story.Children.Add(yAnim);
Storyboard.SetTarget(xAnim, obj);
Storyboard.SetTarget(yAnim, obj);
Storyboard.SetTargetProperty(xAnim, "(UIElement.RenderTransform).(CompositeTransform.ScaleX)");
Storyboard.SetTargetProperty(yAnim, "(UIElement.RenderTransform).(CompositeTransform.ScaleY)");
story.Begin();
SpinAnimation(); // If commented out, the button changes size,
// and if not, it spins, but not both
}
void SpinAnimation()
{
UIElement obj = (UIElement)SpinAndDisappear;
obj.RenderTransform = new RotateTransform();
var storySpin = new Storyboard();
var spinAnim = new DoubleAnimation();
spinAnim.Duration = TimeSpan.FromSeconds(2);
spinAnim.From = 0;
spinAnim.To = 360;
storySpin.Children.Add(spinAnim);
Storyboard.SetTarget(spinAnim, obj);
Storyboard.SetTargetProperty(spinAnim, "(UIElement.RenderTransform).(RotateTranform.Angle)");
storySpin.Begin();
}
So my question is: how to do both simultaneously in WinRT?
Only one Storyboard can run on a single element in WinRT/XAML. If you need to run two of them independently from each other - the easiest way to do it is to have two elements and target separate elements. In your case you could have a parent Grid with a ScaleTransform and a child control with a RotateTransform. Then you would call Storyboard.SetTarget() to target the separate transforms in separate storyboards and everything should just work.

Move a WPF Shape

I cannot figure out how to move a WPF shape at runtime. Specifically, I want to move a ellipse.
Here is my current code:
private void Tick(object sender, EventArgs e)
{
Point ballLocation = ball.TransformToAncestor(Application.Current.MainWindow).Transform(new Point(0, 0));
//MessageBox.Show(ballLocation.ToString());
Canvas.SetLeft(ball, ballLocation.X + 5);
InvalidateVisual();
}
Every time the timer ticks (1 second) the ball should move 5 pixels in the x direction, correct? If this is wrong, how do I get the current location of the Ellipse and how do I set it to a new location. Maybe there is a problem with the InvalidateVisual? I believe that basically repaints the control. If that is wrong, how do I repaint the ellipse to show its change in location. I am also tried ball.InvalidateVisual(), it did not work.
This is how I create and start the timer:
var timer = new DispatcherTimer {IsEnabled = true};
timer.Tick += Tick;
timer.Interval = new TimeSpan(0, 0, 1);
timer.Start();
If Canvas.Left is initialized with some value, either in XAML
<Ellipse Name="ball" Canvas.Left="0" ... />
or in code
Canvas.SetLeft(ball, 0);
the following works:
void Tick(object sender, EventArgs e)
{
Canvas.SetLeft(ball, Canvas.GetLeft(ball) + 5);
}
The initialization is necessary because the default value is Double.NaN.
You may however consider to animate the Canvas.Left property to get a smooth movement. Start reading here: Animation Overview

WPF DrawingContext seems ignore SnapToDevicePixels

I'm drawing a chart by direct calls to DrawLine on the DrawingContext. Since I want to avoid any anti aliasing feature, I tryed to put the SnapToDevicePixels=true on the parent UIElement, but I still have anti-alias:
The project was an old OS project not written for WPF4, but I retarget it to the Framework4, can this be an issue too ?
I found this link where they basically say you should set
ParentUIElement.SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased);
It worked for me, so it might be worth a try!
SnapsToDevicePixels works only for element bounding box. You need to use Guidelines with DrawingContext. Also you can specify VisualXSnappingGuidelines and VisualYSnappingGuidelines if it fits to your requirements.
GuidelineSet is designed to resolve your problems.
#Marat Khasanov recommended you to use GuidelineSet and you replied that it messed up your code. I'm also suffering from this problem, so I write the code below to solve this problem with non-ugly code.
Notice: This method works even in a Viewbox.
public static class SnapDrawingExtensions
{
public static void DrawSnappedLinesBetweenPoints(this DrawingContext dc,
Pen pen, double lineThickness, params Point[] points)
{
var guidelineSet = new GuidelineSet();
foreach (var point in points)
{
guidelineSet.GuidelinesX.Add(point.X);
guidelineSet.GuidelinesY.Add(point.Y);
}
var half = lineThickness / 2;
points = points.Select(p => new Point(p.X + half, p.Y + half)).ToArray();
dc.PushGuidelineSet(guidelineSet);
for (var i = 0; i < points.Length - 1; i = i + 2)
{
dc.DrawLine(pen, points[i], points[i + 1]);
}
dc.Pop();
}
}
Call the method in OnRender and pass line points to it.
protected override void OnRender(DrawingContext dc)
{
// Draw four horizontal lines and one vertical line.
// Notice that even the point X or Y is not an integer, the line is still snapped to device.
dc.DrawSnappedLinesBetweenPoints(_pen, LineThickness,
new Point(0, 0), new Point(320, 0),
new Point(0, 40), new Point(320, 40),
new Point(0, 80.5), new Point(320, 80.5),
new Point(0, 119.7777), new Point(320, 119.7777),
new Point(0, 0), new Point(0, 120));
}
Here is the render result in a viewbox:

Why don't these animations work when I'm using a storyboard?

I've created a simple subclass of StackPanel that I can move around on the screen using an animated TranslateTransform. It looks like this:
public class MovingStackPanel : StackPanel
{
public void BeginMove(Point translatePosition)
{
RenderTransform = new TranslateTransform();
Duration d = new Duration(new TimeSpan(0, 0, 0, 0, 400));
DoubleAnimation x = new DoubleAnimation(translatePosition.X, d);
DoubleAnimation y = new DoubleAnimation(translatePosition.Y, d);
/*
Storyboard.SetTarget(x, RenderTransform);
Storyboard.SetTargetProperty(x, new PropertyPath("X"));
Storyboard.SetTarget(y, RenderTransform);
Storyboard.SetTargetProperty(y, new PropertyPath("Y"));
Storyboard sb = new Storyboard();
sb.Children.Add(x);
sb.Children.Add(y);
sb.Completed += sb_Completed;
sb.Begin();
*/
RenderTransform.BeginAnimation(TranslateTransform.XProperty, x);
RenderTransform.BeginAnimation(TranslateTransform.YProperty, y);
}
void sb_Completed(object sender, EventArgs e)
{
Console.WriteLine("Completed.");
}
}
And here is my problem: If I animate the X and Y properties directly, as the code above does, it works. But if I use the commented-out code above it, which is really the simplest creation of a Storyboard in code imaginable, nothing happens. The animation runs - at least, the Completed event gets raised - but nothing changes on the screen.
Clearly I'm doing something wrong, but I can't see what it is. Every example of creating storyboards in code I've seen looks just like this. There's obviously something about animations and storyboards that I don't know yet: what is it?
As it turns out, you can't use property path syntax in this case, because the properties being animated aren't properties of a FrameworkElement. At least, that's how I interpret the remarkably bewildering exception that I get when I make the change that Anvaka suggested:
Cannot automatically create animation clone for frozen property values on
'System.Windows.Media.TranslateTransform' objects. Only FrameworkElement and
FrameworkContentElement (or derived) types are supported.
To animate those, it seems, I have to use a NameScope and use SetTargetName to name the TransformElement. Then, as long as I pass the FrameworkElement that I set the name scope on to the Begin method, the storyboard can find the object and the properties and animate them and it all works. The end result looks like this:
public void BeginMove(Point translatePosition)
{
NameScope.SetNameScope(this, new NameScope());
RenderTransform = new TranslateTransform();
RegisterName("TranslateTransform", RenderTransform);
Duration d = new Duration(new TimeSpan(0, 0, 0, 0, 400));
DoubleAnimation x = new DoubleAnimation(translatePosition.X, d);
DoubleAnimation y = new DoubleAnimation(translatePosition.Y, d);
Storyboard.SetTargetName(x, "TranslateTransform");
Storyboard.SetTargetProperty(x, new PropertyPath(TranslateTransform.XProperty));
Storyboard.SetTargetName(y, "TranslateTransform");
Storyboard.SetTargetProperty(y, new PropertyPath(TranslateTransform.YProperty));
Storyboard sb = new Storyboard();
sb.Children.Add(x);
sb.Children.Add(y);
sb.Completed += sb_Completed;
// you must pass this to the Begin method, otherwise the timeline won't be
// able to find the named objects it's animating because it doesn't know
// what name scope to look in
sb.Begin(this);
}
It's property path syntax. The following approach works:
public void BeginMove(Point translatePosition)
{
RenderTransform = new TranslateTransform();
Duration d = new Duration(new TimeSpan(0, 0, 0, 0, 400));
DoubleAnimation x = new DoubleAnimation(translatePosition.X, d);
DoubleAnimation y = new DoubleAnimation(translatePosition.Y, d);
Storyboard.SetTarget(x, this);
Storyboard.SetTargetProperty(x,
new PropertyPath("(UIElement.RenderTransform).(TranslateTransform.X)"));
Storyboard.SetTarget(y, this);
Storyboard.SetTargetProperty(y,
new PropertyPath("(UIElement.RenderTransform).(TranslateTransform.Y)"));
Storyboard sb = new Storyboard();
sb.Children.Add(x);
sb.Children.Add(y);
sb.Completed += sb_Completed;
sb.Begin();
//RenderTransform.BeginAnimation(TranslateTransform.XProperty, x);
//RenderTransform.BeginAnimation(TranslateTransform.YProperty, y);
}

Performing a Flip animation completely through code WPF

I am try to add a flip animation to a user control I built. The user control is simple it has a 87x87 image front and back and some properties. It is suppose to represent a tile in a game I am working on for fun. I am trying to animate a flipping affect of the user picking the tile from the deck. I feel I need to do this through code instead of xaml for two reasons: 1. There is another transform after the tile is flip to rotate the tile (currently working) 2. After the tile is flipped I want to unhook the event.
The issue that I am getting is only the last animation runs after the method has exited.
I think I need a Storyboard but all the examples I looked at confused me in two ways:
How do I change the image mid story board, and what do I set the targetProperty to be
I have been working off these two blogs.
http://www.codeguru.com/csharp/csharp/cs_misc/userinterface/article.php/c12221
http://blogs.msdn.com/tess/archive/2009/03/16/silverlight-wpf-flipimage-animation.aspx
public void FlipFront()
{
DoubleAnimation flipfront = new DoubleAnimation(0, 90, new Duration(new TimeSpan(0, 0, 1)));
SkewTransform skew = new SkewTransform();
this.RenderTransform = skew;
skew.BeginAnimation(SkewTransform.AngleYProperty, flipfront);
}
public void FlipBack()
{
ImageSourceConverter source = new ImageSourceConverter();
this.ImageFace.Source = new BitmapImage(new Uri("Back.jpg", UriKind.Relative));
DoubleAnimation flipfront = new DoubleAnimation(90, 0, new Duration(new TimeSpan(0, 0, 1)));
SkewTransform skew = new SkewTransform();
this.RenderTransform = skew;
skew.BeginAnimation(SkewTransform.AngleYProperty, flipfront);
}
public void Flip()
{
FlipFront();
FlipBack();
}
I broke flip into two separate methods because I though it would help fix the issue I am experiencing.
Wow, this hasn't been updated in a loong time...just in case anybody's tracking this one:
The problem is you're not waiting for the "flip front" animation to complete before immediately starting the "flip back" - now since you're basically force-jumping the Y angle animation immediately to 90 degrees, that's why it looks like it's not firing properly.
There are a LOT of ways you can work around this - the first thing that jumps to mind is that the DoubleAnimations have a method on them called CreateClock, which will return you back an AnimationClock object. That object has a Completed event on it, which will tell you when that animation is "done". Attach a handler (remember you'll want to detach it lest you leak memory), and call your "start flipping to back" method there. I've thrown something very inefficient together, but it'll show the principle:
public AnimationClock StartFlipFrontAnimation()
{
this.ImageFace.Source = _frontFace;
DoubleAnimation flipfront = new DoubleAnimation(0, 90, new Duration(new TimeSpan(0, 0, 3)));
SkewTransform skew = new SkewTransform();
this.RenderTransform = skew;
skew.BeginAnimation(SkewTransform.AngleYProperty, flipfront);
return flipfront.CreateClock();
}
public AnimationClock StartFlipBackAnimation()
{
this.ImageFace.Source = _backFace;
DoubleAnimation flipfront = new DoubleAnimation(90, 0, new Duration(new TimeSpan(0, 0, 3)));
SkewTransform skew = new SkewTransform();
this.RenderTransform = skew;
skew.BeginAnimation(SkewTransform.AngleYProperty, flipfront);
return flipfront.CreateClock();
}
public void BeginFlip()
{
var frontClk = StartFlipFrontAnimation();
frontClk.Completed += FrontFlipDone;
}
private void FrontFlipDone(object sender, EventArgs args)
{
var clk = sender as AnimationClock;
if(clk != null)
{
clk.Completed -= FrontFlipDone;
}
var backClk = StartFlipBackAnimation();
}

Resources