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
Related
I have a WPF app with two canvases which overlay each other . . .
<Canvas Name="GeometryCnv" Canvas.Top="0" Canvas.Left="0" Margin="10,21,315,251" />
<Canvas Name="ROIcnv" Background ="Transparent" Canvas.Top="0" Canvas.Left="0" Margin="10,21,315,251" MouseDown="ROIcnvMouseDown" MouseUp="ROIcnvMouseUp" MouseMove="ROIcnvMouseMove"/>
I draw some geometry on the first canvas and I draw a rectangle to denote a Region on Interest (ROI) on the second one, using the Mouse-down event to start the drawing, Mouse-move events while drawing (resizing or positioning) the rectangle, and the Mouse-up event to end the drawing.
Except that it's not handling the events reliably. It gets the initial Mouse-down event to start it. It gets Mouse-move events continuously - regardless of whether the mouse is moving - and it does not get the Mouse-up event at all, nor does it get any subsequent mouse down events, say if I double-click the mouse.
The event-handler code looks like this . . .
private void ROIcnvMouseDown(object sender, MouseButtonEventArgs e)
{
MouseLineBegin = Mouse.GetPosition(ROIcnv);
bMouseDown = true;
}
private void ROIcnvMouseUp(object sender, MouseButtonEventArgs e)
{
MouseLineEnd = Mouse.GetPosition(ROIcnv);
bMouseDown = false;
}
private void ROIcnvMouseMove(object sender, MouseEventArgs e)
{
iMM++; // counting mouse move events
ROIcnv.Children.Clear(); // clear the ROI canvas
if (bMouseDown) // if we're drawing now
{
MouseLineEnd = Mouse.GetPosition(ROIcnv);
// get the upper left and lower right = coords from the beginning and end points . . .
int ulx = 0;
int uly = 0;
int lrx = 0;
int lry = 0;
if (MouseLineEnd.X >= MouseLineBegin.X)
{
ulx = (int) MouseLineBegin.X;
lrx = (int) MouseLineEnd.X;
}
else
{
lrx = (int)MouseLineBegin.X;
ulx = (int)MouseLineEnd.X;
}
if (MouseLineEnd.Y >= MouseLineBegin.Y)
{
uly = (int)MouseLineBegin.Y;
lry = (int)MouseLineEnd.Y;
}
else
{
lry = (int)MouseLineBegin.Y;
uly = (int)MouseLineEnd.Y;
}
int h = Math.Abs(lry-uly);
int w = Math.Abs(lrx-ulx);
var rect = new Path
{
Data = new RectangleGeometry(new Rect(ulx, uly, w, h)),
Stroke = Brushes.Black,
StrokeThickness = 2
};
ROIcnv.Children.Add(rect);
}
}
... I've tried suspending the mouse in mid-air and resting it on towels to eliminate any vibrations that might cause spurious move events with no benefit, any anyway that wouldn't account for not getting subsequent up and down events.
Note: I tried this on another computer with exactly the same results.
You'll have much better responses if you provide a minimal, working example of your problem (specifically both your ROIcnvMouseDown and ROIcnvMouseUp methods are missing as are all of your property declarations). The problem is possibly due to your newly-created Path object interfering with the mouse messages, if so then it can be fixed by setting it's IsHitTestVisible property to false. Need a minimal example to determine this for sure though.
UPDATE: Sorry, my bad, I must have stuffed up the cut-n-paste into my test app. Try capturing the mouse in response to the mouse down event:
private void ROIcnvMouseDown(object sender, MouseButtonEventArgs e)
{
MouseLineBegin = Mouse.GetPosition(ROIcnv);
bMouseDown = true;
Mouse.Capture(sender as IInputElement);
}
And of course you need to release it in response to MouseUp:
private void ROIcnvMouseUp(object sender, MouseButtonEventArgs e)
{
MouseLineEnd = Mouse.GetPosition(ROIcnv);
bMouseDown = false;
Mouse.Capture(sender as IInputElement, CaptureMode.None);
ROIcnv.Children.Clear();
}
The other thing I've done is call ROIcnv.Children.Clear(); in response to MouseUp as I assume you no longer want the selection rectangle to be visible. On my machine this doesn't result in any spurious mouse move events.
Does that answer the question?
I'd like to track the position of the Mouse cursor, in screen coordinates, anywhere on the screen. So even if the mouse cursor moves outside the bounds of the window, is there a way to get the position of the mouse cursor?
What I'm doing is trying to get a popup to follow the mouse cursor, even if it moves off the main window.
Here is a code snippet of what I've tried (and hasn't worked):
private void OnLoaded(object sender, RoutedEventArgs e)
{
bool gotcapture = this.CaptureMouse();
Mouse.AddLostMouseCaptureHandler(this, this.OnMouseLostCapture);
}
Point mouse_position_relative = Mouse.GetPosition(this);
Point mouse_screen_position = popup.PointToScreen(mouse_position_relative);
private void OnMouseLostCapture(object sender, MouseEventArgs e)
{
bool gotcapture = this.CaptureMouse();
this.textblock.Text = "lost capture.";
}
What exactly was your problem?
Wait! There is a way to position a Popup relative to the screen. see PlacementMode.AbsolutePoint
This showed little happy face flying around:
private Popup _popup;
public Window1()
{
InitializeComponent();
this.Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
_popup = new Popup
{
Child = new TextBlock {Text = "=))", Background = Brushes.White},
Placement = PlacementMode.AbsolutePoint,
StaysOpen = true,
IsOpen = true
};
MouseMove += MouseMoveMethod;
CaptureMouse();
}
private void MouseMoveMethod(object sender, MouseEventArgs e)
{
var relativePosition = e.GetPosition(this);
var point= PointToScreen(relativePosition);
_popup.HorizontalOffset = point.X;
_popup.VerticalOffset = point.Y;
}
Never mind, I realized there is no way to position a Popup relative to the screen, only relative to the Visual which contains it.
There are a number of ways to get the screen coordinates of the mouse position outside of a WPF Window. Unfortunately, you'll need to add references to use either of them, but it is possible. You can find examples of them both in #FredrikHedblad's answer to the How do I get the current mouse screen coordinates in WPF? question. Coincidentally, that question was answered a few days before you asked this question and gave up within 21 minutes of asking.
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);
}
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();
}
When trying to animate objects time/frame based in Silverlight (in contrast to using something like DoubleAnimation or Storyboard, which is not suitable e.g. for fast paced games), for example moving a spaceship in a particular direction every frame, the movement is jumpy and not really smooth. The screen even seems to tear.
There seems to be no difference between CompositionTarget and DistpatcherTimer.
I use the following approach (in pseudocode):
Register Handler to Tick-Event of a DispatcherTimer
In each Tick:
Compute the elapsed time from the last frame in milliseconds
Object.X += movementSpeed * ellapsedMilliseconds
This should result in a smooth movement, right? But it doesn't.
Here is an example (Controls: WASD and Mouse): Silverlight Game.
Although the effect I described is not too prevalent in this sample, I can assure you that even moving a single rectangle over a canvas produces a jumpy animation.
Does someone have an idea how to minimize this. Are there other approaches to to frame based animation exept using Storyboards/DoubleAnimations which could solve this?
Edit: Here a quick and dirty approach, animating a rectangle with minimum code (Controls: A and D) Animation Sample
Xaml:
<Grid x:Name="LayoutRoot" Background="Black">
<Canvas Width="1000" Height="400" Background="Blue">
<Rectangle x:Name="rect" Width="48" Height="48"
Fill="White"
Canvas.Top="200"
Canvas.Left="0"/>
</Canvas>
</Grid>
C#:
private bool isLeft = false;
private bool isRight = false;
private DispatcherTimer timer = new DispatcherTimer();
private double lastUpdate;
public Page()
{
InitializeComponent();
timer.Interval = TimeSpan.FromMilliseconds(1);
timer.Tick += OnTick;
lastUpdate = Environment.TickCount;
timer.Start();
}
private void OnTick(object sender, EventArgs e)
{
double diff = Environment.TickCount - lastUpdate;
double x = Canvas.GetLeft(rect);
if (isRight)
x += 1 * diff;
else if (isLeft)
x -= 1 * diff;
Canvas.SetLeft(rect, x);
lastUpdate = Environment.TickCount;
}
private void UserControl_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.D)
isRight = true;
if (e.Key == Key.A)
isLeft = true;
}
private void UserControl_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.D)
isRight = false;
if (e.Key == Key.A)
isLeft = false;
}
Thanks!
Andrej
Updating the position every 1 millisecond is overkill because silverlight will simply not update the screen that fast. When you modify the position of an object like that (or any other ui update) silverlight marks the framebuffer as dirty and then it will eventually redraw the screen, however it will not redraw the screen more often than the MaxFrameRate.
To set the MaxFrameRate just:
Application.Current.Host.Settings.MaxFrameRate = 60;
This will limit your application to 60 frames per second which should be more than enough.
Don't use a DispatchTimer for the animation. Reasons for this. Use a storyboard, if you can, or the CompositionTarget.Rendering event.