Composing sequential rotations around different centers? - silverlight

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

Related

Matrix animation error. in WPF in code behind

I making animation.
Draw a path with use a ArcSegment.
And moves along to the path.
However, different from startingpoint of animation and startingpoint of path.
What should modify?
this image .. Immediately after the starting the animation.
Circle location is in the middle of the Path.
I Want to help me. please...
animation part. source code
i create demo version.
my source code written in korean. so create demo version
If you continue to click the second button.
Different starting point of the path and starting point Of a circle.
If there are other methods that may be.
It does not matter all when you change the source.
This is just to succeed. We look forward to good results.
You are a really good person. thank you
xaml
<Window x:Class="WpfApplication7.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="400" Width="830">
<Grid>
<Button x:Name="num_bt" Content="1.create number" HorizontalAlignment="Left" VerticalAlignment="Top" Width="118" Click="num_bt_Click"/>
<Button x:Name="start" Content="2.start animation" HorizontalAlignment="Left" Margin="-2,50,0,0" VerticalAlignment="Top" Width="118" Click="draw_bt_Click"/>
<Label Content="contiue click" HorizontalAlignment="Left" Margin="0,97,0,0" VerticalAlignment="Top" Width="116"/>
<Label Content="start animation btn" HorizontalAlignment="Left" Margin="0,120,0,0" VerticalAlignment="Top" Width="116"/>
<Canvas x:Name="canvas" HorizontalAlignment="Left" Height="373" Margin="137,0,0,0" VerticalAlignment="Top" Width="685"/>
</Grid>
code behind
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApplication7
{
/// <summary>
/// MainWindow.xaml에 대한 상호 작용 논리
/// </summary>
public partial class MainWindow : Window
{
int num_count = 10; // use data number
List<int> sampleNumbers; // Save a number to use for sorting
int count = 1; // count
Ellipse[] Circle;
List<double> SaveCircleStartPoint;
struct SaveCircleProperty
{
public double Circle_stposX;
public double Circle_stposY;
public double radian;
}
List<SaveCircleProperty> SaveCircleInfos;
// save changed sorting info
struct SortingTraceInfo
{
public int Position; // change position
public int TargetPosition; // will change position
public int[] SortingNumbers; // sorting numbers
}
// save changed sorting info
List<SortingTraceInfo> sortingInfos;
int sortingInfosIndex;
public MainWindow()
{
InitializeComponent();
}
private void num_bt_Click(object sender, RoutedEventArgs e)
{
Random _random = new Random(); // create number
int[] num = new int[10];
sampleNumbers = new List<int>();
for (int i = 0; i < num_count; i++)
{
num[i] = _random.Next(1, 50);
sampleNumbers.Add(num[i]); // save number data
}
BubbleSort();
drawCircle(num_count, sampleNumbers);
}
private void draw_bt_Click(object sender, RoutedEventArgs e)
{
// draw a path and start animation
SortingTraceInfo traceInfo = this.sortingInfos[this.sortingInfosIndex++];
draw_path(traceInfo.Position, traceInfo.TargetPosition);
}
// draw circle
private void drawCircle(int num, List<int> size)
{
// saving a position to draw a circle
SaveCircleStartPoint = new List<double>();
SaveCircleInfos = new List<SaveCircleProperty>();
// To create the circle by the number of input num
Circle = new Ellipse[num];
int location = ((685 / num) - 5); // circle startpoint
int x = 50; // Width
int y = 115; // Height
for (int i = 0; i < num; i++)
{
double circlesize = size[i] + 10;
double radius = circlesize / 2; // radius
double st_posX = x - radius; // circular X-axis position
double st_posY = y - radius; // circular Y-axis position
SaveCircleProperty cp = new SaveCircleProperty();
// define circle property
Circle[i] = new Ellipse();
Circle[i].Name = "circle" + i.ToString();
Circle[i].Stroke = Brushes.Red;
Circle[i].StrokeThickness = 5;
Circle[i].Width = circlesize;
Circle[i].Height = circlesize;
// position of canvs
Canvas.SetLeft(Circle[i], st_posX); // startpoint x-axis
Canvas.SetTop(Circle[i], st_posY); // startpoint Y-axis
// save circls's property
SaveCircleStartPoint.Add(st_posX);
cp.Circle_stposX = st_posX;
cp.Circle_stposY = st_posY;
cp.radian = radius;
SaveCircleInfos.Add(cp);
// startpoint
x += location;
// add canvas
canvas.Children.Add(Circle[i]);
this.RegisterName(Circle[i].Name, Circle[i]);
}
}
private void draw_path(int pos1, int pos2)
{
SaveCircleProperty scp = this.SaveCircleInfos[pos1];
SaveCircleProperty scp2 = this.SaveCircleInfos[pos2];
create_path(scp.Circle_stposX, scp.Circle_stposY, scp2.Circle_stposX, scp2.Circle_stposY, scp.radian, scp2.radian, pos1, pos2);
}
private void create_path(double startPoint, double startPoint2, double endPoint, double endPoint2, double radian1, double radian2, int position, int tartgetPosition)
{
// regist name
NameScope.SetNameScope(this, new NameScope());
NameScope.SetNameScope(canvas, new NameScope());
// circls position reset
Canvas.SetLeft(Circle[position], 0);
Canvas.SetTop(Circle[position], 0);
Canvas.SetLeft(Circle[tartgetPosition], 0);
Canvas.SetTop(Circle[tartgetPosition], 0);
///<summary>
/// Circle left the road going from left to right
///</summary>
PathGeometry pathGeometryLeft = new PathGeometry();
PathFigure Leftfigure = new PathFigure();
Leftfigure.StartPoint = new Point(startPoint, startPoint2); // x-axis , y-axis start point
// point(x-axis, y-axis)
Leftfigure.Segments.Add(new ArcSegment(new Point(endPoint, startPoint2), new Size(150, endPoint - startPoint), 90, false, SweepDirection.Clockwise, true));
pathGeometryLeft.Figures.Add(Leftfigure);
Path Leftpath = new Path();
Leftpath.Data = pathGeometryLeft;
Leftpath.Stroke = Brushes.Green;
canvas.Children.Add(Leftpath);
MatrixTransform LeftcircleMatrixTransform = new MatrixTransform();
Circle[position].RenderTransform = LeftcircleMatrixTransform;
this.RegisterName("LeftCircleMatrixTransform" + count.ToString(), LeftcircleMatrixTransform);
pathGeometryLeft.Freeze();
// Create a MatrixAnimationUsingPath to move the
// circle along the path by animating
// its MatrixTransform.
MatrixAnimationUsingPath Leftmatrixanimation = new MatrixAnimationUsingPath();
Leftmatrixanimation.PathGeometry = pathGeometryLeft;
Leftmatrixanimation.Duration = TimeSpan.FromSeconds(5);
Leftmatrixanimation.DoesRotateWithTangent = true;
// Set the animation to target the Matrix property
// of the MatrixTransform named "ButtonMatrixTransform".
Storyboard.SetTargetName(Leftmatrixanimation, "LeftCircleMatrixTransform" + count.ToString());
Storyboard.SetTargetProperty(Leftmatrixanimation, new PropertyPath(MatrixTransform.MatrixProperty));
// Create a Storyboard to contain and apply the animation.
Storyboard LeftpathAnimationStoryboard = new Storyboard();
LeftpathAnimationStoryboard.Children.Add(Leftmatrixanimation);
LeftpathAnimationStoryboard.Begin(this);
/// <summary>
/// The road to the right circle from right to left (path)
/// </summary>
PathGeometry RightpathGeometry = new PathGeometry();
PathFigure Rightfigure = new PathFigure();
Rightfigure.StartPoint = new Point(endPoint, endPoint2 + (radian2 * 2)); // x축 , y축 시작점
// point(x축 끝, y축 끝점)
Rightfigure.Segments.Add(new ArcSegment(new Point(startPoint, endPoint2 + (radian2 * 2)), new Size(150, endPoint - startPoint), 90, false, SweepDirection.Clockwise, true));
// this.RegisterName("RightmyArcSegment", Rightfigure.Segments);
RightpathGeometry.Figures.Add(Rightfigure);
Path Rightpath = new Path();
Rightpath.Data = RightpathGeometry;
Rightpath.Stroke = Brushes.Red;
canvas.Children.Add(Rightpath);
MatrixTransform RightcircleMatrixTransform = new MatrixTransform();
Circle[tartgetPosition].RenderTransform = RightcircleMatrixTransform;
this.RegisterName("RightCircleMatrixTransform" + count.ToString(), RightcircleMatrixTransform);
RightpathGeometry.Freeze();
MatrixAnimationUsingPath Rightmatrixanimation = new MatrixAnimationUsingPath();
Rightmatrixanimation.PathGeometry = RightpathGeometry;
Rightmatrixanimation.Duration = TimeSpan.FromSeconds(10);
// Set the animation's DoesRotateWithTangent property
// to true so that rotates the rectangle in addition
// to moving it.
Rightmatrixanimation.DoesRotateWithTangent = true;
// Set the animation to target the Matrix property
// of the MatrixTransform named "CircleMatrixTransform".
Storyboard.SetTargetName(Rightmatrixanimation, "RightCircleMatrixTransform" + count.ToString());
Storyboard.SetTargetProperty(Rightmatrixanimation, new PropertyPath(MatrixTransform.MatrixProperty));
// Create a Storyboard to contain and apply the animation.
Storyboard RightpathAnimationStoryboard = new Storyboard();
RightpathAnimationStoryboard.Children.Add(Rightmatrixanimation);
RightpathAnimationStoryboard.Begin(this);
Ellipse temp = null;
temp = Circle[position];
Circle[position] = Circle[tartgetPosition];
Circle[tartgetPosition] = temp;
count++;
}
// 버블 정렬
private List<int> BubbleSort()
{
sortingInfos = new List<SortingTraceInfo>();
List<int> sorting = new List<int>(sampleNumbers);
for (int i = 0; i < sorting.Count - 1; i++)
{
for (int j = 0; j < sorting.Count - 1 - i; j++)
{
if (sorting[j] > sorting[j + 1])
{
Swap(sorting, j, j + 1);
SortingTraceInfo sortInfo = new SortingTraceInfo(); //
sortInfo.Position = j; // save change position
sortInfo.TargetPosition = j + 1; // save will change position
sortInfo.SortingNumbers = sorting.ToArray(); // sorting number saved to array
sortingInfos.Add(sortInfo); // 변경 정보등을 sortingInfos 리스트에 저장
}
}
}
return sorting;
}
private void Swap(List<int> num, int i, int j)
{
int temp = num[i];
num[i] = num[j];
num[j] = temp;
}
}
}
private void draw_path(int pos1, int pos2)
{
var circles = canvas.Children.OfType<Ellipse>().OrderBy(q => (double)q.GetValue(Canvas.LeftProperty)).ToList();
var circle1 = circles[pos1];
var circle2 = circles[pos2];
// horizontal animation for circle1
Storyboard sb1 = new Storyboard();
double from1 = (double)circle1.GetValue(Canvas.LeftProperty);
double to1 = (double)circle2.GetValue(Canvas.LeftProperty) + circle2.ActualWidth / 2 - circle1.ActualWidth / 2;
DoubleAnimation da1 = new DoubleAnimation(from1, to1, new Duration(TimeSpan.FromSeconds(0.6)));
Storyboard.SetTarget(sb1, circle1);
Storyboard.SetTargetProperty(sb1, new PropertyPath(Canvas.LeftProperty));
sb1.Children.Add(da1);
// horizontal animation for circle2
Storyboard sb2 = new Storyboard();
double from2 = (double)circle2.GetValue(Canvas.LeftProperty);
double to2 = (double)circle1.GetValue(Canvas.LeftProperty) + circle1.ActualWidth / 2 - circle2.ActualWidth / 2;
DoubleAnimation da2 = new DoubleAnimation(from2, to2, new Duration(TimeSpan.FromSeconds(0.6)));
Storyboard.SetTarget(sb2, circle2);
Storyboard.SetTargetProperty(sb2, new PropertyPath(Canvas.LeftProperty));
sb2.Children.Add(da2);
// vertical animation for circle1
Storyboard sb3 = new Storyboard();
double from3 = (double)circle1.GetValue(Canvas.TopProperty);
double to3 = (double)circle1.GetValue(Canvas.TopProperty) + circle1.ActualWidth;
DoubleAnimation da3 = new DoubleAnimation(from3, to3, new Duration(TimeSpan.FromSeconds(0.3)));
da3.AutoReverse = true;
da3.AccelerationRatio = 0.1;
Storyboard.SetTarget(sb3, circle1);
Storyboard.SetTargetProperty(sb3, new PropertyPath(Canvas.TopProperty));
sb3.Children.Add(da3);
// vertical animation for circle2
Storyboard sb4 = new Storyboard();
double from4 = (double)circle2.GetValue(Canvas.TopProperty);
double to4 = (double)circle2.GetValue(Canvas.TopProperty) - circle2.ActualWidth;
DoubleAnimation da4 = new DoubleAnimation(from4, to4, new Duration(TimeSpan.FromSeconds(0.3)));
da4.AutoReverse = true;
da4.AccelerationRatio = 0.1;
Storyboard.SetTarget(sb4, circle2);
Storyboard.SetTargetProperty(sb4, new PropertyPath(Canvas.TopProperty));
sb4.Children.Add(da4);
sb1.Begin();
sb2.Begin();
sb3.Begin();
sb4.Begin();
}
Hope, it helps

