How to avoid recursive dependency properties - wpf

I have a class LineG inherited from a shape which will draw a simple line between two points.. I did that simply by adding two dependency properties StartPointProperty and EndPointProperty... Lastly I want to add another functionality which is MidPoint, so when I draw the line there will be a midPoint in the middle of the line.
When I drag the StartPoint or EndPoint the shape will be redrawn, and when I drag the MidPoint the shape will translate depending on the MidPoint change...
private static void PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
LineG lineG = (LineG)d;
if (e.Property.Name == "StartPoint")
{
}
else if (e.Property.Name == "EndPoint")
{
}
else //if MidPoint
{
Point p1 = (Point)e.OldValue;
Point p2 = (Point)e.NewValue;
double offsetX = p2.X - p1.X;
double offsetY = p2.Y - p1.Y;
lineG.StartPoint = new Point(lineG.StartPoint.X + offsetX, lineG.StartPoint.Y + offsetY);
lineG.EndPoint = new Point(lineG.EndPoint.X + offsetX, lineG.EndPoint.Y + offsetY);
lineG.MidPoint = GeneralMethods.MidPoint(lineG.StartPoint, lineG.EndPoint);
}
lineG.InvalidateMeasure();
}
protected override Geometry DefiningGeometry
{
get
{
lg.StartPoint = StartPoint;
lg.EndPoint = EndPoint;
return lg;
}
}

In such cases you can add an int counter to each operation in your class that you increment during processing. You don't do something if the counter is not 0. Example:
private int _suspendCalculation;
private static void OnPropertyChanged(..)
{
if (_suspendCalculation > 0) return;
_suspendCalculation++;
try
{
CalculateAndSetOtherProperty();
}
finally
{
_suspendCalculation--;
}
}

Related

WPF list modification during iteration?

I'm trying to make a very simple game where the yellow ball bouncing back and fourth. If it collides with one of the moving blue squares, the square is supposed to disappear and a new one should appear (always 3 in the window) elsewhere. When my code reaches this part, all 3 squares disappear (then reappears as intended it is not a problem) and I just cant figure out why. It would be a huge help if somebody could run over my methods responsible for the problem. Thank you in advance.
So my timer_Tick method, responsible for every frame:
void timer_Tick(object sender, EventArgs e)
{
logic.MoveBall();
if (model.Enemy.Count<3)
{
logic.AddEnemy();
}
int iii = 0;
foreach (MyShape enemy in model.Enemy) //the whole thing from here is me trying to solve list modification during iteration
{
if (logic.MoveEnemy(enemy) == -1)
{
logic.MoveEnemy(enemy);
}
else iii = logic.MoveEnemy(enemy);
}
if (iii > -1)
{
for (int j = model.Enemy.Count - 1; j >= 0; j--)
{
if (j == model.Enemy.Count - iii)
{
model.Enemy.RemoveAt(j);
}
}
}
}
MoveEnemy: I try to decide whether there is collusion and if yes, then try to remove the given shape object (blue square). Because This whole method is in a foreach, I just save the removable element and forward it to timer_Tick
public int MoveEnemy(MyShape shape)
{
int i = 0;
int ii = -1;
if ((shape.Area.IntersectsWith(model.Ball.Area)))
{
i = 0;
foreach (var e in model.Enemy)
{
i++;
if (shape == e)
{
ii = i;
}
}
}
shape.ChangeX(shape.Dx);
shape.ChangeY(shape.Dy);
bool coll = false;
foreach (var e in model.Enemy)
{
if ((e.Area.IntersectsWith(shape.Area)) && (shape != e))
{
coll = true;
}
}
if (shape.Area.Left < 0 || shape.Area.Right > Config.Width-40 || coll)
{
shape.Dx = -shape.Dx;
}
if (shape.Area.Top < 0)
{
shape.Dy = -shape.Dy;
}
if (shape.Area.Bottom > Config.Height/2)
{
shape.Dy = -shape.Dy;
}
RefreshScreen?.Invoke(this, EventArgs.Empty);
return ii;
}
And finally AddEnemy:
public void AddEnemy()
{
rnd = new Random();
int r = rnd.Next(-300, 300);
model.Enemy.Add(new MyShape(Config.Width / 2+r, 0, 40, 40));
RefreshScreen?.Invoke(this, EventArgs.Empty);
}
List<T> (or IList and Enumerable) exposes some useful methods to compact code:
int itemIndex = list.IndexOf(item); // Gets the index of the item if found, otherwise returns -1
list.Remove(item); // Remove item if contained in collection
list.RemoveAll(item => item > 5); // Removes all items that satisfy a condition (replaces explicit iteration)
bool hasAnyMatch = list.Any(item => item > 5); // Returns true as soon as the first item satisfies the condition (replaces explicit iteration)
A simplified version, which should eliminate the flaw:
void timer_Tick(object sender, EventArgs e)
{
if (model.Enemy.Count < 3)
{
logic.AddEnemy();
}
logic.MoveBall();
model.Enemy.ForeEach(logic.MoveEnemy);
model.Enemy.RemoveAll(logic.IsCollidingWithBall);
}
public void AddEnemy()
{
rnd = new Random();
int r = rnd.Next(-300, 300);
model.Enemy.Add(new MyShape(Config.Width / 2 + r, 0, 40, 40));
RefreshScreen?.Invoke(this, EventArgs.Empty);
}
public bool IsCollidingWithBall(MyShape shape)
{
return shape.Area.IntersectsWith(model.Ball.Area);
}
public int MoveEnemy(MyShape shape)
{
shape.ChangeX(shape.Dx);
shape.ChangeY(shape.Dy);
bool hasCollision = model.Enemy.Any(enemy => enemy.Area.IntersectsWith(shape.Area)
&& enemy != shape);
if (hasCollision || shape.Area.Left < 0 || shape.Area.Right > Config.Width - 40)
{
shape.Dx = -shape.Dx;
}
if (shape.Area.Top < 0)
{
shape.Dy = -shape.Dy;
}
if (shape.Area.Bottom > Config.Height / 2)
{
shape.Dy = -shape.Dy;
}
RefreshScreen?.Invoke(this, EventArgs.Empty);
}

