Clipping InkPresenter to drawing area - c#

I am trying to capture a signature in windows phone 7.1.
I can draw on the screen yet I can not limit the drawing area to the InkPresenter control except by adding some handling in the mousemove event.
How can I limit the drawing area using XAML or is this not possible?
XAML Code
<InkPresenter Name="inkTest" Background="White" MinHeight="180" MinWidth="250" />
Code Behind
private Stroke _currentStroke;
private void inkTest_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
_currentStroke = null;
}
private void inkTest_MouseMove(object sender, MouseEventArgs e)
{
if (_currentStroke == null) return;
//HACK: want to set this in XAML
var position = e.GetPosition(inkTest);
if (position.X <= inkTest.ActualWidth &&
position.Y <= inkTest.ActualHeight)
_currentStroke.StylusPoints.Add(GetStylusPoint(position));
}
private void inkTest_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
inkTest.CaptureMouse();
_currentStroke = new Stroke();
_currentStroke.StylusPoints.Add(GetStylusPoint(e.GetPosition(inkTest)));
_currentStroke.DrawingAttributes.Color = Colors.Blue;
inkTest.Strokes.Add(_currentStroke);
}
private StylusPoint GetStylusPoint(Point position)
{
return new StylusPoint(position.X, position.Y);
}

Untested, but try clipping:
<InkPresenter Name="inkTest" Background="White" MinHeight="180" MinWidth="250">
<InkPresenter.Clip>
<RectangleGeometry Rect="0,0,180,250"/>
</InkPresenter.Clip>
</InkPresenter>
Change the boundaries of the RectangleGeometry to what you want (or change the RectangleGeometry element itself if you need a different shape).

Related

How can I move a Picture with just "drag" the picture?

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;
}

How to enable both scrolling and zooming using pinch in WPF?

