How do I only allow dragging in a circular path? - wpf

Is it possible to restrict the drag source to only move within the boundaries of a circular path when dragging it?

You don't need the 360-point path. Instead, as you are dragging, compute the current angle using Math.Atan2(Y,X), and then generate the point on the circle. You would still need to compute center and radius on resize and store them, or compute them inside MouseMove.
private void UserControl_MouseMove(object sender, MouseEventArgs e)
{
if (!isDraggingMarker)
return;
var position = e.GetPosition(this);
double angle = Math.Atan2(position.Y - center.Y, position.X - center.X);
var closest = new Point(center.X + radius*Math.Cos(angle),
center.Y + radius*Math.Sin(angle));
SetMarkerPosition(closest);
}

Create a circle of points and then when the mouse moves (and we are dragging) calculate the nearest point and snap to that point.
CircularDrag.xaml
<UserControl x:Class="DraggingBoundaries.CircularDrag"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
SizeChanged="UserControl_SizeChanged"
MouseMove="UserControl_MouseMove"
MouseLeave="UserControl_MouseLeave"
MouseLeftButtonUp="UserControl_MouseLeftButtonUp"
>
<Grid Background="White">
<Border
x:Name="Marker"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="14"
Height="14"
Background="CornflowerBlue"
CornerRadius="2"
BorderThickness="1"
BorderBrush="DarkGray"
MouseLeftButtonDown="Marker_MouseLeftButtonDown"
/>
</Grid>
</UserControl>
CircularDrag.xaml.cs
using System;
using System.Linq;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace DraggingBoundaries
{
public partial class CircularDrag : UserControl
{
List<Point> allowedWheelMarkerPositions;
bool isDraggingMarker;
public CircularDrag()
{
InitializeComponent();
}
private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)
{
var center = new Point(e.NewSize.Width / 2, e.NewSize.Height / 2);
var radius = (center.X < center.Y ? center.X : center.Y) - 15;
allowedWheelMarkerPositions = CreateCirclePath(center, radius);
SetMarkerPosition(allowedWheelMarkerPositions.First());
}
private List<Point> CreateCirclePath(Point center, double radius)
{
var result = new List<Point>();
for (double angle = 0; angle <= 360; angle++)
{
double angleR = angle * (Math.PI / 180);
double x = center.X + Math.Cos(angleR) * radius;
double y = center.Y - Math.Sin(angleR) * radius;
result.Add(new Point(x, y));
}
return result;
}
private void UserControl_MouseMove(object sender, MouseEventArgs e)
{
if (!isDraggingMarker)
return;
var position = e.GetPosition(this);
var closest = allowedWheelMarkerPositions
.OrderBy(p => GetDistance(position, p))
.First();
SetMarkerPosition(closest);
}
private void SetMarkerPosition(Point closest)
{
Marker.Margin = new Thickness(closest.X - Marker.Width / 2, closest.Y - Marker.Height / 2, 0, 0);
}
private double GetDistance(Point a, Point b)
{
var deltaX = a.X - b.X;
var deltaY = a.Y - b.Y;
return Math.Sqrt(Math.Pow(deltaX, 2) + Math.Pow(deltaY, 2));
}
private void Marker_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
isDraggingMarker = true;
}
private void UserControl_MouseLeave(object sender, MouseEventArgs e)
{
isDraggingMarker = false;
}
private void UserControl_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
isDraggingMarker = false;
}
}
}

Related

How can I draw lines in WPF inkCanvas with sequence of PNGs