Printing control and setting its position on A4 page

I am trying to print selected control to PDF/printer yet I have problem with positioning it on printed page.
When I changed location in Rect it doesn't affect the outcome. I want the control to be in the center of the page or at least the left top corner of the control should be in left top corner of the page, but all the time I have the same picture which starts at the middle of page.
This is my code:
private async Task PrintObjectVisual(bool canUserSelectPrinter, FrameworkElement frameworkElement)
{
if (_CreatePrinterDialog(canUserSelectPrinter, out var prnt)) return;
var offset = VisualTreeHelper.GetOffset(frameworkElement);
Transform originalScale = frameworkElement.LayoutTransform.Clone();
try
{
bool allowForceLandscape = true;
bool forceLandscape =
allowForceLandscape &&
(_starMainPage.ActPage.PresPage.PrintLandscape || frameworkElement.ActualWidth > prnt.PrintableAreaWidth)
||
(frameworkElement is IGraphViewControl || frameworkElement is IMapViewControl);
double scale = 0.0;
PageMediaSize pageSize = null;
pageSize = new PageMediaSize(PageMediaSizeName.ISOA4, CmToPx(29.7), CmToPx(21));
prnt.PrintTicket.PageMediaSize = pageSize;
if (forceLandscape)
{
prnt.PrintTicket.PageOrientation = PageOrientation.Landscape;
if (pageSize.Width != null && pageSize.Height != null)
scale = Math.Min(
(pageSize.Height.Value / frameworkElement.ActualWidth), (pageSize.Width.Value / frameworkElement.ActualHeight));
}
else
{
prnt.PrintTicket.PageOrientation = PageOrientation.Portrait;
if (pageSize.Width != null && pageSize.Height != null)
scale = Math.Min(
(pageSize.Width.Value / frameworkElement.ActualWidth), (pageSize.Height.Value / frameworkElement.ActualHeight));
}
//if (scale > 1.0)
// scale = 1.0;
frameworkElement.LayoutTransform = new ScaleTransform(scale, scale);
// after a thought i know that i might not need it
//var size = _GetPrintAreaSize(forceLandscape, pageSize);
//frameworkElement.Measure(size);
//frameworkElement.Arrange(
// new Rect(new Point(0, 0), size));
prnt.PrintVisual(frameworkElement, $"Printing Object {frameworkElement.Name}");
}
catch (Exception e)
{
PresHelper.WriteToDebug(_presBaseObject, e);
}
finally
{
frameworkElement.LayoutTransform = originalScale;
// after a thought i know that i might not need it
//Size size2 = new Size(_starMainPage.Canvas.ActualWidth, _starMainPage.Canvas.ActualHeight);
//frameworkElement.Measure(size2);
//var x = offset.X - (offset.X * 2);// offset.X
//var y = offset.Y - (offset.Y * 2);// offset.X
//frameworkElement.Arrange(new Rect(new Point(x, y), size2));
}
}
private static bool _CreatePrinterDialog(bool canUserSelectPrinter, out PrintDialog prnt)
{
prnt = new PrintDialog();
if (canUserSelectPrinter)
{
var result = prnt.ShowDialog();
if (result == false)
return true;
}
return false;
}
private Size _GetPrintAreaSize(bool forceLandscape, PageMediaSize pageSize)
{
if (pageSize.Height == null || pageSize.Width == null) throw new NullReferenceException($"Page size values are null! Can't print without it.");
Size size;
if (forceLandscape)
size = new Size(pageSize.Height.Value, pageSize.Width.Value);
else
size = new Size(pageSize.Width.Value, pageSize.Height.Value);
return size;
}
Ok, so after some time on it I have solved the problem.
I assign original Canvas.LeftProperty and TopProperty to some private class members and then assign my own (double) values.
This code is just before the printing.
Later i'm assigning old values:
frameworkElement.LayoutTransform = new ScaleTransform(scale, scale);
left = (double)frameworkElement.GetValue(Canvas.LeftProperty);
top = (double)frameworkElement.GetValue(Canvas.TopProperty);
frameworkElement.SetValue(Canvas.LeftProperty, _marginSize);
frameworkElement.SetValue(Canvas.TopProperty, _marginSize);
prnt.PrintVisual(frameworkElement, $"Printing MikObject {frameworkElement.Name}");
}
catch (Exception e)
{
PresHelper.WriteToDebug(_presBaseObject, e);
}
finally
{
frameworkElement.SetValue(Canvas.LeftProperty, left);
frameworkElement.SetValue(Canvas.TopProperty, top);
frameworkElement.LayoutTransform = originalScale;
}

