I have a canvas inside a Scrollviewer.Now I need to limit the manipulations of canvas.It is going beyond the scrollviewer and not visible.
I have my XAML code like this:
<ScrollViewer x:Name="MyScrollView"
HorizontalScrollMode="Auto" VerticalScrollMode="Auto"
ZoomMode="Enabled" MinZoomFactor="1" ManipulationMode="All" VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto" Margin="0,0,0,0">
<canvas:CanvasControl x:Name="canvas" Height="560" Width="686"
Draw="Canvas_Draw" ManipulationMode="All" ManipulationDelta="Drag_ManipulationDelta">
</canvas:CanvasControl>
And my cs file is like this:
private CompositeTransform dragTranslation;
canvas.ManipulationDelta += Drag_ManipulationDelta;
dragTranslation = new CompositeTransform();
canvas.RenderTransform = this.dragTranslation;
void Drag_ManipulationDelta(object sender,
ManipulationDeltaRoutedEventArgs e)
{
// Move the rectangle.
if (dragTranslation != null)
{
dragTranslation.TranslateX += e.Delta.Translation.X;
dragTranslation.TranslateY += e.Delta.Translation.Y;
// dragTranslation.Rotation += e.Delta.Scale / Math.PI;
//dragTranslation.TranslateX += e.Delta.Translation.X;
//dragTranslation.TranslateY += e.Delta.Translation.Y;
}
}
I would be very thankful for any help.
In your ManipulationDelta handler you can check the results of the translation. Only commit it if the end point will be ok.
void Drag_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
// Move the rectangle.
if (dragTranslation != null)
{
float newX = dragTranslation.TranslateX + e.Delta.Translation.X;
float newY = dragTranslation.TranslateY + e.Delta.Translation.Y;
if (IsInBounds(newX,newY))
{
dragTranslation.TranslateX = newX;
dragTranslation.TranslateY = newY;
}
}
}
You can define IsInBounds appropriate to your scenario.
See my blog entry Constraining manipulations for more details and sample code.
Related
So I want to make a funtunality like in Windows when you display a Picture that when you zoom in you can just move the Picture with the mouse. I already got the zoom part in my project:
<ScrollViewer Name="scroll" Margin="40"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<Canvas
MouseWheel="Container_MouseWheel" x:Name="canvas">
<Viewbox>
<Image x:Name="img" Source="{Binding imageSource}" Grid.ColumnSpan="2" />
</Viewbox>
</Canvas>
</ScrollViewer>
and the Code in the MouseWheel Event:
var element = sender as UIElement;
var position = e.GetPosition(element);
var transform = element.RenderTransform as MatrixTransform;
var matrix = transform.Matrix;
var scale = e.Delta >= 0 ? 1.1 : (1.0 / 1.1); // choose appropriate scaling factor
matrix.ScaleAtPrepend(scale, scale, position.X, position.Y);
element.RenderTransform = new MatrixTransform(matrix);
but now Ive got the Problem that when I zoom I cant just click and hold the Image with the mouse and move to an another part of the picture, if you dont really know what I mean just open an Image on windows zoom in and then hold the mouse and move in the picture.
What I have tried was to add these 3 Events to my Canvas and this is the code behind for the 3 Events:
private Image draggedImage;
private Point mousePosition;
private void Canvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var image = e.Source as Image;
if (image != null && canvas.CaptureMouse())
{
mousePosition = e.GetPosition(canvas);
draggedImage = image;
Panel.SetZIndex(draggedImage, 1); // in case of multiple images
}
}
private void Canvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (draggedImage != null)
{
canvas.ReleaseMouseCapture();
Panel.SetZIndex(draggedImage, 0);
draggedImage = null;
}
}
private void Canvas_MouseMove(object sender, MouseEventArgs e)
{
if (draggedImage != null)
{
var position = e.GetPosition(canvas);
var offset = position - mousePosition;
mousePosition = position;
Canvas.SetLeft(draggedImage, Canvas.GetLeft(draggedImage) + offset.X);
Canvas.SetTop(draggedImage, Canvas.GetTop(draggedImage) + offset.Y);
}
}
but this didnt work saddly, does anyone has an Idea on how to make it work?
You should not mix layout (i.e. set Canvas.Left/Top) and render transformations. If you zoom by means of a RenderTransform, you should also pan by the same RenderTransform.
Here is a simple example where the MouseMove handler also manipulates the RenderTransform of the Image element.
<Canvas x:Name="canvas"
Background="Transparent"
MouseLeftButtonDown="CanvasMouseLeftButtonDown"
MouseLeftButtonUp="CanvasMouseLeftButtonUp"
MouseMove="CanvasMouseMove"
MouseWheel="CanvasMouseWheel">
<Image x:Name="image" Source=...>
<Image.RenderTransform>
<MatrixTransform/>
</Image.RenderTransform>
</Image>
</Canvas>
Note that mouse positions are determined differently for pan and zoom, in order to get an "unscaled" offset for panning, but an origin point relative to the Image for zooming.
private Point? mousePosition;
private void CanvasMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (canvas.CaptureMouse())
{
mousePosition = e.GetPosition(canvas); // position in Canvas
}
}
private void CanvasMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
canvas.ReleaseMouseCapture();
mousePosition = null;
}
private void CanvasMouseMove(object sender, MouseEventArgs e)
{
if (mousePosition.HasValue)
{
var position = e.GetPosition(canvas); // position in Canvas
var translation = position - mousePosition.Value;
mousePosition = position;
var transform = (MatrixTransform)image.RenderTransform;
var matrix = transform.Matrix;
matrix.Translate(translation.X, translation.Y);
transform.Matrix = matrix;
}
}
private void CanvasMouseWheel(object sender, MouseWheelEventArgs e)
{
var position = e.GetPosition(image); // position in Image
var scale = e.Delta >= 0 ? 1.1 : (1.0 / 1.1);
var transform = (MatrixTransform)image.RenderTransform;
var matrix = transform.Matrix;
matrix.ScaleAtPrepend(scale, scale, position.X, position.Y);
transform.Matrix = matrix;
}
I am very new to C# and WPF and would to create a WPF application that draws shapes with a button. The shapes then need to be able to move around the canvas. When I create a shape in the XAML it moves. However I cannot get the one created by the button to move. Could anyone please assist? Below are the XAML and code that i am using.
XAML:
<Window x:Class="All_test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Canvas x:Name="canvas" >
<Button Content="Button" Canvas.Left="250" Canvas.Top="260" Width="75" Click="Button_Click_1" />
<Rectangle x:Name="rect"
Height="100" Width ="100" Fill="red"
MouseLeftButtonDown="rect_MouseLeftButtonDown"
MouseLeftButtonUp="rect_MouseLeftButtonUp"
MouseMove="rect_MouseMove"
Canvas.Left="342" Canvas.Top="110" />
</Canvas>
This is the code I am using to move the red square drawn in XAML. How can I do the same for the green one created by the button?
public partial class MainWindow : Window
{
private bool _isRectDragInProg;
public MainWindow()
{
InitializeComponent();
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
Rectangle rect = new Rectangle();
rect.Fill = new SolidColorBrush(Colors.Green);
rect.Stroke = new SolidColorBrush(Colors.Black);
rect.Height = 100;
rect.Width = 100;
rect.StrokeThickness = 4;
canvas.Children.Add(rect);
InitializeComponent();
}
private void rect_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_isRectDragInProg = true;
rect.CaptureMouse();
}
private void rect_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
_isRectDragInProg = false;
rect.ReleaseMouseCapture();
}
private void rect_MouseMove(object sender, MouseEventArgs e)
{
if (!_isRectDragInProg) return;
// get the position of the mouse relative to the Canvas
var mousePos = e.GetPosition(canvas);
// center the rect on the mouse
double left = mousePos.X - (rect.ActualWidth / 2);
double top = mousePos.Y - (rect.ActualHeight / 2);
Canvas.SetLeft(rect, left);
Canvas.SetTop(rect, top);
}
You should bind the mouse events for this Rectangle:
private void Button_Click_1(object sender, RoutedEventArgs e)
{
Rectangle rect = new Rectangle();
rect.Fill = new SolidColorBrush(Colors.Green);
rect.Stroke = new SolidColorBrush(Colors.Black);
rect.Height = 100;
rect.Width = 100;
rect.StrokeThickness = 4;
// here
rect.MouseLeftButtonDown += rect_MouseLeftButtonDown;
rect.MouseLeftButtonUp += rect_MouseLeftButtonUp;
rect.MouseMove += rect_MouseMove;
canvas.Children.Add(rect);
// InitializeComponent(); <--- lose the InitializeComponent here, that's should only be called ones.. (in the constructor)
}
Recommendations:
Use the sender parameter to get the current Rectangle.
You can lose the _isRectDragInProg boolean... use the IsMouseCaptured property instead.
For example:
private void rect_MouseMove(object sender, MouseEventArgs e)
{
var rect = (Rectangle)sender;
if (!rect.IsMouseCaptured) return;
// get the position of the mouse relative to the Canvas
var mousePos = e.GetPosition(canvas);
// center the rect on the mouse
double left = mousePos.X - (rect.ActualWidth / 2);
double top = mousePos.Y - (rect.ActualHeight / 2);
Canvas.SetLeft(rect, left);
Canvas.SetTop(rect, top);
}
How do I get the pinch-to-zoom x and y scaling values independent of each other for a Windows Store App? I'm currently using ManipulationDeltaRoutedEventArgs's ManipulationDelta structure, but as you can see it only offers a single scale.
// Global Transform used to change the position of the Rectangle.
private TranslateTransform dragTranslation;
private ScaleTransform scaleTransform;
// Constructor
public MainPage()
{
InitializeComponent();
// Add handler for the ManipulationDelta event
TestRectangle.ManipulationDelta += Drag_ManipulationDelta;
dragTranslation = new TranslateTransform();
scaleTransform = new ScaleTransform();
TestRectangle.RenderTransform = this.dragTranslation;
}
void Drag_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
// Move the rectangle.
dragTranslation.X += e.Delta.Translation.X;
dragTranslation.Y += e.Delta.Translation.Y;
// Scaling, but I want X and Y independent!
scaleTransform.ScaleX = e.Delta.Scale;
scaleTransform.ScaleY = e.Delta.Scale;
}
XAML:
<Rectangle Name="TestRectangle"
Width="200" Height="200" Fill="Blue"
ManipulationMode="All"/>
Code mostly taken from here.
I ended up using Handling Two, Three, Four Fingers Swipe Gestures in WinRT App to grab the coordinates of the two fingers, calculated the initial difference between them, and then scaled accordingly as the distance changed.
int numActiveContacts;
Dictionary<uint, int> contacts;
List<PointF> locationsOfSortedTouches;
void myCanvas_PointerPressed(object sender, PointerRoutedEventArgs e) {
PointerPoint pt = e.GetCurrentPoint(myCanvas);
locationsOfSortedTouches.Add(new PointF((float) pt.Position.X, (float) pt.Position.Y));
touchHandler.TouchesBegan(locationsOfSortedTouches);
contacts[pt.PointerId] = numActiveContacts;
++numActiveContacts;
e.Handled = true;
}
void myCanvas_PointerMoved(object sender, PointerRoutedEventArgs e) {
var pt = e.GetCurrentPoint(myCanvas);
var ptrId = pt.PointerId;
if (contacts.ContainsKey(ptrId)) {
var ptrOrdinal = contacts[ptrId];
Windows.Foundation.Point currentContact = pt.Position;
locationsOfSortedTouches[ptrOrdinal] = new PointF((float) pt.Position.X, (float) pt.Position.Y);
//distance calculation and zoom redraw here
}
e.Handled = true;
}
i can successfully move a line around the window in wpf but it's not working properly.
it's like if the cursor is way fast that the line it's dragging.
you can test the code to see if you can find what's the problem on it.
public partial class MainWindow : Window
{
Line line = new Line();
Point p ;
bool isdragging = false;
public MainWindow()
{
InitializeComponent();
canvas1.Children.Add(line);
Thickness thickness = new Thickness(101, -11, 362, 250);
line.Margin = thickness;
line.Visibility = System.Windows.Visibility.Visible;
line.StrokeThickness = 4;
line.Stroke = System.Windows.Media.Brushes.Black;
line.X1 = 10;
line.X2 = 200;
line.Y1 = 0;
line.Y2 = 70;
line.MouseDown+=new MouseButtonEventHandler(line_MouseDown);
line.MouseMove+=new MouseEventHandler(line_MouseMove);
line.MouseUp+=new MouseButtonEventHandler(line_MouseUp);
}
public void line_MouseDown(object sender, MouseButtonEventArgs e)
{
isdragging = true;
p = e.GetPosition(canvas1);
}
public void line_MouseMove(object sender, MouseEventArgs e)
{
if (isdragging == true && e.LeftButton == MouseButtonState.Pressed)
{
line.X1 += e.GetPosition(canvas1).X - p.X;
line.X2 += e.GetPosition(canvas1).X - p.X;
line.Y1 += e.GetPosition(canvas1).Y - p.Y;
line.Y2 += e.GetPosition(canvas1).Y - p.Y;
}
}
public void line_MouseUp(object sender, MouseButtonEventArgs e)
{
isdragging = false;
}
}
}
If you want really smooth dragging you could wrap the Line in a Thumb so you can use the DragDelta event to calculate the new position.
Example:
Xaml:
<Window x:Class="WpfApplication8.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="233" Width="405" Name="UI">
<Canvas>
<Thumb DragDelta="onDragDelta" Canvas.Left="0" Canvas.Top="0" >
<Thumb.Template>
<ControlTemplate>
<Line X1="10" X2="200" Y1="0" Y2="70" StrokeThickness="4" Stroke="Black"/>
</ControlTemplate>
</Thumb.Template>
</Thumb>
</Canvas>
</Window>
Code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
void onDragDelta(object sender, DragDeltaEventArgs e)
{
var thumb = sender as Thumb;
Canvas.SetLeft(thumb, Canvas.GetLeft(thumb) + e.HorizontalChange);
Canvas.SetTop(thumb, Canvas.GetTop(thumb) + e.VerticalChange);
}
}
You are moving the line after every mouse move event. This will cause the code to attempt to redraw the line in it's new location after every slightest move of the mouse - even down to a single pixel.
You could keep a track of the position and only redraw the line if the mouse has moved more than a certain amount:
public void line_MouseMove(object sender, MouseEventArgs e)
{
if (isdragging == true && e.LeftButton == MouseButtonState.Pressed)
{
Point newPos = e.GetPosition(canvas1);
if (Size(p, newPos) > 10)
{
line.X1 += newPos.X - p.X;
line.X2 += newPos.X - p.X;
line.Y1 += newPos.Y - p.Y;
line.Y2 += newPos.Y - p.Y;
p = newPos;
}
}
}
Where Size is a method that calculates the distance from p to newPos.
Then you just need to add a final update when the mouse is released so that the line ends up where the user expects it to be:
public void line_MouseUp(object sender, MouseButtonEventArgs e)
{
Point newPos = e.GetPosition(canvas1);
line.X1 += newPos.X - p.X;
line.X2 += newPos.X - p.X;
line.Y1 += newPos.Y - p.Y;
line.Y2 += newPos.Y - p.Y;
isdragging = false;
}
Thank you guys for you help!!
i think i solved that problem buy capturing the mouse when i'm m and i release the mouse capture after releasing the button of the mouse.
it works just fine.
thank you again guys!
I am trying to learn some WPF and I was hoping to be able to implement a simple game. In this game, there are a few items on a Canvas. For the purpose of this question, let’s say there’s just one, and it’s an Ellipse:
<Canvas Name="canvas">
<Ellipse Name="ellipse" Width="100" Height="100" Stroke="Black" StrokeThickness="3" Fill="GreenYellow"/>
</Canvas>
The user needs to be able to drag these items around arbitrarily.
So I implemented the following code and it seems to work:
public MainWindow()
{
InitializeComponent();
Canvas.SetLeft(ellipse, 0);
Canvas.SetTop(ellipse, 0);
ellipse.MouseDown += new MouseButtonEventHandler(ellipse_MouseDown);
ellipse.MouseMove += new MouseEventHandler(ellipse_MouseMove);
ellipse.MouseUp += new MouseButtonEventHandler(ellipse_MouseUp);
}
void ellipse_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.LeftButton != MouseButtonState.Pressed)
return;
ellipse.CaptureMouse();
ellipse.RenderTransform = new ScaleTransform(1.25, 1.25, ellipse.Width / 2, ellipse.Height / 2);
ellipse.Opacity = 0.75;
}
void ellipse_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton != MouseButtonState.Pressed || !ellipse.IsMouseCaptured)
return;
var pos = e.GetPosition(canvas);
Canvas.SetLeft(ellipse, pos.X - ellipse.Width * 0.5);
Canvas.SetTop(ellipse, pos.Y - ellipse.Height * 0.5);
}
void ellipse_MouseUp(object sender, MouseButtonEventArgs e)
{
if (!ellipse.IsMouseCaptured)
return;
ellipse.ReleaseMouseCapture();
ellipse.RenderTransform = null;
ellipse.Opacity = 1;
}
Now, if you try this, you’ll see that the movements are very jagged. When you mouse down, the ellipse grows instantly and changes its transparency instantly. I want to smoothen this out so that there are no sudden jumps.
I’ve tried the obvious things using a DoubleAnimation on Ellipse.OpacityProperty, ScaleTransform.ScaleXProperty and (notably) Canvas.LeftProperty/TopProperty. However, I run into the following problems:
As soon as I begin an animation on Canvas.LeftProperty/TopProperty, I can never use Canvas.SetLeft/Top again, so the ellipse doesn’t move when it is dragged. I couldn’t find a way to remove the animation from the object.
If the user releases the mouse while the animation is still happening, the “shrinking” animation on the ScaleTransform starts from the full size before the “growing” animation has reached it, which causes a sudden jump. If you click the mouse frantically, the object’s size jumps frantically, which it shouldn’t.
If you need to, you can look at my failed code, which doesn’t work.
How do you implement these smooth motions properly in WPF?
Please do not post an answer without trying it out first. If there are any sudden jumps, the result is unsatisfactory. Thanks!
Instead of creating a new ScaleTransform for each change, use the same one and keep applying new animations. If you don't specify a From property for the animation, it will start with the current value and do a smooth animation.
To avoid the location skip, remember the position of the mouse within the ellipse instead of always centering it. That way you won't need to worry about recentering it. (You can call BeginAnimation with a null timeline to stop the current animation, but then you'll just get a jump on the first MouseMove.)
In XAML:
<Ellipse Name="ellipse" Width="100" Height="100"
Stroke="Black" StrokeThickness="3" Fill="GreenYellow">
<Ellipse.RenderTransform>
<ScaleTransform x:Name="scale" CenterX="50" CenterY="50"/>
</Ellipse.RenderTransform>
</Ellipse>
In code:
private Point offsetInEllipse;
void ellipse_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.LeftButton != MouseButtonState.Pressed)
return;
ellipse.CaptureMouse();
offsetInEllipse = e.GetPosition(ellipse);
var scaleAnimate = new DoubleAnimation(1.25,
new Duration(TimeSpan.FromSeconds(1)));
scale.BeginAnimation(ScaleTransform.ScaleXProperty, scaleAnimate);
scale.BeginAnimation(ScaleTransform.ScaleYProperty, scaleAnimate);
}
void ellipse_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton != MouseButtonState.Pressed || !ellipse.IsMouseCaptured)
return;
var pos = e.GetPosition(canvas);
Canvas.SetLeft(ellipse, pos.X - offsetInEllipse.X);
Canvas.SetTop(ellipse, pos.Y - offsetInEllipse.Y);
}
void ellipse_MouseUp(object sender, MouseButtonEventArgs e)
{
if (!ellipse.IsMouseCaptured)
return;
ellipse.ReleaseMouseCapture();
var scaleAnimate = new DoubleAnimation(1,
new Duration(TimeSpan.FromSeconds(1)));
scale.BeginAnimation(ScaleTransform.ScaleXProperty, scaleAnimate);
scale.BeginAnimation(ScaleTransform.ScaleYProperty, scaleAnimate);
}
how do I return Canvas.SetLeft to
normal operation after having an
animation on the Canvas.LeftProperty?
One way is to set the FillBehavior to Stop:
ellipse.BeginAnimation(Canvas.LeftProperty, new DoubleAnimation(
pos.X - ellipse.Width * 0.5,
new Duration(TimeSpan.FromSeconds(1)),
FillBehavior.Stop));
Canvas.SetLeft(ellipse, pos.X - ellipse.Width * 0.5);
That will cause the property to go back to its un-animated value after the animation ends. If you set the value after you start the animation then the un-animated value will just be the final value.
Another way is to clear the animation when you're done:
ellipse.BeginAnimation(Canvas.LeftProperty, null);
Either of those will still cause it to jump when you drag, though. You could have the drag start a new animation every time, but that will make the dragging feel very laggy. Maybe you want to handle the dragging using Canvas.Left, but handle the smooth centering using an animated TranslateTransform?
XAML:
<Ellipse.RenderTransform>
<TransformGroup>
<ScaleTransform x:Name="scale" CenterX="50" CenterY="50"/>
<TranslateTransform x:Name="translate"/>
</TransformGroup>
</Ellipse.RenderTransform>
Code:
void ellipse_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.LeftButton != MouseButtonState.Pressed)
return;
ellipse.CaptureMouse();
var scaleAnimate = new DoubleAnimation(1.25,
new Duration(TimeSpan.FromSeconds(1)));
scale.BeginAnimation(ScaleTransform.ScaleXProperty, scaleAnimate);
scale.BeginAnimation(ScaleTransform.ScaleYProperty, scaleAnimate);
// We are going to move the center of the ellipse to the mouse
// location immediately, so start the animation with a shift to
// get it back to the current center and end the animation at 0.
var offsetInEllipse = e.GetPosition(ellipse);
translate.BeginAnimation(TranslateTransform.XProperty,
new DoubleAnimation(ellipse.Width / 2 - offsetInEllipse.X, 0,
new Duration(TimeSpan.FromSeconds(1))));
translate.BeginAnimation(TranslateTransform.YProperty,
new DoubleAnimation(ellipse.Height / 2 - offsetInEllipse.Y, 0,
new Duration(TimeSpan.FromSeconds(1))));
MoveEllipse(e);
}
void ellipse_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton != MouseButtonState.Pressed || !ellipse.IsMouseCaptured)
return;
MoveEllipse(e);
}
private void MoveEllipse(MouseEventArgs e)
{
var pos = e.GetPosition(canvas);
Canvas.SetLeft(ellipse, pos.X - ellipse.Width / 2);
Canvas.SetTop(ellipse, pos.Y - ellipse.Height / 2);
}
You probably should look into the Thumb control.
Here is a nice CodeProject using it.
You could get some animations with effects and it would of soften the initial drag effect.... I am afraid the drag animation may not be smoothed out as WPF wasnt just made for this kinds of things, i think you should go for XNA instead :p
This video may suit your needs:
http://windowsclient.net/learn/video.aspx?v=280279
As Quartermeister already mentioned you should not specify from value for the animations. This way animation will start with current value and will combine with currently executing animations. Also you should not re-create transformations every time.
Besides that I suggest that you use TranslateTransform instead of setting Top/Left properties of Canvas. It gives you move flexibility and you are not tied to Canvas panel.
So, here is what I got:
XAML:
<Canvas Name="canvas">
<Ellipse Name="ellipse" Width="100" Height="100" Stroke="Black" StrokeThickness="3" Fill="GreenYellow"
RenderTransformOrigin="0.5,0.5">
<Ellipse.RenderTransform>
<TransformGroup>
<ScaleTransform />
<TranslateTransform />
</TransformGroup>
</Ellipse.RenderTransform>
</Ellipse>
</Canvas>
Code-behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Canvas.SetLeft(ellipse, 0);
Canvas.SetTop(ellipse, 0);
ellipse.MouseDown += new MouseButtonEventHandler(ellipse_MouseDown);
ellipse.MouseMove += new MouseEventHandler(ellipse_MouseMove);
ellipse.MouseUp += new MouseButtonEventHandler(ellipse_MouseUp);
}
private ScaleTransform EllipseScaleTransform
{
get { return (ScaleTransform)((TransformGroup)ellipse.RenderTransform).Children[0]; }
}
private TranslateTransform EllipseTranslateTransform
{
get { return (TranslateTransform)((TransformGroup)ellipse.RenderTransform).Children[1]; }
}
void ellipse_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.LeftButton != MouseButtonState.Pressed)
return;
ellipse.CaptureMouse();
var pos = e.GetPosition(canvas);
AnimateScaleTo(1.25);
}
void ellipse_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton != MouseButtonState.Pressed || !ellipse.IsMouseCaptured)
return;
var pos = e.GetPosition(canvas);
AnimatePositionTo(pos);
}
void ellipse_MouseUp(object sender, MouseButtonEventArgs e)
{
if (!ellipse.IsMouseCaptured)
return;
ellipse.ReleaseMouseCapture();
AnimateScaleTo(1);
}
private void AnimateScaleTo(double scale)
{
var animationDuration = TimeSpan.FromSeconds(1);
var scaleAnimate = new DoubleAnimation(scale, new Duration(animationDuration));
EllipseScaleTransform.BeginAnimation(ScaleTransform.ScaleXProperty, scaleAnimate);
EllipseScaleTransform.BeginAnimation(ScaleTransform.ScaleYProperty, scaleAnimate);
}
private void AnimatePositionTo(Point pos)
{
var xOffset = pos.X - ellipse.Width * 0.5;
var yOffset = pos.Y - ellipse.Height * 0.5;
var animationDuration = TimeSpan.FromSeconds(1);
EllipseTranslateTransform.BeginAnimation(TranslateTransform.XProperty,
new DoubleAnimation(xOffset, new Duration(animationDuration)));
EllipseTranslateTransform.BeginAnimation(TranslateTransform.YProperty,
new DoubleAnimation(yOffset, new Duration(animationDuration)));
}
}