I was trying to make a method to draw some arrowheads from a PNG image with transparency.
While I moving mouse, the application will be plot that png along the path.
What is the way to make that?
Another form I was wondering is creating a polygnon shape. I was tryed draw the shape (triangle) when enter on mouse down event.
void MainWindow_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
double posX = e.GetPosition(null).X;
double posY = e.GetPosition(null).Y;
double posX1 = posX - (posX / 4);
double posY1 = posY - (posY / 4);
double posX2 = posX + (posX / 4);
double posY2 = posY;
double posX3 = posX - (posX / 4);
double posY3 = posY + (posY - posY / 3);
Polygon p = new Polygon();
p.Stroke = Brushes.Black;
p.Fill = Brushes.LightBlue;
p.StrokeThickness = 1;
p.HorizontalAlignment = HorizontalAlignment.Left;
p.VerticalAlignment = VerticalAlignment.Center;
p.Points = new PointCollection() { new Point(posX1, posY1), new Point(posX2, posY2), new Point(posX3, posY3), new Point(posX1, posY1) };
MyCanvas.Children.Add(p);
}
But still can't click and draw a triangle correctly in mouse (x,y) position.
http://i.stack.imgur.com/bM2i4.png
Here is a quick example.
XAML
<Canvas x:Name='DrawingCanvas'
Margin='30'
Background='LightBlue'
MouseDown='Canvas_MouseDown'
MouseMove='Canvas_MouseMove'
MouseUp='Canvas_MouseUp'>
<Rectangle>
<Rectangle.Fill>
<ImageBrush ImageSource='Arrow.png' x:Name='ArrowBrush'/>
</Rectangle.Fill>
</Rectangle>
</Canvas>
CODE
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
}
private bool _isDrawing = false;
private Point _lastPosition;
const double MIN_MOVEMENT = 60;
private void Canvas_MouseDown(object sender, MouseButtonEventArgs e) {
_isDrawing = true;
_lastPosition = e.GetPosition(DrawingCanvas);
// add the first arrow
AddArrow(_lastPosition);
}
private void Canvas_MouseMove(object sender, MouseEventArgs e) {
if (!_isDrawing)
{
return;
}
var currentPosition = e.GetPosition(DrawingCanvas);
// draw a new image if mouse has traveled minimum distance
if (Math.Abs((currentPosition.X - _lastPosition.X)) > MIN_MOVEMENT ||
Math.Abs((currentPosition.Y - _lastPosition.Y)) > MIN_MOVEMENT)
{
AddArrow(currentPosition);
_lastPosition = e.GetPosition(DrawingCanvas);
}
}
private void AddArrow(Point currentPosition) {
var rect = new Rectangle();
rect.Width = 120;
rect.Height = 120;
rect.Fill = ArrowBrush;
Canvas.SetTop(rect, currentPosition.Y);
Canvas.SetLeft(rect, currentPosition.X);
DrawingCanvas.Children.Add(rect);
}
private void Canvas_MouseUp(object sender, MouseButtonEventArgs e) {
_isDrawing = true;
}
}
Screenshot

Strange act of canvas when Scale/ Zoom in Silverlight