How to Animate a line connected to an object on Dragcompleted?

My problem is that i have 2 nodes connected with a line. When one node is dragged to any position on the canvas, it needs to come back to its original position. Also, while coming back to its original position needs to be animated (oscillate function or bounce/elastic ease effect). the problem here is while i have managed to animate the node, the line joining the nodes doesnt stick to it.
Also the nodes are not on the xaml. They are in code behind generated dynamically. Hence i couldn't use LayoutUpdated event as well.
Here's my code:XAML
<my:MyThumb x:Name="myThumb1" LayoutUpdated="myThumb1_LayoutUpdated" DragCompleted="OnDragCompleted" DragDelta="OnDragDelta" Canvas.Left="250" Canvas.Top="70" Template="{StaticResource template1}">
</my:MyThumb>
<my:MyThumb x:Name="myThumb2" DragCompleted="OnDragCompleted" DragDelta="OnDragDelta" Canvas.Left="50" Canvas.Top="70" Template="{StaticResource template1}"/>
In code Behind:
private void OnDragDelta(object sender, DragDeltaEventArgs e)
{
var thumb = e.Source as MyThumb;
var left = Canvas.GetLeft(thumb) + e.HorizontalChange;
var top = Canvas.GetTop(thumb) + e.VerticalChange;
Canvas.SetLeft(thumb, left);
Canvas.SetTop(thumb, top);
double x1 = Canvas.GetLeft(thumb) - e.HorizontalChange; //previous
double y1 = Canvas.GetTop(thumb) - e.VerticalChange; //previous
double x2 = Canvas.GetLeft(thumb); //current
double y2 = Canvas.GetTop(thumb); //current
this.Lable1.Content = "Old x=" + x1 + "|old y=" + y1 + "|new x=" + x2 + "|new y=" + y2;
}
private void OnDragCompleted(object sender, DragCompletedEventArgs e)
{
var thumb = e.Source as MyThumb;
double x1 = Canvas.GetLeft(thumb) - e.HorizontalChange;
double y1 = Canvas.GetTop(thumb) - e.VerticalChange;
double x2 = Canvas.GetLeft(thumb);
double y2 = Canvas.GetTop(thumb);
AnimateThis4(x1, x2, y1, y2, thumb);
}
private void AnimateThis4(double x1, double x2, double y1, double y2, MyThumb thumb)
{
// Create a duration of 2 seconds.
Duration duration = new Duration(TimeSpan.FromSeconds(2));
// Create two DoubleAnimations and set their properties.
DoubleAnimation myDoubleAnimation1 = new DoubleAnimation();
DoubleAnimation myDoubleAnimation2 = new DoubleAnimation();
myDoubleAnimation1.Duration = duration;
myDoubleAnimation2.Duration = duration;
myDoubleAnimation1.FillBehavior = FillBehavior.Stop;
myDoubleAnimation2.FillBehavior = FillBehavior.Stop;
ElasticEase ea = new ElasticEase();
ea.EasingMode = EasingMode.EaseOut;
ea.Springiness = 2;
ea.Oscillations = 5;
myDoubleAnimation1.EasingFunction = ea;
myDoubleAnimation2.EasingFunction = ea;
Storyboard sb = new Storyboard();
sb.Duration = duration;
sb.Children.Add(myDoubleAnimation1);
sb.Children.Add(myDoubleAnimation2);
Storyboard.SetTarget(myDoubleAnimation1, thumb);
Storyboard.SetTarget(myDoubleAnimation2, thumb);
Storyboard.SetTargetProperty(myDoubleAnimation1, new PropertyPath("(Canvas.Left)"));
Storyboard.SetTargetProperty(myDoubleAnimation2, new PropertyPath("(Canvas.Top)"));
//Storyboard.SetTargetProperty(myDoubleAnimation1, new PropertyPath(TranslateTransform.XProperty));
//Storyboard.SetTargetProperty(myDoubleAnimation2, new PropertyPath(TranslateTransform.YProperty));
myDoubleAnimation1.From = x2;
myDoubleAnimation2.From = y2;
myDoubleAnimation1.To = x1;
myDoubleAnimation2.To = y1;
myDoubleAnimation1.Completed += new EventHandler((sender, e) => da_Completed(sender, e, x1, y1, thumb));
myDoubleAnimation2.Completed += new EventHandler((sender, e) => da_Completed(sender, e, x1, y1, thumb));
// Begin the animation.
sb.Begin();
this.Lable1.Content = "Old x=" + x1 + "|old y=" + y1 + "|new x=" + x2 + "|new y=" + y2;
}
// AFTER ANIMATION IS OVER, SET THE THUMB POSITION TO ORIGINAL
void da_Completed(object sender, EventArgs e, double X, double Y, MyThumb thumb)
{
Canvas.SetLeft(thumb, X);
Canvas.SetTop(thumb, Y);
}
// LINE IS CREATED HERE. I CANT SEEM TO BIND THE THUMB TO THE LINE
private void btnJoinLineClick(object sender, RoutedEventArgs e)
{
line = new Line();
myCanvas.Children.Add(line);
line.Stroke = Brushes.Red;
line.StrokeThickness = 2;
line.X1 = Canvas.GetLeft(myThumb2) + myThumb2.ActualWidth / 2;
line.Y1 = Canvas.GetTop(myThumb2) + myThumb2.ActualHeight / 2;
line.X2 = Canvas.GetLeft(myThumb1) + myThumb1.ActualWidth / 2;
line.Y2 = Canvas.GetTop(myThumb1) + myThumb1.ActualHeight / 2;
//DependencyProperty dpX1 = Line.X1Property;
//Binding b1 = new Binding();
//b1.Path = new PropertyPath("Canvas.Left");
//b1.ElementName = this.myThumb2.Name;
////b.Source = itemsControl.ItemContainerGenerator.ContainerFromIndex(0);
//BindingOperations.SetBinding(line, dpX1, b1);
//DependencyProperty dpY1 = Line.Y1Property;
//Binding b2 = new Binding();
//b2.Path = new PropertyPath("Canvas.Top");
//b2.ElementName = this.myThumb2.Name;
////b.Source = itemsControl.ItemContainerGenerator.ContainerFromIndex(0);
//BindingOperations.SetBinding(line, dpY1, b2);
DependencyProperty dpX2 = Line.X2Property;
Binding b3 = new Binding();
b3.Path = new PropertyPath("Canvas.Left");
//b3.ElementName = this.myThumb1.Name;
b3.Source = Canvas.GetLeft(myThumb1) + myThumb1.ActualWidth / 2;
//b.Source = itemsControl.ItemContainerGenerator.ContainerFromIndex(0);
BindingOperations.SetBinding(line, dpX2, b3);
DependencyProperty dpY2 = Line.Y2Property;
Binding b4 = new Binding();
b4.Path = new PropertyPath("Canvas.Top");
b4.ElementName = this.myThumb1.Name;
//b4.Source = Canvas.GetTop(myThumb1) + myThumb1.ActualHeight / 2;
BindingOperations.SetBinding(line, dpY2, b4);
}
Code may be a little ugly...but it works, wish this will help you, any problem just leave a comment.
There are two point you need take care.
1, WPF do not support string propertyPath when you try to binding attached DP to an element, you can use new PropertyPath(Canvas.TopProperty) instead of "Canvas.Top", but in your scenario, we need center position of thumb, so you cann't binding this property directly.
2, since we use our custom DP, we need update this property when animation start(AddValueChanged statement).
namespace WpfApplicationThumbDrag
{
public class MyThumb : Thumb
{
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.DragDelta += thumb_DragDelta;
this.DragCompleted += new DragCompletedEventHandler(MyThumb_DragCompleted);
this.Loaded += (s, e) => { UpdateCenter(); };
var left = DependencyPropertyDescriptor.FromProperty(Canvas.LeftProperty, typeof(Canvas));
var right = DependencyPropertyDescriptor.FromProperty(Canvas.RightProperty, typeof(Canvas));
left.AddValueChanged(this, OnCanvasLeftRightChanged);
right.AddValueChanged(this, OnCanvasLeftRightChanged);
}
private void OnCanvasLeftRightChanged(object sender, EventArgs e)
{
UpdateCenter();
}
private void MyThumb_DragCompleted(object sender, DragCompletedEventArgs e)
{
var thumb = e.Source as MyThumb;
double x1 = Canvas.GetLeft(thumb) - e.HorizontalChange;
double y1 = Canvas.GetTop(thumb) - e.VerticalChange;
double x2 = Canvas.GetLeft(thumb);
double y2 = Canvas.GetTop(thumb);
AnimateThis4(x1, x2, y1, y2, thumb);
}
private void AnimateThis4(double x1, double x2, double y1, double y2, MyThumb thumb)
{
// Create a duration of 2 seconds.
Duration duration = new Duration(TimeSpan.FromSeconds(2));
// Create two DoubleAnimations and set their properties.
DoubleAnimation myDoubleAnimation1 = new DoubleAnimation();
DoubleAnimation myDoubleAnimation2 = new DoubleAnimation();
myDoubleAnimation1.Duration = duration;
myDoubleAnimation2.Duration = duration;
myDoubleAnimation1.FillBehavior = FillBehavior.Stop;
myDoubleAnimation2.FillBehavior = FillBehavior.Stop;
ElasticEase ea = new ElasticEase();
ea.EasingMode = EasingMode.EaseOut;
ea.Springiness = 2;
ea.Oscillations = 5;
myDoubleAnimation1.EasingFunction = ea;
myDoubleAnimation2.EasingFunction = ea;
Storyboard sb = new Storyboard();
sb.Duration = duration;
sb.Children.Add(myDoubleAnimation1);
sb.Children.Add(myDoubleAnimation2);
Storyboard.SetTarget(myDoubleAnimation1, thumb);
Storyboard.SetTarget(myDoubleAnimation2, thumb);
Storyboard.SetTargetProperty(myDoubleAnimation1, new PropertyPath("(Canvas.Left)"));
Storyboard.SetTargetProperty(myDoubleAnimation2, new PropertyPath("(Canvas.Top)"));
//Storyboard.SetTargetProperty(myDoubleAnimation1, new PropertyPath(TranslateTransform.XProperty));
//Storyboard.SetTargetProperty(myDoubleAnimation2, new PropertyPath(TranslateTransform.YProperty));
myDoubleAnimation1.From = x2;
myDoubleAnimation2.From = y2;
myDoubleAnimation1.To = x1;
myDoubleAnimation2.To = y1;
myDoubleAnimation1.Completed += new EventHandler((sender, e) => da_Completed(sender, e, x1, y1, thumb));
myDoubleAnimation2.Completed += new EventHandler((sender, e) => da_Completed(sender, e, x1, y1, thumb));
// Begin the animation.
sb.Begin();
}
// AFTER ANIMATION IS OVER, SET THE THUMB POSITION TO ORIGINAL
void da_Completed(object sender, EventArgs e, double X, double Y, MyThumb thumb)
{
Canvas.SetLeft(thumb, X);
Canvas.SetTop(thumb, Y);
}
public Point Center
{
get { return (Point)GetValue(CenterProperty); }
set { SetValue(CenterProperty, value); }
}
public static readonly DependencyProperty CenterProperty =
DependencyProperty.Register("Center", typeof(Point), typeof(MyThumb), new UIPropertyMetadata(new Point()));
private void thumb_DragDelta(object sender, DragDeltaEventArgs e)
{
var left = Canvas.GetLeft(this) + e.HorizontalChange;
var top = Canvas.GetTop(this) + e.VerticalChange;
Canvas.SetLeft(this, left);
Canvas.SetTop(this, top);
}
private void UpdateCenter()
{
var p = new Point();
p.X = Canvas.GetLeft(this) + this.ActualWidth / 2;
p.Y = Canvas.GetTop(this) + this.ActualHeight / 2;
this.Center = p;
}
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void btnJoinLine_Click(object sender, RoutedEventArgs e)
{
var line = new Line();
myCanvas.Children.Add(line);
line.Stroke = Brushes.Red;
line.StrokeThickness = 2;
line.X1 = Canvas.GetLeft(myThumb2) + myThumb2.ActualWidth / 2;
line.Y1 = Canvas.GetTop(myThumb2) + myThumb2.ActualHeight / 2;
line.X2 = Canvas.GetLeft(myThumb1) + myThumb1.ActualWidth / 2;
line.Y2 = Canvas.GetTop(myThumb1) + myThumb1.ActualHeight / 2;
//Binding Line start X
var binding1 = new Binding();
binding1.ElementName = "myThumb1";
binding1.Path = new PropertyPath("Center.X");
BindingOperations.SetBinding(line, Line.X1Property, binding1);
//Binding Line start Y
var binding11 = new Binding();
binding11.ElementName = "myThumb1";
binding11.Path = new PropertyPath("Center.Y");
BindingOperations.SetBinding(line, Line.Y1Property, binding11);
//Binding Line end X
var binding2 = new Binding();
binding2.ElementName = "myThumb2";
binding2.Path = new PropertyPath("Center.X");
BindingOperations.SetBinding(line, Line.X2Property, binding2);
//Binding Line end Y
var binding22 = new Binding();
binding22.ElementName = "myThumb2";
binding22.Path = new PropertyPath("Center.Y");
BindingOperations.SetBinding(line, Line.Y2Property, binding22);
}
}
}
<Window x:Class="WpfApplicationThumbDrag.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplicationThumbDrag"
Title="MainWindow"
Width="525"
Height="350">
<Canvas x:Name="myCanvas">
<local:MyThumb x:Name="myThumb1"
Canvas.Left="250"
Canvas.Top="70"
Width="100"
Height="30" />
<local:MyThumb x:Name="myThumb2"
Canvas.Left="50"
Canvas.Top="70"
Width="100"
Height="30" />
<Button x:Name="btnJoinLine"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Click="btnJoinLine_Click"
Content="join" />
</Canvas>
</Window>

