Pivot point for positioning Path in C# WPF canvas - c#

so i have this Path and i need to place it rotated by its center in a coordinate. so i have this static Path in .xaml
<Path Stroke="Black" RenderTransformOrigin="0.379,0.494" Canvas.Left="30" Canvas.Top="0">
<Path.RenderTransform>
<TransformGroup>
<RotateTransform Angle="-38.28"/>
<TranslateTransform X="-30" Y="-30"/>
</TransformGroup>
</Path.RenderTransform>
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure StartPoint="75,30">
<PathFigure.Segments>
<PathSegmentCollection>
<LineSegment Point="0,0"/>
<LineSegment Point="12,30"/>
<LineSegment Point="0, 60"/>
<LineSegment Point="75, 30"/>
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
but then i create it from code the same way and it just doesn't wanna center, it just starts rotating around the point
{
PathGeometry pathGeom = new PathGeometry();
Sprite = new Path
{
Data = pathGeom,
RenderTransformOrigin = new Point(0.379, 0.5),
Stroke = new SolidColorBrush(Color.FromArgb(120, 0, 0, 0)),
Fill = new SolidColorBrush(Color.FromArgb(120, 30, 200, 7))
};
var pf = new PathFigure { StartPoint = new Point(75, 30) };
pf.Segments.Add(new LineSegment { Point = new Point(0, 0) });
pf.Segments.Add(new LineSegment { Point = new Point(12, 30) });
pf.Segments.Add(new LineSegment { Point = new Point(0, 60) });
pf.Segments.Add(new LineSegment { Point = new Point(75, 30) });
pathGeom.Figures.Add(pf);
UpdateRotation();
canvas.Children.Add(Sprite);
}
private void UpdateRotation()
{
Sprite.RenderTransform = new TransformGroup
{
Children = new TransformCollection {
new RotateTransform(Road.Angle + (Direction == -1 ? 90 : 0), -30, -30) , <-- i tried doing this
new TranslateTransform(-30, -30), <-- and this separately, but they both didn't work
}
};
Canvas.SetLeft(Sprite, Loc.X);
Canvas.SetTop(Sprite, Loc.Y);
}
here's what the static path looks like:what it looks like

You may simplify your drawing by defining the pivot point as origin - with coordinates (0,0). There is no need for a TranslateTransform or a centered RotateTransform. You would also not have to create new transforms on each direction update. Just set the Angle property of the existing RotateTransform.
private Path Sprite { get; }
public MainWindow()
{
InitializeComponent();
Sprite = new Path
{
Data = Geometry.Parse("M0,0 L-12,-30 63,0 -12,30Z"),
// or Data = Geometry.Parse("M-13,0 L-25,-30 50,0 -25,30Z"),
// or whatever corresponds to the original RenderTransformOrigin
RenderTransform = new RotateTransform(),
Stroke = new SolidColorBrush(Color.FromArgb(120, 0, 0, 0)),
Fill = new SolidColorBrush(Color.FromArgb(120, 30, 200, 7))
};
canvas.Children.Add(Sprite);
UpdatePosition(100, 100, 45); // for example
}
private void UpdatePosition(double x, double y, double direction)
{
((RotateTransform)Sprite.RenderTransform).Angle = direction;
Canvas.SetLeft(Sprite, x);
Canvas.SetTop(Sprite, y);
}

Related

ArcSegment C# Formula To Get A Specific Point on Arc WPF

I have arc in a canvas drawn from Path with the following details. I want to get Point(115,225). Please see screenshot to get more details. Please help me getting the formula to get to point (115,225).
startX=250
startY=250
ArcSegment Size (70,70)
ArcSegment Point (250,200)
Computation
var meanX=(startX+startX)/2-rX;//(250+250)/2-70=180
var meanY=(startY+ArcSegment.Point.Y)/2-rY;//(250+200)/2-70=225
//center Point (180,225)
//What is the formula if I want to get Point(115,225)
XAML
<Canvas Name="canvas" Background="White" Opacity="99">
<Path Stroke="Blue" MouseLeftButtonDown="Path_MouseLeftButtonDown" >
<Path.Data>
<PathGeometry>
<PathFigureCollection>
<PathFigure StartPoint="250,250" IsClosed="True">
<ArcSegment Size="70,70" IsLargeArc="True" SweepDirection="Clockwise" Point="250,200"/>
</PathFigure>
</PathFigureCollection>
</PathGeometry>
</Path.Data>
</Path>
</Canvas>
C#
private void Path_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var curve = sender as Path;
var geometry = curve.Data as PathGeometry;
var figure = geometry.Figures.FirstOrDefault();
var arcSegment = figure.Segments.FirstOrDefault() as ArcSegment;
var startX = figure.StartPoint.X;
var yStart = figure.StartPoint.Y;
var startAngle = arcSegment.Point.X;
var sweepAngle = arcSegment.Point.Y;
var rX = arcSegment.Size.Width;
var rY = arcSegment.Size.Height;
var endAngle = startAngle + sweepAngle;
var meanX = (startX + startAngle) / 2 - rX;
var meanY = (yStart + sweepAngle) / 2 - rY;
}
Screenshot
After hours of research, I found out everything is here.
var geometry = path.Data as PathGeometry;
var leftX=geometry.Bounds.Location.X;
var topY=geometry.Bounds.Location.Y;