I'm working on Zooming with Silverlight 5,
My Idea is to zoom according to mouse position on the Canvas, and the ability to Drag that Canvas,
The problem I have is when scale is less than 1, about 0.6 or 0.5, point to the corner of the canvas and wheel up, the canvas will change its position or "jump", any help please?
these two photos describe the status before Then after:
I have the following XAML:
<Grid x:Name="LayoutRoot" Background="White">
<ScrollViewer x:Name="sv" Margin="0,0,0,76" ScrollViewer.VerticalScrollBarVisibility="Disabled" Background="#FFE3E7F1">
<Canvas x:Name="grd" Height="394" Width="630">
<Canvas x:Name="cvs" Background="White" MouseWheel="cvs_MouseWheel" MouseLeftButtonDown="cvs_MouseLeftButtonDown" MouseLeftButtonUp="cvs_MouseLeftButtonUp" MouseMove="cvs_MouseMove" Height="391" Canvas.Left="2" Canvas.Top="1" Width="625">
<Canvas.Effect>
<DropShadowEffect ShadowDepth="0"/>
</Canvas.Effect>
<Rectangle Height="70" Canvas.Left="155" Canvas.Top="58" Width="79" Fill="#FFFFBFBF"/>
<Rectangle Height="70" Canvas.Left="544" Canvas.Top="126" Width="79" Fill="#FF8B92FF"/>
</Canvas>
</Canvas>
</ScrollViewer>
</Grid>
and here's the C#:
public partial class MainPage : UserControl
{
CompositeTransform canvasTransform = new CompositeTransform();
bool canDragCanvas;
double mouseRelatedPositionX = 0;
double mouseRelatedPositionY = 0;
public MainPage()
{
// Required to initialize variables
InitializeComponent();
}
private void cvs_MouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
{
var scaleFactor = 0.2*(e.Delta < 0?-1:1);
var centerX = e.GetPosition(cvs).X;
var centerY = e.GetPosition(cvs).Y;
if (centerX > cvs.ActualWidth * canvasTransform.ScaleX || centerX < 0 || centerY > cvs.ActualHeight * canvasTransform.ScaleY || centerY < 0)
{
centerX = cvs.ActualWidth/2;
centerY = cvs.ActualHeight/2;
}
canvasTransform.CenterX = centerX;
canvasTransform.CenterY = centerY;
canvasTransform.ScaleX += scaleFactor;
canvasTransform.ScaleY += scaleFactor;
cvs.RenderTransform = canvasTransform;
}
private void cvs_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
canDragCanvas = true;
mouseRelatedPositionX = e.GetPosition(cvs).X;
mouseRelatedPositionY = e.GetPosition(cvs).Y;
}
private void cvs_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
canDragCanvas = false;
}
private void cvs_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
if(!canDragCanvas) return;
var leftValueToAdd = e.GetPosition(cvs).X - mouseRelatedPositionX;
var topValueToAdd = e.GetPosition(cvs).Y - mouseRelatedPositionY;
UpdateCanvasPosition(leftValueToAdd*canvasTransform.ScaleX, topValueToAdd*canvasTransform.ScaleX);
}
void UpdateCanvasPosition(double leftValueToAdd,double topValueToAdd)
{
var leftOffset = canvasTransform.CenterX - canvasTransform.CenterX * canvasTransform.ScaleX;
var rightOffset = (cvs.ActualWidth - canvasTransform.CenterX) - (cvs.ActualWidth - canvasTransform.CenterX) * canvasTransform.ScaleX;
var topOffset = canvasTransform.CenterY - canvasTransform.CenterY * canvasTransform.ScaleY;
var bottomOffset = (cvs.ActualHeight - canvasTransform.CenterY) - (cvs.ActualHeight - canvasTransform.CenterY) * canvasTransform.ScaleY;
var canvasLeftInBorders = Canvas.GetLeft(cvs)+ leftValueToAdd + leftOffset > 0;
var canvasRightInBorders = Canvas.GetLeft(cvs) + cvs.ActualWidth * canvasTransform.ScaleX + leftValueToAdd + leftOffset < grd.ActualWidth;
var canvasTopInBorders = Canvas.GetTop(cvs) + topValueToAdd + topOffset > 0;
var canvasBottomInBorders = Canvas.GetTop(cvs) + cvs.ActualHeight * canvasTransform.ScaleY + topValueToAdd + topOffset < grd.ActualHeight;
if (leftValueToAdd > 0)
{
if (canvasLeftInBorders)
leftValueToAdd = 0;
}
else if (leftValueToAdd < 0)
if (canvasRightInBorders)
leftValueToAdd = 0;
if (topValueToAdd > 0)
{
if (canvasTopInBorders)
topValueToAdd = 0;
}
else if (topValueToAdd < 0)
if (canvasBottomInBorders)
topValueToAdd = 0;
Canvas.SetLeft(cvs, Canvas.GetLeft(cvs) + leftValueToAdd);
Canvas.SetTop(cvs,Canvas.GetTop(cvs)+topValueToAdd);
}
}
Basically you are attaching mouse events to the surface that is zooming and the mouse coordinates get altered too. Silverlight is designed to still be interactive when you rotate, zoom and tilt.
You want to put a transparent layer over the top, that is not zoomed, and attached your mouse methods that that layer.
If you leave that layer turned on you will have to calculate collisions, but you can make it so that the layer only appears on mouse down and goes away on mouse up.

Make Object Follow Mouse On MouseDown and "Stick" On MouseUp