I am struggling with making both touch events and manipulation work properly in a WPF project. I have a ScrollViewer which contains a picture and I would like to scroll both horizontally and vertically using a swipe gestures. Additionally, I would like to zoom in/out in the center of the pinch gesture. The code below achieves what I wish, but it has the following problems:
Sometimes the scrolling is laggy;
The scrolling does not work on the first try, only when attempting the same gesture a second time;
The zoom in/out does not work on the first try, only when attempting the same gesture a second time.
I enabled the IsManipulationEnabled and I implemented the code for zoom in/out functionality. However, I was not able to combine it with the scrolling functionality (by setting the PanningMode in the ScrollViewer only). Therefore, I created a custom control which inherits from Image control and I overwritten the OnTouchDown and OnTouchUp event handlers. Basically, what I am doing in these overwritten handlers is counting the number of touches on the screen and enabling/disabling manipulation. I also tried setting the PanningMode for the ScrollViewer, but it did not do the trick.
Below is the XAML:
<Grid>
<ScrollViewer
x:Name="ScrollViewerParent"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
PanningMode="Both">
<local:CustomImage
x:Name="MainImage"
Source="{Binding Source={x:Static local:Constants.ImagePath}}"
IsManipulationEnabled="True"
ManipulationStarting="MainImage_ManipulationStarting"
ManipulationDelta="MainImage_ManipulationDelta">
</local:CustomImage>
</ScrollViewer>
</Grid>
Here is the code-behind:
public partial class MainWindow : Window
{
private void MainImage_ManipulationStarting(object sender, ManipulationStartingEventArgs e)
{
e.ManipulationContainer = ScrollViewerParent;
e.Handled = true;
}
private void MainImage_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
var matrix = MainImage.LayoutTransform.Value;
Point? centerOfPinch = (e.ManipulationContainer as FrameworkElement)?.TranslatePoint(e.ManipulationOrigin, ScrollViewerParent);
if (centerOfPinch == null)
{
return;
}
var deltaManipulation = e.DeltaManipulation;
matrix.ScaleAt(deltaManipulation.Scale.X, deltaManipulation.Scale.Y, centerOfPinch.Value.X, centerOfPinch.Value.Y);
MainImage.LayoutTransform = new MatrixTransform(matrix);
Point? originOfManipulation = (e.ManipulationContainer as FrameworkElement)?.TranslatePoint(e.ManipulationOrigin, MainImage);
double scrollViewerOffsetX = ScrollViewerParent.HorizontalOffset;
double scrollViewerOffsetY = ScrollViewerParent.VerticalOffset;
double pointMovedOnXOffset = originOfManipulation.Value.X - originOfManipulation.Value.X * deltaManipulation.Scale.X;
double pointMovedOnYOffset = originOfManipulation.Value.Y - originOfManipulation.Value.Y * deltaManipulation.Scale.Y;
double multiplicatorX = ScrollViewerParent.ExtentWidth / MainImage.ActualWidth;
double multiplicatorY = ScrollViewerParent.ExtentHeight / MainImage.ActualHeight;
ScrollViewerParent.ScrollToHorizontalOffset(scrollViewerOffsetX - pointMovedOnXOffset * multiplicatorX);
ScrollViewerParent.ScrollToVerticalOffset(scrollViewerOffsetY - pointMovedOnYOffset * multiplicatorY);
e.Handled = true;
}
}
The XAML for the custom control:
<Style TargetType="{x:Type local:CustomImage}" />
Here is where I override the OnTouchDown and OnTouchUp event handlers:
public class CustomImage : Image
{
private volatile int nrOfTouchPoints;
private volatile bool isManipulationReset;
private object mutex = new object();
static CustomImage()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomImage), new FrameworkPropertyMetadata(typeof(CustomImage)));
}
protected override void OnTouchDown(TouchEventArgs e)
{
lock (mutex)
{
nrOfTouchPoints++;
if (nrOfTouchPoints >= 2)
{
IsManipulationEnabled = true;
isManipulationReset = false;
}
}
base.OnTouchDown(e);
}
protected override void OnTouchUp(TouchEventArgs e)
{
lock (mutex)
{
if (!isManipulationReset)
{
IsManipulationEnabled = false;
isManipulationReset = true;
nrOfTouchPoints = 0;
}
}
base.OnTouchUp(e);
}
}
What I expect from this code is the following:
When using one finger to swipe horizontally or vertically across the touchscreen, the image should be scrolled accordingly;
When I use a pinch gesture on the touch screen, the image should be zoomed in/out in the center of the pinch.
Fortunately, I managed to find the perfect solution. Therefore, I am going to post the answer in the case that someone is working on a similar problem and needs some help.
What I did:
Got rid of the custom control as it was not necessary;
Create a field which counts the number of the touch points;
Implemented the TouchDown event handler, which increases the number of touch points by 1 (this method is called each time there is a touch down gesture on the device);
Implemented the TouchUp event handler, which decreases the number of touch points by 1 (this method is called each time there is a touch up gesture on the device);
In the Image_ManipulationDelta event handler, I check the number of touch points:
if the number of touch points < 2, then the translation value is added to the current offset of the scrollbars, thus achieving scrolling;
otherwise, the center of the pinch is calculated and a scale gesture is applied.
Here is the full XAML:
<Grid
x:Name="GridParent">
<ScrollViewer
x:Name="ScrollViewerParent"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
PanningMode="Both">
<Image
x:Name="MainImage"
Source="{Binding Source={x:Static local:Constants.ImagePath}}"
IsManipulationEnabled="True"
TouchDown="MainImage_TouchDown"
TouchUp="MainImage_TouchUp"
ManipulationDelta="Image_ManipulationDelta"
ManipulationStarting="Image_ManipulationStarting"/>
</ScrollViewer>
</Grid>
Here is the entire code discussed above:
public partial class MainWindow : Window
{
private volatile int nrOfTouchPoints;
private object mutex = new object();
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void Image_ManipulationStarting(object sender, ManipulationStartingEventArgs e)
{
e.ManipulationContainer = ScrollViewerParent;
e.Handled = true;
}
private void Image_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
int nrOfPoints = 0;
lock (mutex)
{
nrOfPoints = nrOfTouchPoints;
}
if (nrOfPoints >= 2)
{
DataLogger.LogActionDescription($"Executed {nameof(Image_ManipulationDelta)}");
var matrix = MainImage.LayoutTransform.Value;
Point? centerOfPinch = (e.ManipulationContainer as FrameworkElement)?.TranslatePoint(e.ManipulationOrigin, ScrollViewerParent);
if (centerOfPinch == null)
{
return;
}
var deltaManipulation = e.DeltaManipulation;
matrix.ScaleAt(deltaManipulation.Scale.X, deltaManipulation.Scale.Y, centerOfPinch.Value.X, centerOfPinch.Value.Y);
MainImage.LayoutTransform = new MatrixTransform(matrix);
Point? originOfManipulation = (e.ManipulationContainer as FrameworkElement)?.TranslatePoint(e.ManipulationOrigin, MainImage);
double scrollViewerOffsetX = ScrollViewerParent.HorizontalOffset;
double scrollViewerOffsetY = ScrollViewerParent.VerticalOffset;
double pointMovedOnXOffset = originOfManipulation.Value.X - originOfManipulation.Value.X * deltaManipulation.Scale.X;
double pointMovedOnYOffset = originOfManipulation.Value.Y - originOfManipulation.Value.Y * deltaManipulation.Scale.Y;
double multiplicatorX = ScrollViewerParent.ExtentWidth / MainImage.ActualWidth;
double multiplicatorY = ScrollViewerParent.ExtentHeight / MainImage.ActualHeight;
ScrollViewerParent.ScrollToHorizontalOffset(scrollViewerOffsetX - pointMovedOnXOffset * multiplicatorX);
ScrollViewerParent.ScrollToVerticalOffset(scrollViewerOffsetY - pointMovedOnYOffset * multiplicatorY);
e.Handled = true;
}
else
{
ScrollViewerParent.ScrollToHorizontalOffset(ScrollViewerParent.HorizontalOffset - e.DeltaManipulation.Translation.X);
ScrollViewerParent.ScrollToVerticalOffset(ScrollViewerParent.VerticalOffset - e.DeltaManipulation.Translation.Y);
}
}
private void MainImage_TouchDown(object sender, TouchEventArgs e)
{
lock (mutex)
{
nrOfTouchPoints++;
}
}
private void MainImage_TouchUp(object sender, TouchEventArgs e)
{
lock (mutex)
{
nrOfTouchPoints--;
}
}
}
}