Why are my polygons being drawn in the wrong location?

I am practicing C# basic WPF/XAML drawing for an assignment and right off the bat I cannot figure out why my polygons are being drawn in the wrong place.
My window is of 1280x720 fixed, non-resizeable. I am trying to programmatically create my polygons by:
Creating points in the coordinates I want them to be:
`
[0,0]
[max height, 0],
[max height, max width],
[0, max width],
[max height/2, max width/2]
`
Creating polygons that consists of three points each, [0,0] and two edges. My screen is supposed to be split into four triangles.
I tried breaking down the code to something really explicit to see if I could figure out where the issue is, so this is what I have:
private void CreatePolygons()
{
List<Point> PointList = new List<Point>
{
new Point(MainUI.Height / 2, MainUI.Width / 2),
new Point(0, 0),
new Point(0, MainUI.Height),
new Point(MainUI.Width, MainUI.Height),
new Point(MainUI.Width, 0)
};
Polygon p1 = new Polygon();
Polygon p2 = new Polygon();
Polygon p3 = new Polygon();
Polygon p4 = new Polygon();
p1.Points.Add(PointList[0]);
p1.Points.Add(PointList[1]);
p1.Points.Add(PointList[2]);
p2.Points.Add(PointList[0]);
p2.Points.Add(PointList[2]);
p2.Points.Add(PointList[3]);
p3.Points.Add(PointList[0]);
p3.Points.Add(PointList[3]);
p3.Points.Add(PointList[4]);
p4.Points.Add(PointList[0]);
p4.Points.Add(PointList[4]);
p4.Points.Add(PointList[1]);
p1.Stroke = System.Windows.Media.Brushes.LightSkyBlue;
p2.Stroke = System.Windows.Media.Brushes.LightSkyBlue;
p3.Stroke = System.Windows.Media.Brushes.LightSkyBlue;
p4.Stroke = System.Windows.Media.Brushes.LightSkyBlue;
p1.StrokeThickness = 1;
p2.StrokeThickness = 1;
p3.StrokeThickness = 1;
p4.StrokeThickness = 1;
MainGrid.Children.Add(p1);
MainGrid.Children.Add(p2);
MainGrid.Children.Add(p3);
MainGrid.Children.Add(p4);
}
The end result is a completely misplaced grid and I can't understand what the coordinates it ended up creating refer to:
What am I missing?
You have accidentally swapped the Width and Height in the first point:
new Point(MainUI.Height / 2, MainUI.Width / 2),
Should be:
new Point(MainUI.Width / 2, MainUI.Height / 2),
Further, assuming MainUI is the app window itself, the points will still be a bit off, because the Height of the window includes its title bar height. You should better use MainGrid.ActualWidth and MainGrid.ActualHeight:
List<Point> PointList = new List<Point>
{
new Point(MainGrid.ActualWidth / 2, MainGrid.ActualHeight / 2),
new Point(0, 0),
new Point(0, MainGrid.ActualHeight),
new Point(MainGrid.ActualWidth, MainGrid.ActualHeight),
new Point(MainGrid.ActualWidth, 0)
};
As an alternative to all the Polygon point calcuations, you may use this simple Path element, which produces the same output and stretches automatically:
<Grid>
<Path Stretch="Fill" Stroke="LightSkyBlue" StrokeThickness="1"
Data="M0,0 L1,0 1,1 0,1Z M0,0 L1,1 M0,1 L1,0"/>
</Grid>
Besides that you have confused Width and Height of the first point, I'd suggest not to create UI elements like Polygons in code behind. Better use an ItemsControl like this:
<Grid SizeChanged="MainUISizeChanged">
<ItemsControl x:Name="polygons">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Polygon Stroke="LightSkyBlue" StrokeThickness="1"
Points="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
and assign its ItemsSource property to a collection of PointCollections, e.g. whenever the size of your MainUI element changes:
private void MainUISizeChanged(object sender, SizeChangedEventArgs e)
{
var points = new List<Point>
{
new Point(e.NewSize.Width / 2, e.NewSize.Height / 2),
new Point(0, 0),
new Point(0, e.NewSize.Height),
new Point(e.NewSize.Width, e.NewSize.Height),
new Point(e.NewSize.Width, 0)
};
polygons.ItemsSource = new List<PointCollection>
{
new PointCollection(new Point[] { points[0], points[1], points[2] }),
new PointCollection(new Point[] { points[0], points[2], points[3] }),
new PointCollection(new Point[] { points[0], points[3], points[4] }),
new PointCollection(new Point[] { points[0], points[4], points[1] }),
};
}