I'm working with a project that is WPF and VB.net. I want to visually simulate "dragging" an object (though I do not want to use standard drag and drop for reason of purpose).
Basically, I have a label object that, on its MouseDown event, I want it to follow the mouse cursor inside a 640x480 solid-size grid (but not outside of it!). Mind you, this grid is centered inside a full-screen window. Again, the object should not follow the mouse outside of the grid (I'm guessing a "ClipToBounds = True" here)
Then, on the label's MouseUp event, I want it to either stay in its current position or return to its original position, as determined by the value of a boolean variable set by another object's MouseEnter property.
Note, if it would be easier to work with, I can change the grid to a canvas in a cinch. I'm guessing that would be desirable.
So, after that long-winded explanation, here is my question (two-fold):
How do I make the object (label) follow the mouse cursor inside the grid/canvas, but not outside of it? This needs to happen on the MouseDown event of the label.
How do I make the object "stick" in its current position? (From this, I can probably figure out how to make it return to its original position on my own. :D )
My upvote to whoever can help me accomplish this goal the most efficiently! Thank you all very much.
How about something like this :
XAML :
<Canvas x:Name="canv" ToolTip="tt one" Width="400" Height="400" Background="Blue">
<Rectangle x:Name="rec" Fill="Red" Height="50" Width="50" MouseDown="Rectangle_MouseDown" MouseMove="Rectangle_MouseMove" MouseUp="Rectangle_MouseUp" />
</Canvas>
CODE-BEHIND :
private bool isDragging;
private void Rectangle_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
rec.CaptureMouse();
isDragging = true;
}
private void Rectangle_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
if (isDragging)
{
Point canvPosToWindow = canv.TransformToAncestor(this).Transform(new Point(0, 0));
Rectangle r = sender as Rectangle;
var upperlimit = canvPosToWindow.Y + (r.Height / 2);
var lowerlimit = canvPosToWindow.Y + canv.ActualHeight - (r.Height / 2);
var leftlimit = canvPosToWindow.X + (r.Width / 2);
var rightlimit = canvPosToWindow.X + canv.ActualWidth - (r.Width / 2);
var absmouseXpos = e.GetPosition(this).X;
var absmouseYpos = e.GetPosition(this).Y;
if ((absmouseXpos > leftlimit && absmouseXpos < rightlimit)
&& (absmouseYpos > upperlimit && absmouseYpos < lowerlimit))
{
Canvas.SetLeft(r, e.GetPosition(canv).X - (r.Width / 2));
Canvas.SetTop(r, e.GetPosition(canv).Y - (r.Height / 2));
}
}
}
private void Rectangle_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
rec.ReleaseMouseCapture();
isDragging = false;
}
This code could be enhanced, but I think you got the idea ;)
Based on #Bruno's, this is my solution:
double maxX;
double maxY;
private void OnRectMouseDown(object sender, MouseButtonEventArgs e)
{
maxX = canv.ActualWidth - rect.Width;
maxY = canv.ActualHeight - rect.Height;
rect.CaptureMouse();
rect.MouseMove += OnRectMouseMove;
rect.MouseUp += OnRectMouseUp;
}
private void OnRectMouseMove(object sender, MouseEventArgs e)
{
var pos = e.GetPosition(canv);
var newX = pos.X - (rect.Width / 2);
var newY = pos.Y - (rect.Height / 2);
if (newX < 0) newX = 0;
if (newX > maxX) newX = maxX;
if (newY < 0) newY = 0;
if (newY > maxY) newY = maxY;
rect.SetValue(Canvas.LeftProperty, newX);
rect.SetValue(Canvas.TopProperty, newY);
xVal.Content = (newX / maxX).ToString("F3");
yVal.Content = (newY / maxY).ToString("F3");
}
private void OnRectMouseUp(object sender, MouseButtonEventArgs e)
{
rect.ReleaseMouseCapture();
rect.MouseMove -= OnRectMouseMove;
rect.MouseUp -= OnRectMouseUp;
}
try this:
private void Form1_MouseClick(object sender, MouseEventArgs e)
{
(name).Location = new Point(e.X,e.Y);
}
so that will make it so if you click the object will appear there

Silverlight cannot update property after animation runs