Graphic view of binary tree in Windows Forms

I have to make a graphical representation of a binary tree with the possibility of selecting nodes...Any ideas?
It should look like this
binary tree
I might translate this later for a quick, working example in C#, but here is a Java homework assignment I did way back in 2000!
You should be able to glean the algorithm and code necessary to draw the Tree based on the Java class:
// FileName: DrawBinaryTree.java
/************************************
* Student: Michael Tomlinson *
* Course: CS 145-Section 1 *
* Instructor: Dareleen Schaffer *
* *
* Assignment #4, Problem #1 *
* Due Date: July 24, 2000 *
************************************
*/
package BinaryTree;
import java.awt.*;
import javax.swing.*;
public class DrawBinaryTree {
// Data Fields
private BinaryTree tree=null;
private double xDiv=50, yDiv=50;
private boolean sizeToFit=true;
private Panel outputPanel=null;
// Constructors
public DrawBinaryTree() {} // default constructor
public DrawBinaryTree(BinaryTree tree) {
setBinaryTree(tree);
} // end constructor with (BinaryTree) parameters
public DrawBinaryTree(Panel panel) {
setOutputPanel(panel);
} // end constructor with (JPanel) parameters
public DrawBinaryTree(BinaryTree tree, Panel panel) {
setBinaryTree(tree);
setOutputPanel(panel);
} // end constructor with (BinaryTree, JPanel) parameters
// Modifiers
public void setBinaryTree(BinaryTree tree) {
this.tree = tree;
} // end method setBinaryTree
public void setOutputPanel(Panel panel) {
this.outputPanel = panel;
} // end method setOutputPanel
public void setSizeToFit(boolean sizeToFit) {
this.sizeToFit = sizeToFit;
} // end method setSizeToFit
public void increaseXDiv() {
xDiv*=1.1;
} // end method increaseXDiv
public void increaseYDiv() {
yDiv*=1.1;
} // end method increaseYDiv
public void decreaseXDiv() {
xDiv*=.9;
} // end method decreaseXDiv
public void decreaseYDiv() {
yDiv*=.9;
} // end method decreaseyDiv
// Public Methods
public void drawTree(Point translate) {
if(outputPanel!=null && tree!=null) {
Graphics g = outputPanel.getGraphics();
g.translate(translate.x, translate.y);
Dimension panelSize = outputPanel.getSize();
if(!tree.isEmpty()) {
int treeDepth = tree.maxLevel();
if(sizeToFit) {
xDiv = (double)panelSize.width/(Math.pow(2,treeDepth+1)+1);
yDiv = (double)panelSize.height/((double)treeDepth+1);
}
Point rootCoord = new Point(panelSize.width/2, (int)(yDiv/2));
drawTreeNode(rootCoord, tree.getRoot(), 0, g);
}
else {
Font f = g.getFont();
String message = "The Tree is Empty.";
FontMetrics fm = g.getFontMetrics(f);
int messageLength = fm.stringWidth(message);
int messageHeight = fm.getHeight();
g.drawString(message, panelSize.width/2 - messageLength/2,
panelSize.height/2 - messageHeight/2);
}
g.dispose();
}
} // end method drawTree
// Private Methods
private void drawTreeNode(Point coord, SearchTreeNode node, int depth,
Graphics g) {
double xOffset = (Math.pow(2, tree.maxLevel() - depth)/2)*xDiv;
int newY =(int)(((double)depth+1)*yDiv+yDiv/2.0);
if(node.leftChild!=null) {
int lcx = (int)(coord.x - xOffset);
g.setColor(Color.blue);
g.drawLine(coord.x, coord.y, lcx, newY);
Point leftChildCoord = new Point(lcx, newY);
drawTreeNode(leftChildCoord, node.leftChild, (depth + 1), g);
}
if(node.rightChild!=null) {
int rcx = (int)(coord.x + xOffset);
g.setColor(Color.red);
g.drawLine(coord.x, coord.y, rcx, newY);
Point rightChildCoord = new Point(rcx, newY);
drawTreeNode(rightChildCoord, node.rightChild, (depth + 1), g);
}
g.setColor(Color.black);
g.drawOval(coord.x-5, coord.y-5, 10, 10);
g.fillOval(coord.x-5, coord.y-5, 10, 10);
g.drawString(node.item.toString(), coord.x+10, coord.y+5);
} // end method drawTreeNode
} // end class DrawBinary Tree