Move an image over a canvas in windows store app

I need to move an image over a canvas on tap and slide. How can I achieve this. I tried the following. The image is moving but not as the user moves it.
XAML
<Canvas Background="White">
<Image Name="img" Width="200" Height="200" Source="Assets/11.png" ManipulationMode="All" ManipulationStarted="img_ManipulationStarted" ManipulationDelta="img_ManipulationDelta"/>
</Canvas>
C#
private Point initialPt;
private void img_ManipulationStarted(object sender, ManipulationStartedRoutedEventArgs e)
{
initialPt = e.Position;
}
private void img_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
Point currentPt = e.Position;
double x = currentPt.X - initialPt.X;
double y = currentPt.Y - initialPt.Y;
if (x != 0 || y != 0)
{
TranslateTransform posTransform = new TranslateTransform();
posTransform.X = currentPt.X;
posTransform.Y = currentPt.Y;
img.RenderTransform = posTransform;
e.Complete();
}
}
Instead of using a TranslateTransform, you should directly set the absolute position in the canvas, so you have to bind the ManipulationDelta event to the Canvas, and detect if the point of impact is inside of the image.
<Canvas Background="White" ManipulationMode="All" ManipulationDelta="canvas_ManipulationDelta">
<Image Name="img" Width="200" Height="200" Source="Assets/11.png"/>
</Canvas>
Here is the new event handling function:
private void canvas_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
if ((e.Position.X > Canvas.GetLeft(img) && e.Position.X < Canvas.GetLeft(img) + img.Width)
|| (e.Position.Y > Canvas.GetTop(img) && e.Position.Y < Canvas.GetTop(img) + img.Height)) {
{
Canvas.SetLeft(img, e.Position.X);
Canvas.SetTop(img, e.Position.Y);
}
}
Simple as pie. You can remove initialPt and img_ManipulationStarted.

Why does not MouseLeftButtonUp fire in WPF?

Why does not the MouseLeftButtonUp on my Canvas fire in my WPF app?
Here is the XAML:
<Grid Height="300" Width="400">
<Canvas Name="canvas" MouseMove="canvas_MouseMove" MouseLeftButtonUp="canvas_MouseLeftButtonUp" Background="LightGray"/>
</Grid>
And the code:
private bool hasClicked = false;
public Window1()
{
InitializeComponent();
}
private void canvas_MouseMove(object sender, MouseEventArgs e)
{
if (!this.hasClicked)
{
this.Cursor = Cursors.None;
this.canvas.Children.Clear();
this.insertRectangle(false);
}
}
private void insertRectangle(bool filled)
{
Rectangle rect = createRect(filled);
Point pos = Mouse.GetPosition(this.canvas);
Canvas.SetLeft(rect, pos.X);
Canvas.SetTop(rect, pos.Y);
this.canvas.Children.Add(rect);
}
private Rectangle createRect(bool fill)
{
Rectangle rect = new Rectangle();
rect.Height = 50;
rect.Width = 120;
if (fill)
{
rect.Fill = new SolidColorBrush(Colors.Green);
}
else
{
rect.Stroke = new SolidColorBrush(Colors.Green);
}
return rect;
}
private void canvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
this.hasClicked = true;
this.insertRectangle(true);
this.Cursor = Cursors.Arrow;
}
Edit: I have tried adding a background colour to the canvas, but still the event is not fired. It seems like the MouseMove somehow overrides the MouseLeftButtonUp.
Edit2: If I remove the MouseMove event, mouseLeftButtonUp will fire.
Edit3: Bigger code example.
In the insertRectangle method, if I use
Canvas.SetTop(rect, 50);
instead of
Canvas.SetTop(rect, pos.Y);
the events fires just fine.
If you don't set a Background on the canvas, it doesn't seem to pay attention to your mouse events.
Try:
<Grid>
<Canvas Name="canvas"
MouseMove="canvas_MouseMove"
MouseLeftButtonUp="canvas_MouseLeftButtonUp"
Background="White" />
</Grid>
By not clearing the canvas, but instead moving the preview rectangle in the mouseMove method solved the problem.

How to achieve smooth animated effect when dragging an object in WPF

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)));
}
}

Categories