Im trying to create a simple pan and zoom app using silverlight 4, but Im having trouble updating the TranslateTransform and ScaleTransform properties after I run an animation on them.
I have tried to set the FillBehaviour to Stop, with no success.
Here is the code that I have:
<Canvas x:Name="LayoutRoot" Background="White" Width="800" Height="600">
<Canvas x:Name="PanningCanvas" Height="600" Width="800">
<Canvas.RenderTransform>
<TransformGroup>
<ScaleTransform x:Name="CanvasScale"/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform x:Name="CanvasTransform"/>
</TransformGroup>
</Canvas.RenderTransform>
<Rectangle x:Name="MovingRect" Fill="#FF834040" Height="209" Canvas.Left="219" Stroke="#FF500505" Canvas.Top="220" Width="329" StrokeThickness="2"/>
</Canvas>
<Button Content="Animate" Width="107" Canvas.Left="38" Canvas.Top="46" ClickMode="Press" Click="Button_Click"/>
</Canvas>
And here is the C# behind it all (slightly abbreviated)
public partial class MainPage : UserControl
{
private ManipulationProcessor2D manipulationProcessor;
public MainPage()
{
InitializeComponent();
this.manipulationProcessor = new ManipulationProcessor2D(Manipulations2D.Translate | Manipulations2D.Scale);
this.manipulationProcessor.Delta += OnManipulationDelta;
Touch.FrameReported += OnCapturedTouchReported;
}
private void OnManipulationDelta(object sender, Manipulation2DDeltaEventArgs e)
{
float zoomVal = e.Delta.ScaleX;
Point pinchPoint = new Point(e.OriginX, e.OriginY);
float xTranslation = e.Delta.TranslationX;
float yTranslation = e.Delta.TranslationY;
if (zoomVal != 1.0)
{
Zoom(zoomVal, PanningCanvas.RenderTransform.Inverse.Transform(pinchPoint), pinchPoint, xTranslation, yTranslation, true);
}
else if (xTranslation != 0 || yTranslation != 0)
{
Translate(xTranslation, yTranslation);
}
}
public void Zoom(double zoom, Point pinchPosition, Point physicalPosition, float xTranslation, float yTranslation, bool isFinger)
{
if (isFinger)
{
CanvasScale.ScaleX = CanvasScale.ScaleX * zoom;
CanvasScale.ScaleY = CanvasScale.ScaleY * zoom;
CanvasTransform.X = -1 * (pinchPosition.X * CanvasScale.ScaleX - physicalPosition.X);
CanvasTransform.Y = -1 * (pinchPosition.Y * CanvasScale.ScaleY - physicalPosition.Y);
}
else
{
CanvasScale.ScaleX = CanvasScale.ScaleX + zoom;
CanvasScale.ScaleY = CanvasScale.ScaleY + zoom;
}
}
private void Translate(float xTranslation, float yTranslation)
{
CanvasTransform.X += xTranslation;
CanvasTransform.Y += yTranslation;
}
private void OnCapturedTouchReported(object sender, TouchFrameEventArgs e)
{
//..removed..//
// process manipulations
this.manipulationProcessor.ProcessManipulators(DateTime.UtcNow.Ticks,manipulators);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
AnimatePosition(new Point(400, 300), 1);
AnimateScale(1.2, 1);
}
private Storyboard translateStoryboard = new Storyboard();
private Storyboard scaleStoryboard = new Storyboard();
public void AnimatePosition(Point destinationPoint, double lengthOfAnimation)
{
Point offset = new Point(destinationPoint.X, destinationPoint.Y);
var translationAnimationX = new DoubleAnimation() { SpeedRatio = 1, Duration = new Duration(TimeSpan.FromSeconds(lengthOfAnimation)), To = offset.X };
translateStoryboard.Children.Add(translationAnimationX);
Storyboard.SetTargetProperty(translationAnimationX, new PropertyPath("(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)"));
var translationAnimationY = new DoubleAnimation() { SpeedRatio = 1, Duration = new Duration(TimeSpan.FromSeconds(lengthOfAnimation)), To = offset.Y };
translateStoryboard.Children.Add(translationAnimationY);
Storyboard.SetTargetProperty(translationAnimationY, new PropertyPath("(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.Y)"));
Storyboard.SetTarget(translateStoryboard, PanningCanvas);
translateStoryboard.Begin();
}
public void AnimateScale( double scale, double lengthOfAnimation)
{
var scaleAnimationX = new DoubleAnimation() { SpeedRatio = 1, Duration = new Duration(TimeSpan.FromSeconds(lengthOfAnimation)), To = scale };
scaleStoryboard.Children.Add(scaleAnimationX);
Storyboard.SetTargetProperty(scaleAnimationX, new PropertyPath("(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)"));
var scaleAnimationY = new DoubleAnimation() { SpeedRatio = 1, Duration = new Duration(TimeSpan.FromSeconds(lengthOfAnimation)), To = scale };
scaleStoryboard.Children.Add(scaleAnimationY);
Storyboard.SetTargetProperty(scaleAnimationY, new PropertyPath("(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)"));
Storyboard.SetTarget(scaleStoryboard, PanningCanvas);
scaleStoryboard.Begin();
}
}
The issue is that after I click the button, I cannot translate or zoom anymore...
please help!
Mark
Ok, so I got it working, but I dont know why.
Changing the animation to work like this:
Storyboard translateStoryboard = new Storyboard();
Point offset = new Point(destinationPoint.X, destinationPoint.Y);
var translationAnimationX = new DoubleAnimation() { SpeedRatio = 1, Duration = new Duration(TimeSpan.FromSeconds(lengthOfAnimation)), To = offset.X };
var translationAnimationY = new DoubleAnimation() { SpeedRatio = 1, Duration = new Duration(TimeSpan.FromSeconds(lengthOfAnimation)), To = offset.Y };
Storyboard.SetTargetProperty(translationAnimationX, new PropertyPath("X"));
Storyboard.SetTargetProperty(translationAnimationY, new PropertyPath("Y"));
Storyboard.SetTargetName(translationAnimationX, "CanvasTransform");
Storyboard.SetTargetName(translationAnimationY, "CanvasTransform");
translateStoryboard.Children.Add(translationAnimationX);
translateStoryboard.Children.Add(translationAnimationY);
translateStoryboard.Begin();
Did the trick, but I dont know why...
Can someone please explain it to me?