WPF 3D POINT DRAWING

I need to draw a point in 3D using WPF. I am using class ScreenSpaceLines3D to draw a line in 3D, but I'm not able to draw point in 3D (while model zooming). Can anybody help me? Some example code would be appreciated. Thanks
Use PointsVisual3D class from helixtoolkit, with it you can add points on viewport with color, size. Even you can apply transforms, too.
A point can't be seen, because it has no surface area. WPF 3d is not a ray tracing engine, unfortunately.
However, someone built a pretty nice ray-tracing starter library. This project is a good starting point if you want to do ray-tracing with WPF: http://raytracer.codeplex.com/.
It is not so complicated task.
Download the Helix3D Library
Find the sphere mesh code in samples
Attach the scphere's radius to zooming realization.
i can create a PointVisual3d Own Class. The code of PointVisual3D is below i think this one is helpful for you...
public class PointVisual3D:ModelVisual3D
{
private readonly GeometryModel3D _model;
private readonly MeshGeometry3D _mesh;
private Matrix3D _visualToScreen;
private Matrix3D _screenToVisual;
public PointVisual3D()
{
_mesh = new MeshGeometry3D();
_model = new GeometryModel3D();
_model.Geometry = _mesh;
SetColor(this.Color);
this.Content = _model;
this.Points = new Point3DCollection();
CompositionTarget.Rendering += OnRender;
}
private void OnRender(object sender, EventArgs e)
{
if (Points.Count == 0 && _mesh.Positions.Count == 0)
{
return;
}
if (UpdateTransforms() && MainWindow.mousedown==false)
{
RebuildGeometry();
}
}
public static readonly DependencyProperty ColorProperty = DependencyProperty.Register("Color", typeof(Color), typeof(PointVisual3D), new PropertyMetadata(Colors.White, OnColorChanged));
private static void OnColorChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
((PointVisual3D)sender).SetColor((Color)args.NewValue);
}
private void SetColor(Color color)
{
MaterialGroup unlitMaterial = new MaterialGroup();
unlitMaterial.Children.Add(new DiffuseMaterial(new SolidColorBrush(Colors.Black)));
unlitMaterial.Children.Add(new EmissiveMaterial(new SolidColorBrush(color)));
unlitMaterial.Freeze();
_model.Material = unlitMaterial;
_model.BackMaterial = unlitMaterial;
}
public Color Color
{
get { return (Color)GetValue(ColorProperty); }
set { SetValue(ColorProperty, value); }
}
//public static readonly DependencyProperty ThicknessProperty = DependencyProperty.Register("Thickness", typeof(double), typeof(PointVisual3D), new PropertyMetadata(1.0, OnThicknessChanged));
//private static void OnThicknessChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
//{
// ((PointVisual3D)sender).GeometryDirty();
//}
//public double Thickness
//{
// get { return (double)GetValue(ThicknessProperty); }
// set { SetValue(ThicknessProperty, value); }
//}
public static readonly DependencyProperty PointsProperty = DependencyProperty.Register("Points", typeof(Point3DCollection), typeof(PointVisual3D), new PropertyMetadata(null, OnPointsChanged));
private static void OnPointsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
((PointVisual3D)sender).GeometryDirty();
}
public Point3DCollection Points
{
get { return (Point3DCollection)GetValue(PointsProperty); }
set { SetValue(PointsProperty, value); }
}
private void GeometryDirty()
{
_visualToScreen = MathUtils.ZeroMatrix;
}
private void RebuildGeometry()
{
//double halfThickness = Thickness / 2.0;
//int numLines = Points.Count / 2;
//Point3DCollection positions = new Point3DCollection(numLines * 4);
//for (int i = 0; i < numLines; i++)
//{
// int startIndex = i * 2;
// Point3D startPoint = Points[startIndex];
// Point3D endPoint = Points[startIndex + 1];
// AddSegment(positions, startPoint, endPoint, halfThickness);
//}
//positions.Freeze();
//_mesh.Positions = positions;
Int32Collection indices = new Int32Collection(Points.Count * 6);
for (int i = 0; i < Points.Count; i++)
{
indices.Add(i * 4 + 2);
indices.Add(i * 4 + 1);
indices.Add(i * 4 + 0);
indices.Add(i * 4 + 2);
indices.Add(i * 4 + 3);
indices.Add(i * 4 + 1);
}
indices.Freeze();
_mesh.TriangleIndices = indices;
_mesh.Positions = CreatePositions(this.Points, this.Size, 0.0);
}
public Point3DCollection CreatePositions(IList<Point3D> points, double size = 1.0, double depthOffset = 0.0)
{
double halfSize = size / 2.0;
int numPoints = points.Count;
var outline = new[]
{
new Vector(-halfSize, halfSize), new Vector(-halfSize, -halfSize), new Vector(halfSize, halfSize),
new Vector(halfSize, -halfSize)
};
var positions = new Point3DCollection(numPoints * 4);
for (int i = 0; i < numPoints; i++)
{
var screenPoint = (Point4D)points[i] * this._visualToScreen;
double spx = screenPoint.X;
double spy = screenPoint.Y;
double spz = screenPoint.Z;
double spw = screenPoint.W;
if (!depthOffset.Equals(0))
{
spz -= depthOffset * spw;
}
var p0 = new Point4D(spx, spy, spz, spw) * this._screenToVisual;
double pwinverse = 1 / p0.W;
foreach (var v in outline)
{
var p = new Point4D(spx + v.X * spw, spy + v.Y * spw, spz, spw) * this._screenToVisual;
positions.Add(new Point3D(p.X * pwinverse, p.Y * pwinverse, p.Z * pwinverse));
}
}
positions.Freeze();
return positions;
}
/// <summary>
/// Identifies the <see cref="Size"/> dependency property.
/// </summary>
public static readonly DependencyProperty SizeProperty = DependencyProperty.Register(
"Size", typeof(double), typeof(PointVisual3D), new UIPropertyMetadata(1.0, GeometryChanged));
protected static void GeometryChanged(object sender, DependencyPropertyChangedEventArgs e)
{
((PointVisual3D)sender).GeometryDirty();
}
public double Size
{
get
{
return (double)this.GetValue(SizeProperty);
}
set
{
this.SetValue(SizeProperty, value);
}
}
//private void AddSegment(Point3DCollection positions, Point3D startPoint, Point3D endPoint, double halfThickness)
//{
// Vector3D lineDirection = endPoint * _visualToScreen - startPoint * _visualToScreen;
// lineDirection.Z = 0;
// lineDirection.Normalize();
// Vector delta = new Vector(-lineDirection.Y, lineDirection.X);
// delta *= halfThickness;
// Point3D pOut1, pOut2;
// Widen(startPoint, delta, out pOut1, out pOut2);
// positions.Add(pOut1);
// positions.Add(pOut2);
// Widen(endPoint, delta, out pOut1, out pOut2);
// positions.Add(pOut1);
// positions.Add(pOut2);
//}
//private void Widen(Point3D pIn, Vector delta, out Point3D pOut1, out Point3D pOut2)
//{
// Point4D pIn4 = (Point4D)pIn;
// Point4D pOut41 = pIn4 * _visualToScreen;
// Point4D pOut42 = pOut41;
// pOut41.X += delta.X * pOut41.W;
// pOut41.Y += delta.Y * pOut41.W;
// pOut42.X -= delta.X * pOut42.W;
// pOut42.Y -= delta.Y * pOut42.W;
// pOut41 *= _screenToVisual;
// pOut42 *= _screenToVisual;
// pOut1 = new Point3D(pOut41.X / pOut41.W, pOut41.Y / pOut41.W, pOut41.Z / pOut41.W);
// pOut2 = new Point3D(pOut42.X / pOut42.W, pOut42.Y / pOut42.W, pOut42.Z / pOut42.W);
//}
private bool UpdateTransforms()
{
Viewport3DVisual viewport;
bool success;
Matrix3D visualToScreen = MathUtils.TryTransformTo2DAncestor(this, out viewport, out success);
if (!success || !visualToScreen.HasInverse)
{
_mesh.Positions = null;
return false;
}
if (visualToScreen == _visualToScreen)
{
return false;
}
_visualToScreen = _screenToVisual = visualToScreen;
_screenToVisual.Invert();
return true;
}
}