Drawing line attached to two shapes

NOTE: I'm not looking for a XAML Solution.
I'm having trouble figuring out how to attach a line to two shapes. The best visible representation of what I'm looking for would be two balls attached to both ends of a straight stick. The problem I'm having is on how to display the line which is dependent on both the positions of ball01's and ball02's center position. As of now, both balls display as I want it, but when ball02 moves away from ball01 (ball02 starts off centered on ball01), the line is not visible.
ball01 = new Ellipse() { Height = BIG_SIZE, Width = BIG_SIZE };
ball01.Fill = baseBrush;
ball01.SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased);
setBall01X(e.GetPosition(canvas).X - (BIG_SIZE / 2));
setBall01Y(e.GetPosition(canvas).Y - (BIG_SIZE / 2));
Canvas.SetLeft(ball01, getBall01X());
Canvas.SetTop(ball01, getBall01Y());
canvas.Children.Add(ball01);
ball02 = new Ellipse() { Height = SMALL_SIZE, Width = SMALL_SIZE };
ball02.Fill = childBrush;
ball02.SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased);
setBall02X(e.GetPosition(canvas).X - (SMALL_SIZE / 2));
setBall02Y(e.GetPosition(canvas).Y - (SMALL_SIZE / 2));
Canvas.SetLeft(ball02, getBall02X());
Canvas.SetTop(ball02, getBall02Y());
canvas.Children.Add(ball02);
// line's X's and Y's are to point to the center of both balls
// Regardless of where the balls move.
line01 = new Line()
{
X1 = getBall01X() + (BIG_SIZE / 2),
Y1 = getBall01Y() + (BIG_SIZE / 2),
X2 = getBall02X() + (SMALL_SIZE / 2),
Y2 = getBall02Y() + (SMALL_SIZE / 2)
};
line01.Fill = baseBrush;
line01.SnapsToDevicePixels = true;
line01.SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased);
line01.StrokeThickness = 2;
// Canvas.Set???
canvas.Children.Add(line01);
Instead of using Ellipse and Line controls and positioning them by Canvas.Left and Canvas.Top you may prefer to use three Path controls with appropriate geometries. Especially the EllipseGeometry provides far easier handling of its center point, compared to an Ellipse control.
private EllipseGeometry ball1Geometry = new EllipseGeometry();
private EllipseGeometry ball2Geometry = new EllipseGeometry();
private LineGeometry lineGeometry = new LineGeometry();
public MainWindow()
{
InitializeComponent();
canvas.Children.Add(new Path
{
Stroke = Brushes.Black,
Data = ball1Geometry
});
canvas.Children.Add(new Path
{
Stroke = Brushes.Black,
Data = ball2Geometry
});
canvas.Children.Add(new Path
{
Stroke = Brushes.Black,
Data = lineGeometry
});
}
...
private void UpdateDrawing(
Point ball1Position, double ball1Radius,
Point ball2Position, double ball2Radius)
{
ball1Geometry.RadiusX = ball1Radius;
ball1Geometry.RadiusY = ball1Radius;
ball1Geometry.Center = ball1Position;
ball2Geometry.RadiusX = ball2Radius;
ball2Geometry.RadiusY = ball2Radius;
ball2Geometry.Center = ball2Position;
lineGeometry.StartPoint = ball1Position;
lineGeometry.EndPoint = ball2Position;
}
Then you may also prefer to do it the WPF way and create the Paths in XAML:
<Canvas>
<Path Stroke="Black">
<Path.Data>
<EllipseGeometry x:Name="ball1Geometry"/>
</Path.Data>
</Path>
<Path Stroke="Black">
<Path.Data>
<EllipseGeometry x:Name="ball2Geometry"/>
</Path.Data>
</Path>
<Path Stroke="Black">
<Path.Data>
<LineGeometry x:Name="lineGeometry"/>
</Path.Data>
</Path>
</Canvas>
I think you'd better draw in two steps :
1) add the 3 figures and store them (when building your window).
2) update the coordinates in an animating loop.
It will be faster / handier than clearing/filling the canvas on each frame.
For your line issue : hook it on circle 1's center, and have it go to circle 2's center :
// new line coordinates :
X1 = Y1 = 0
X2 = Balle02X - Balle01X + ( SMALL_SIZE / 2 )
Y2 = Balle02Y - Balle01Y + ( SMALL_SIZE / 2 )
Canvas.SetTop ( line01, Balle01X + (BIG_SIZE / 2) )
Canvas.SetLeft( line01, Balle01Y + (BIG_SIZE / 2) )