Composing sequential rotations around different centers?

The following code draws a line, rotates it 30 degrees around its left end, restores it to its original position, rotates it 30 degrees around its right end, and then repeats several times.
How can I sequence these rotations without restoring the line to its original position in between? The first rotation (around the left endpoint) causes the right endpoint to move; so I would like the next rotation to be around its new position.
The net effect of the sequence should be to make the line segment "walk" forward.
(Note that this code uses the same angle over and over again. But I need a solution that will also work if the angle is different every time.)
XAML:-
<UserControl x:Class="Rotation.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
<Grid x:Name="LayoutRoot">
<Canvas Width="500" Height="500">
<Line Name="TheLine" X1="100" Y1="200" X2="200" Y2="200" Stroke="Black" StrokeThickness="5"></Line>
</Canvas>
</Grid>
</UserControl>
Code:-
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace Rotation
{
public partial class MainPage : UserControl
{
double x1, y1, x2, y2;
public MainPage()
{
InitializeComponent();
for (int i = 0; i < 5; i++)
{
_animations.Add(() => { return rot(true, -30); });
_animations.Add(() => { return rot(false, 30); });
}
_enumerator = _animations.GetEnumerator();
x1 = TheLine.X1;
x2 = TheLine.X2;
y1 = TheLine.Y1;
y2 = TheLine.Y2;
this.Loaded += delegate(object sender, RoutedEventArgs e)
{
RunNextAnimation();
};
}
List<Func<Storyboard>> _animations = new List<Func<Storyboard>>();
IEnumerator<Func<Storyboard>> _enumerator;
public void AnimationCompleted(object sender, EventArgs args)
{
RunNextAnimation();
}
void RunNextAnimation()
{
if (_enumerator.MoveNext())
{
Func<Storyboard> fn = _enumerator.Current;
if (fn != null)
{
Storyboard board = fn();
board.Completed += AnimationCompleted;
board.Begin();
}
}
}
public Storyboard rot(bool aroundLeft, double angle)
{
Storyboard board = new Storyboard();
int duration = 5;
if (true)
{
RotateTransform rot = new RotateTransform();
if (aroundLeft)
{
rot.CenterX = x1;
rot.CenterY = y1;
}
else
{
rot.CenterX = x2;
rot.CenterY = y2;
}
TheLine.RenderTransform = rot;
DoubleAnimation an = new DoubleAnimation();
an.Duration = new Duration(new TimeSpan(0, 0, duration));
an.From = 0;
an.To = angle;
board.Children.Add(an);
Storyboard.SetTarget(an, TheLine);
Storyboard.SetTargetProperty(an, new PropertyPath("(UIElement.RenderTransform).Angle"));
}
return board;
}
}
}
I figured out one way to do this: With each rotation, compute where the line's endpoints will move to. Then before starting the next rotation, move the line so that its position and angle reflect the desired center of that rotation.
Here's the code to do it. Instead of a line, I'm now using a canvas containing a composite shape, which is a little more general purpose.
XAML:
<UserControl x:Class="Rotation.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
<Grid x:Name="LayoutRoot">
<Canvas Width="500" Height="500">
<Canvas Name="TheRect" Canvas.Left="100" Canvas.Top="100" Width="100" Height="20">
<Rectangle Canvas.Left="0" Canvas.Top="0" Width="100" Height="20" Stroke="Black" StrokeThickness="1"></Rectangle>
<Ellipse Width="10" Height="10" Fill="Blue" Canvas.Left="0" Canvas.Top="0" />
</Canvas>
</Canvas>
</Grid>
</UserControl>
C#:
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace Rotation
{
public partial class MainPage : UserControl
{
double x1, y1, x2, y2, w;
double lastAngle;
public MainPage()
{
InitializeComponent();
for (int i = 0; i < 10; i++)
{
_animations.Add(() => { return rot(true, 30); });
_animations.Add(() => { return rot(false, -30); });
}
_enumerator = _animations.GetEnumerator();
x1 = (double)TheRect.GetValue(Canvas.LeftProperty);
y1 = (double)TheRect.GetValue(Canvas.TopProperty);
w = (double)TheRect.GetValue(Canvas.WidthProperty);
x2 = x1 + w;
y2 = y1;
lastAngle = 0.0;
this.Loaded += delegate(object sender, RoutedEventArgs e)
{
RunNextAnimation();
};
}
List<Func<Storyboard>> _animations = new List<Func<Storyboard>>();
IEnumerator<Func<Storyboard>> _enumerator;
public void AnimationCompleted(object sender, EventArgs args)
{
RunNextAnimation();
}
void RunNextAnimation()
{
if (_enumerator.MoveNext())
{
Func<Storyboard> fn = _enumerator.Current;
if (fn != null)
{
Storyboard board = fn();
board.Completed += AnimationCompleted;
board.Begin();
}
}
}
public Storyboard rot(bool aroundLeft, double angle)
{
Storyboard board = new Storyboard();
int duration = 5;
if (true)
{
TheRect.SetValue(Canvas.LeftProperty, aroundLeft ? x1 : x1 - w*(1 - Math.Cos(lastAngle * Math.PI / 180)));
TheRect.SetValue(Canvas.TopProperty, aroundLeft ? y1 : y2);
RotateTransform rot = new RotateTransform();
rot.CenterX = aroundLeft ? 0 : w;
rot.CenterY = aroundLeft ? 0 : 0;
rot.Angle = aroundLeft ? lastAngle : -lastAngle;
TheRect.RenderTransform = rot;
DoubleAnimation an = new DoubleAnimation();
an.Duration = new Duration(new TimeSpan(0, 0, duration));
an.From = lastAngle;
an.To = lastAngle + angle;
board.Children.Add(an);
Storyboard.SetTarget(an, TheRect);
Storyboard.SetTargetProperty(an, new PropertyPath("(UIElement.RenderTransform).Angle"));
// and for next time around:
lastAngle += angle;
if (aroundLeft)
{
// rotating will move x2,y2; compute the updated values for next time
double x0 = x2 - x1;
double y0 = y2 - y1;
double sin = Math.Sin(angle * Math.PI / 180.0);
double cos = Math.Cos(angle * Math.PI / 180.0);
x2 = x1 + (x0 * cos) - (y0 * sin);
y2 = y1 + (x0 * sin) + (y0 * cos);
}
else
{
// rotating will move x1, y1; compute the updated values for next time
double x0 = x1 - x2;
double y0 = y1 - y2;
double sin = Math.Sin(angle * Math.PI / 180.0);
double cos = Math.Cos(angle * Math.PI / 180.0);
x1 = x2 + (x0 * cos) - (y0 * sin);
y1 = y2 + (x0 * sin) + (y0 * cos);
}
}
return board;
}
}
}

Resources