how to deform an ellipse in running time

i first sorry about my english,i´ll try to explain want i want to do
i need to draw an ellipse with wpf that represents an aura and it´s "deformations" representing problematic zones in it,in short an ellipse that can be deformed in running time in specific points
I'm trying to draw several bezier curves forming an ellipse but it´t very difficult (and i don´t know how) to make points that can be dragged forming convex or hollow zones in that ellipse.
¿i made myselft clear in my spanglish? ¿is there an easy way to do that?
Thanks in advance
I don't know what exactly you are trying to do but I recommend making a high resolution version of the ellipse and keeping track of the deformations yourself. Here is a sample program to get you started.
For this demo the XAML is simple:
<Canvas Name="canvas" Focusable="True" KeyDown="canvas_KeyDown" MouseDown="canvas_MouseDown" MouseMove="canvas_MouseMove" MouseUp="canvas_MouseUp"/>
and a the code-behind:
public partial class EllipseDemo : Window
{
const int resolution = 1000;
const double major = 150;
const double minor = 100;
const double xOrigin = 200;
const double yOrigin = 200;
const double radius = 10;
const double scale = 0.1;
const double spread = 10;
const double magnitude = 10;
Path path;
Ellipse controlPoint;
LineSegment[] segments;
double[] deformation;
double[] perturbation;
int controlPointIndex;
public EllipseDemo()
{
InitializeComponent();
segments = new LineSegment[resolution];
deformation = new double[resolution];
perturbation = new double[resolution];
for (int i = 0; i < resolution; i++)
{
var x = i >= resolution / 2 ? i - resolution : i;
perturbation[i] = magnitude * Math.Exp(-Math.Pow(scale * x, 2) / spread);
}
path = new Path();
path.Stroke = new SolidColorBrush(Colors.Black);
path.StrokeThickness = 5;
CalculateEllipse();
canvas.Children.Add(path);
controlPoint = new Ellipse();
controlPoint.Stroke = new SolidColorBrush(Colors.Red);
controlPoint.Fill = new SolidColorBrush(Colors.Transparent);
controlPoint.Width = 2 * radius;
controlPoint.Height = 2 * radius;
MoveControlPoint(0);
canvas.Children.Add(controlPoint);
canvas.Focus();
}
void CalculateEllipse()
{
for (int i = 0; i < resolution; i++)
{
double angle = 2 * Math.PI * i / resolution;
double x = xOrigin + Math.Cos(angle) * (major + deformation[i]);
double y = yOrigin + Math.Sin(angle) * (minor + deformation[i]);
segments[i] = new LineSegment(new Point(x, y), true);
}
var figure = new PathFigure(segments[0].Point, segments, true);
var figures = new PathFigureCollection();
figures.Add(figure);
var geometry = new PathGeometry();
geometry.Figures = figures;
path.Data = geometry;
}
void MoveControlPoint(int index)
{
controlPointIndex = index;
Canvas.SetLeft(controlPoint, segments[index].Point.X - radius);
Canvas.SetTop(controlPoint, segments[index].Point.Y - radius);
}
bool mouseDown;
void canvas_MouseDown(object sender, MouseButtonEventArgs e)
{
if (Mouse.DirectlyOver != controlPoint)
return;
mouseDown = true;
controlPoint.CaptureMouse();
}
void canvas_MouseMove(object sender, MouseEventArgs e)
{
if (!mouseDown)
return;
int index = FindNearestIndex(e.GetPosition(canvas));
MoveControlPoint(index);
}
void canvas_MouseUp(object sender, MouseButtonEventArgs e)
{
if (!mouseDown)
return;
controlPoint.ReleaseMouseCapture();
mouseDown = false;
}
private void canvas_KeyDown(object sender, KeyEventArgs e)
{
int delta = 0;
switch (e.Key)
{
case Key.Up:
delta = 1;
break;
case Key.Down:
delta = -1;
break;
}
if (delta == 0)
return;
int index = controlPointIndex;
for (int i = 0; i < resolution; i++)
deformation[(i + index) % resolution] += delta * perturbation[i];
CalculateEllipse();
MoveControlPoint(index);
}
int FindNearestIndex(Point point)
{
var min = double.PositiveInfinity;
var index = -1;
for (int i = 0; i < segments.Length; i++)
{
var vector = point - segments[i].Point;
var distance = vector.LengthSquared;
if (distance < min)
{
index = i;
min = distance;
}
}
return index;
}
}
This works mostly with a Path represented by line segments and an Ellipse as a control point. The mouse can move the control point around the ellipse and then the arrow keys add or remove a canned perturbation. Everything is hard coded but if you are OK with the math then it should help you get started.
Here's the program in action:

Resources