WPF Clipping with a shape

I am trying to create a 3..2..1 countdown in the form of a user control. Something like this. My Idea was to create two rectangles on top of each other, one light and one dark and have a radial circle as the clipper for the dark rectangle. The radial circle would have Angle property animated so it would turn around.
I found an implementation of the radial circle and bound the Clip property of the rectangle on the RenderedGeometry property of my circle. Here is the result :
The red stroke is the shape of my clipper. This seems to be an odd behavior of the clipping but I sort of understand it but I would like to know if there was a way of going around the
fact that my clipped object seems to use the RenderedGeometry in a weird way.
Edit 1 : The effect I am looking for http://www.youtube.com/watch?v=9FPHTo5V2BQ
The simple derived Shape control shown below draws the countdown rectangle. You have to set its Fill (and perhaps Stroke), Width, Height and Angle properties, and you can animate Angle from 0 to 360.
public class CountdownRect : Shape
{
static CountdownRect()
{
WidthProperty.OverrideMetadata(typeof(CountdownRect),
new FrameworkPropertyMetadata((o, e) => ((CountdownRect)o).UpdateGeometry()));
HeightProperty.OverrideMetadata(typeof(CountdownRect),
new FrameworkPropertyMetadata((o, e) => ((CountdownRect)o).UpdateGeometry()));
StrokeLineJoinProperty.OverrideMetadata(typeof(CountdownRect),
new FrameworkPropertyMetadata(PenLineJoin.Round));
}
public static readonly DependencyProperty AngleProperty =
DependencyProperty.Register("Angle", typeof(double), typeof(CountdownRect),
new FrameworkPropertyMetadata((o, e) => ((CountdownRect)o).UpdateGeometry()));
public double Angle
{
get { return (double)GetValue(AngleProperty); }
set { SetValue(AngleProperty, value); }
}
private readonly StreamGeometry geometry = new StreamGeometry();
protected override Geometry DefiningGeometry
{
get { return geometry; }
}
private void UpdateGeometry()
{
if (!double.IsNaN(Width) && !double.IsNaN(Height))
{
var angle = ((Angle % 360d) + 360d) % 360d;
var margin = StrokeThickness / 2d;
var p0 = new Point(margin, margin);
var p1 = new Point(Width - margin, margin);
var p2 = new Point(Width - margin, Height - margin);
var p3 = new Point(margin, Height - margin);
using (var context = geometry.Open())
{
if (angle == 0d)
{
context.BeginFigure(p0, true, true);
context.LineTo(p1, true, false);
context.LineTo(p2, true, false);
context.LineTo(p3, true, false);
}
else
{
var x = p2.X / 2d;
var y = p2.Y / 2d;
var a = Math.Atan2(x, y) / Math.PI * 180d;
var t = Math.Tan(angle * Math.PI / 180d);
context.BeginFigure(new Point(x, y), true, true);
if (angle < a)
{
context.LineTo(new Point(x + y * t, p0.Y), true, false);
context.LineTo(p1, true, false);
context.LineTo(p2, true, false);
context.LineTo(p3, true, false);
context.LineTo(p0, true, false);
}
else if (angle < 180d - a)
{
context.LineTo(new Point(p2.X, y - x / t), true, false);
context.LineTo(p2, true, false);
context.LineTo(p3, true, false);
context.LineTo(p0, true, false);
}
else if (angle < 180d + a)
{
context.LineTo(new Point(x - y * t, p2.Y), true, false);
context.LineTo(p3, true, false);
context.LineTo(p0, true, false);
}
else if (angle < 360d - a)
{
context.LineTo(new Point(p0.X, y + x / t), true, false);
context.LineTo(p0, true, false);
}
else
{
context.LineTo(new Point(x + y * t, p0.Y), true, false);
}
context.LineTo(new Point(x, p0.Y), true, false);
}
}
}
}
}
You can clip your rectangle by using an ArcSegment in the clipping PathGeometry, and animate that ArcSegment's endpoint (Point).
The endpoint can be animated with a PointAnimationUsingPath animation, using an identical ArcSegment as its path. Below is a suggestion based on Charlez Petzold's excellent answer here: Drawing pie slices
<UserControl ... >
<UserControl.Resources>
<Point x:Key="SweepCenter" X="100" Y="100" />
<Size x:Key="SweepRadius" Width="130" Height="130" />
<!-- Start sweeping at twelve o'clock.. -->
<Point x:Key="SweepStart" X="100" Y="-30" />
<!-- ..and keep sweeping clockwise until we're (almost) back at the start point: -->
<Point x:Key="SweepEnd" X="99.99" Y="-30" />
<Storyboard x:Key="Sweeper" RepeatBehavior="Forever" AutoReverse="False" >
<PointAnimationUsingPath Storyboard.TargetName="arc"
Storyboard.TargetProperty="Point"
Duration="0:0:5">
<PointAnimationUsingPath.PathGeometry>
<PathGeometry>
<PathFigure StartPoint="{StaticResource SweepStart}">
<ArcSegment Size="{StaticResource SweepRadius}"
Point="{StaticResource SweepEnd}"
SweepDirection="Clockwise"
IsLargeArc="True" />
</PathFigure>
</PathGeometry>
</PointAnimationUsingPath.PathGeometry>
</PointAnimationUsingPath>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="arc"
Storyboard.TargetProperty="IsLargeArc" >
<DiscreteBooleanKeyFrame KeyTime="0:0:2.5" Value="True" />
<DiscreteBooleanKeyFrame KeyTime="0:0:5" Value="False" />
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</UserControl.Resources>
<Grid Width="200" Height="200" >
<Rectangle Fill="Black" />
<Rectangle Fill="Gray" >
<Rectangle.Triggers>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard Storyboard="{StaticResource Sweeper}" />
</EventTrigger>
</Rectangle.Triggers>
<Rectangle.Clip>
<PathGeometry>
<PathFigure StartPoint="{StaticResource SweepCenter}"
IsClosed="True" >
<LineSegment Point="{StaticResource SweepStart}" />
<ArcSegment x:Name="arc"
Size="{StaticResource SweepRadius}"
Point="{StaticResource SweepStart}"
SweepDirection="Clockwise" />
</PathFigure>
</PathGeometry>
</Rectangle.Clip>
</Rectangle>
</Grid>
</UserControl>

