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

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>

Related

WPF: DrawingContext resolution

I'm using DrawingContext class to draw a bar graph, but the produced labels and ticks are blurry:
In the code I am using a class BarRenderer derived from FrameworkElement. Each bar is represented by a BarRenderer instance, and I do the drawing by overriding the OnRender method. Each "bar" has a label and a value where label is the text below and value defines the height of the bar. and The logic responsible for drawing labels and tick is as follows:
private void DrawLabel(DrawingContext drawingContext, FormattedText text, double x, double y, int direction)
{
drawingContext.DrawLine(_blackPen, new Point(Width * 0.5, y), new Point(Width * 0.5, y + 6));
y += 6;
if (LabelRotationDegree != 0)
{
RotateTransform rotateTransform = new RotateTransform(-LabelRotationDegree, 0.5 * Width, y + text.Height * 0.5);
TranslateTransform translateTransform = new TranslateTransform(0, direction * text.WidthIncludingTrailingWhitespace * 0.5);
drawingContext.PushTransform(translateTransform);
drawingContext.PushTransform(rotateTransform);
}
drawingContext.DrawText(text, new Point(x, y));
if (LabelRotationDegree != 0)
{
drawingContext.Pop();
drawingContext.Pop();
}
}
I doubt that the issue is with the rotation transform, since when the labels are not transformed they are no longer as blurry as before:
But as can be seen some of the ticks are darker than the others. So what am I doing wrong?
EDIT:
I've already set SnapsToDevicePixels to true, and when I set RenderOptions.SetEdgeMode to Aliased some ticks disappear and text is still blurry:
Edit 2: More code
public class GraphUnitRenderer : FrameworkElement
{
private const double TEXT_SIZE = 12;
private const double KEY_LABELS_HEIGHT = 50;
private readonly Typeface _typeface;
private readonly CultureInfo _cultureInfo;
private readonly Pen _blackPen;
private readonly Pen _bluePen;
private readonly Pen _tickBlackPen;
private readonly Pen _whitePen;
public BarData Data { get; set; }
//Some properties
public GraphUnitRenderer()
{
_typeface = new Typeface("Segoe UI");
_cultureInfo = CultureInfo.CurrentCulture;
_blackPen = new Pen(Brushes.Black, 1);
_bluePen = new Pen(Brushes.Blue, 1);
_tickBlackPen = new Pen(Brushes.Black, 2);
_whitePen = new Pen(Brushes.White, 1);
/*
_blackPen.Freeze();
_bluePen.Freeze();
_tickBlackPen.Freeze();
_whitePen.Freeze();
*/
RenderOptions.SetEdgeMode(this, EdgeMode.Aliased);
SnapsToDevicePixels = true;
}
protected override void OnRender(DrawingContext drawingContext)
{
TextOptions.SetTextFormattingMode(this, TextFormattingMode.Display);
FormattedText keyText = GetText(Data.Key);
//Bar drawing logic
if (LabelDisplayIsForced || (LabelRotationDegree == 0 && keyText.WidthIncludingTrailingWhitespace < Width) || (LabelRotationDegree != 0 && keyText.Height < Width))
DrawLabel(drawingContext, keyText, 0.5 * (Width - keyText.WidthIncludingTrailingWhitespace), GetYPosition(1) + 1, 1);
}
private void DrawLabel(DrawingContext drawingContext, FormattedText text, double x, double y, int direction)
{
drawingContext.DrawLine(_blackPen, new Point(Width * 0.5, y), new Point(Width * 0.5, y + 6));
y += 6;
if (LabelRotationDegree != 0)
{
RotateTransform rotateTransform = new RotateTransform(-LabelRotationDegree, 0.5 * Width, y + text.Height * 0.5);
TranslateTransform translateTransform = new TranslateTransform(0, direction * text.WidthIncludingTrailingWhitespace * 0.5);
drawingContext.PushTransform(translateTransform);
drawingContext.PushTransform(rotateTransform);
}
drawingContext.DrawText(text, new Point(x, y));
if (LabelRotationDegree != 0)
{
drawingContext.Pop();
drawingContext.Pop();
}
}
private double GetYPosition(double position) => Height - position - KEY_LABELS_HEIGHT;
private FormattedText GetText(string text) => new FormattedText(text, _cultureInfo, FlowDirection.LeftToRight, _typeface, TEXT_SIZE, Brushes.Black, VisualTreeHelper.GetDpi(this).PixelsPerDip);
}

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

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

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