Zooming To Mouse Point With ScrollView and ViewBox in Wpf

I have some paths drawn to the screen in wpf. The coordinates being used are quite small so they have been made to fill the screen with a view box. I am now trying to implement pan and zoom functionality. I would like to be able to zoom to wherever the mouse is in relation to the ui (i.e zoomed screen center is equal to mouse coordinates). The current outcome is that the center of the screen when zoomed is not reflective of the exact mouse position on the ui.
If you want to see what is happening... Here is my current solution file.
Or
Heres some code:
View Xaml
<Grid Name="MasterGrid" DataContext="{StaticResource mainWindowViewModel}">
<ScrollViewer HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible" Name="VisualisationScroller">
<Viewbox Name="VisualisationBox" Stretch="Fill" Loaded="VisualisationBox_Loaded">
<ItemsControl Name="CustomDrawingElement" ItemsSource="{Binding Trajectories}" Width="{Binding VisualisationWidth}" Height="{Binding VisualisationHeight}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="data:VisualisedTrajectory">
<Path Data = "{Binding PathData}" Stroke="Red" StrokeThickness="0.001" Fill="Transparent" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas
Background="DarkGray"
IsItemsHost="True">
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="1" ScaleY="1" />
<TranslateTransform />
</TransformGroup>
</ItemsControl.RenderTransform>
</ItemsControl>
</Viewbox>
</ScrollViewer>
</Grid>
View Model
public class MainWindowViewModel : BaseViewModel
{
public MainWindowViewModel()
{
VisualiseRawTrajectories();
}
private ObservableCollection<VisualisedTrajectory> _trajectories = new ObservableCollection<VisualisedTrajectory>();
public ObservableCollection<VisualisedTrajectory> Trajectories
{
get { return _trajectories; }
}
#region VisualisationDimensions
private double _visualisationWidth = 100;
public double VisualisationWidth
{
get { return _visualisationWidth; }
private set { _visualisationWidth = value; }
}
private double _visualisationHeight = 100;
public double VisualisationHeight
{
get { return _visualisationHeight; }
private set { _visualisationHeight = value; }
}
#endregion
public void VisualiseRawTrajectories()
{
var rand = new Random();
for (int i = 0; i < 5; i++)
{
var currentTrajectorySet = new List<Point>(); //each time through reinitialise
for (int j = 0; j < 5; j++)
{
currentTrajectorySet.Add(new Point(rand.NextDouble() * 0.5, rand.NextDouble() * 0.5)); //add a new point with max 0.5
if(j == 4)
{
currentTrajectorySet.Add(new Point(0.5, 0.5)); //for good measure :)
_trajectories.Add(new VisualisedTrajectory(CreatePathData(currentTrajectorySet)));
}
}
}
VisualisationHeight = 0.5;
VisualisationWidth = 0.5; //just for demonstration purposes
OnPropertyChanged("VisualisationHeight");
OnPropertyChanged("VisualisationWidth");
}
private Geometry CreatePathData(IList<Point> points)
{
var geometry = new StreamGeometry {FillRule = FillRule.EvenOdd};
using (StreamGeometryContext ctx = geometry.Open())
{
ctx.BeginFigure(points[0], false, false); //use the first index
ctx.PolyLineTo(points, true, true);
}
return (Geometry)geometry.GetAsFrozen();
}
}
View Code Behind
public MainWindow()
{
InitializeComponent();
VisualisationScroller.PreviewMouseWheel += OnPreviewMouseWheel;
}
private Point originalDimensions;
private void VisualisationBox_Loaded(object sender, RoutedEventArgs e)
{
Viewbox viewBox = sender as Viewbox;
viewBox.Width = viewBox.ActualWidth;
viewBox.Height = viewBox.ActualHeight;
originalDimensions = new Point(viewBox.ActualWidth, viewBox.ActualHeight);
}
#region Zoom
private int _numberDrawnItems = 0;
private void OnPreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
var zoomScale = new Point(CustomDrawingElement.RenderTransform.Value.M11,
CustomDrawingElement.RenderTransform.Value.M22); //gets the scale x and scale y
if (CustomDrawingElement != null && _numberDrawnItems != CustomDrawingElement.Items.Count) //if there was something draw to screen
{
_numberDrawnItems = CustomDrawingElement.Items.Count;
CustomDrawingElement.RenderTransformOrigin = new Point(0.5, 0.5); //if not set zoom from center
}
if (e.Delta > 0)
{
if (CustomDrawingElement != null)
{
VisualisationBox.Width = originalDimensions.X * (zoomScale.X + 1);
VisualisationBox.Height = originalDimensions.Y * (zoomScale.Y + 1);
var mousePosition = e.GetPosition(MasterGrid);
CustomDrawingElement.RenderTransformOrigin = new Point(mousePosition.X / MasterGrid.ActualWidth, mousePosition.Y / MasterGrid.ActualHeight);
CustomDrawingElement.RenderTransform = new MatrixTransform(zoomScale.X + 1, 0, 0, zoomScale.Y + 1, 0, 0);
}
}
if (e.Delta < 0)
{
if (zoomScale.X > 1 && zoomScale.Y > 1) //stops you from zooming out too much
{
if (CustomDrawingElement != null)
{
VisualisationBox.Width = VisualisationBox.Width - originalDimensions.X;
VisualisationBox.Height = VisualisationBox.Height - originalDimensions.Y;
CustomDrawingElement.RenderTransform = new MatrixTransform(zoomScale.X - 1, 0, 0, zoomScale.Y - 1, 0, 0);
}
}
}
e.Handled = true;
}
#endregion //Zooming code
}
Solved it changed the back code for zooming the View to:
if (e.Delta > 0)
{
if (CustomDrawingElement != null)
{
//zoom
_zoomScale++; //increase it now that we have zoomed
VisualisationBox.Width = _originalDimensions.X * (_zoomScale);
VisualisationBox.Height = _originalDimensions.Y * (_zoomScale);
ScrollerDimensions.Content = VisualisationScroller.ActualWidth + "x" + VisualisationScroller.ActualHeight;
BoxDimensions.Content = VisualisationBox.ActualWidth + "x" + VisualisationBox.ActualHeight;
var mousePosition = e.GetPosition(MasterGrid);
mousePosition = MasterGrid.TransformToVisual(VisualisationBox).Transform(mousePosition);
ScrolledPoint.Content = mousePosition.X + "," + mousePosition.Y;
VisualisationScroller.ScrollToHorizontalOffset(mousePosition.X);
VisualisationScroller.ScrollToVerticalOffset(mousePosition.Y);
}
}
if (e.Delta < 0)
{
if (_zoomScale > 1) //stops you from zooming out too much
{
if (CustomDrawingElement != null)
{
var mousePosition = e.GetPosition(MasterGrid);
_zoomScale -= 1; //decrease the zoom
VisualisationBox.Width = VisualisationBox.Width - _originalDimensions.X;
VisualisationBox.Height = VisualisationBox.Height - _originalDimensions.Y;
mousePosition = MasterGrid.TransformToVisual(VisualisationBox).Transform(mousePosition);
mousePosition = new Point(mousePosition.X - _originalDimensions.X, mousePosition.Y - _originalDimensions.Y);
VisualisationScroller.ScrollToHorizontalOffset(mousePosition.X);
VisualisationScroller.ScrollToVerticalOffset(mousePosition.Y);
}
}
}
e.Handled = true;
If anyone is interested HERE is the finished solution file with panning implemented too.

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.

How do I only allow dragging in a circular path?

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

Resources