Convert xaml to C# code

I am new at using path in a wpf and I do not know how to convert a segment of xaml code to C# code. Could someone help me with this? I cite following the xaml code and then my attempt to convert it. What the C# code lacks of? One more thing I 'd like to ask is if a grid is enough so as a path to appear in the window.
<Path Stroke="Black" StrokeThickness="1">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure StartPoint="10,100">
<PathFigure.Segments>
<PathSegmentCollection>
<QuadraticBezierSegment Point1="200,200" Point2="300,100" />
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
My C# code:
Path myPath = new Path();
myPath.Stroke = Brushes.Black;
myPath.StrokeThickness = 1
PathGeometry myPathGeometry = new PathGeometry();
myPathGeometry.Figures = new PathFigureCollection();
PathFigure myPathFigure = new PathFigure();
myPathFigure.StartPoint = new Point(10, 100);
myPathFigure.Segments = new PathSegmentCollection();
QuadraticBezierSegment theSegment = new QuadraticBezierSegment();
theSegment.Point1 = new Point(200, 200);
theSegment.Point2 = new Point(100, 300);
myPathFigure.Segments.Add(theSegment);
myPathGeometry.Figures.Add(myPathFigure);
You have to add following line at the end,
myPath.Data = myPathGeometry;
And you should add x:Name to your <Grid> as <Grid x:Name='myGrid'>
And add one more line,
myGrid.Children.Add(myPath);
Your C# code could look a lot like the WPF markup. Just add the path to the control you wish to display it in.
var myPath = new Path
{
Stroke = Brushes.Black,
StrokeThickness = 1.0,
Data = new PathGeometry
{
Figures = new PathFigureCollection
{
new PathFigure
{
StartPoint = new Point(10, 100),
Segments = new PathSegmentCollection
{
new QuadraticBezierSegment
{
Point1 = new Point(200, 200),
Point2 = new Point(300, 100),
},
},
},
},
},
};
myGrid.Children.Add(myPath);

Categories

Resources