WPF Different behaviour when drawing point versus line - c#

I am attempting to draw a line from the center of an image on a canvas to the mouse's position when the scroll wheel is moved.
I have a function that looks like this:
// e is MouseWheelEventArgs
var position = e.GetPosition(canvas);
var x = Canvas.GetLeft(image) + image.ActualWidth / 2;
var y = Canvas.GetTop(image) + image.ActualHeight / 2;
Ellipse point = new Ellipse
{
Margin = new Thickness(x, y, 0, 0)
};
Line line = new Line
{
X1 = position.X,
Y1 = position.Y,
X2 = x,
Y2 = y
};
canvas.Children.Add(point);
canvas.Children.Add(line);
The point is drawn correctly at the pointer's location, and the line is drawn from the center of the image, but the point the line is drawn to is incorrect. Why is this?
Here is an image to show the location of the point and the line

I would suggest to implement this with Geometries.
With a XAML like this
<Canvas Background="Transparent" MouseWheel="Canvas_MouseWheel">
<Image x:Name="image" Canvas.Left="0" Canvas.Top="0" Source="..."/>
<Path x:Name="line" Stroke="Green" StrokeThickness="2"/>
<Path x:Name="point" Fill="Red"/>
</Canvas>
the event handler could look like this:
private void Canvas_MouseWheel(object sender, MouseWheelEventArgs e)
{
var position = e.GetPosition((Canvas)sender);
var center = new Point(
Canvas.GetLeft(image) + image.ActualWidth / 2,
Canvas.GetTop(image) + image.ActualHeight / 2);
line.Data = new LineGeometry(center, position);
point.Data = new EllipseGeometry(position, 3, 3);
}

Related

Get a grid to rotate on mouse drag

How do get a grid to rotate on mouse drag as it shows in the gif in WPF c#?
I've tried this code but I'm getting a very bad result and I do not have any idea how should I do the sizing.
double angle = 30;
Point oldPoint;
Point newPoint;
int d;
RotateTransform transe = new RotateTransform();
private void Border_MouseMove(object sender, MouseEventArgs e)
{
Grid b = sender as Grid;
if (Mouse.Captured == b)
{
newPoint = Mouse.GetPosition(mu);
if (oldPoint.Y != newPoint.Y)
{
if (oldPoint.Y > newPoint.Y)
transe.Angle = (oldPoint.Y - newPoint.Y);
else
transe.Angle += (newPoint.Y - oldPoint.Y);
gt.RenderTransform = transe;
}
return;
}
}
private void Border_MouseLeftButtonDown_2(object sender, MouseButtonEventArgs e)
{
Grid b = sender as Grid;
if (Mouse.Captured != b)
{
oldPoint = Mouse.GetPosition(mu);
Mouse.Capture(b);
}
}
private void Border_MouseLeftButtonUp_1(object sender, MouseButtonEventArgs e)
{
oldPoint = new Point(0, 0);
newPoint = new Point(0, 0);
Mouse.Capture(null);
}
The contol in xaml:
<Grid x:Name="mu" Height="90" Width="128" MouseUp="mu_MouseUp" MouseMove="Drase_MouseMove"
Canvas.Left="30" Canvas.Top="45" Cursor="Arrow" Background="White">
<Grid x:Name="gt" HorizontalAlignment="Left" Height="6" VerticalAlignment="Center"
Background="Black" Width="{Binding ActualWidth, ElementName=mu, Mode=OneWay}"
MouseMove="Border_MouseMove" MouseLeftButtonDown="Border_MouseLeftButtonDown_2"
MouseLeftButtonUp="Border_MouseLeftButtonUp_1" RenderTransformOrigin="0.5,0.5"/>
</Grid>
This is what I have managed to get so far. As you can see it's rotating uncontrollably.
Your approach isn't quite right. Here's my working version of the code:
private void Border_MouseMove(object sender, MouseEventArgs e)
{
Grid b = sender as Grid;
if (Mouse.Captured == b)
{
Point origin = new Point(mu.ActualWidth / 2, mu.ActualHeight / 2);
var rawPoint = Mouse.GetPosition(mu);
var transPoint = new Point(rawPoint.X - origin.X, rawPoint.Y - origin.Y);
var radians = Math.Atan2(transPoint.Y, transPoint.X);
var angle = radians * (180 / Math.PI);
transe.Angle = angle;
gt.RenderTransform = transe;
}
}
I'm not the best when it comes to the math, but I'll try to what I'm doing. The idea behind my code is that the angle of the rotate transform should always match the angle of the mouse relative to the vertical center-line of the mu.
Point origin = new Point(mu.ActualWidth / 2, mu.ActualHeight / 2); gives me the center of the mu, which is the mathematical origin point for the calculations.
var transPoint = new Point(rawPoint.X - origin.X, rawPoint.Y - origin.Y); translates the location of the mouse cursor so that it's relative to the center of mu (i.e. origin), as opposed to the top-left corner.
Then, stealing a bit of code I don't quite understand from this answer, I use Atan2 to calculate the angle.

WPF C# coded lines doesn't recognize coordinates origin

I'm exploring graphics capabilities of WPF+C#. Drawing a line with XAML makes what expected. ok.
To draw a simple line with C# code makes something "strange". The coordinates appear not where
they should be. Why? The following code should implement a diagonal from (0, 0) to (width, height) of
the container panel of the line.
private void buttonStart_Click(object sender, RoutedEventArgs e)
{
Line line1 = new Line
{
Stroke = Brushes.Black
};
Thickness thickness = new Thickness(10, -10, 36, 250);
line1.Margin = thickness;
line1.Visibility = Visibility.Visible;
line1.StrokeThickness = 4;
line1.X1 = 0;
line1.Y1 = 0;
line1.X2 = MainGrid.ActualWidth;
line1.Y2 = MainGrid.ActualHeight;
line1.HorizontalAlignment = HorizontalAlignment.Left;
MainGrid.Children.Add(line1);
}
It doesn't matter what panel you use: canvas, dockpanel, grid, stackpanel, I observed the same
strange and annoying behavior.
Not showing the diagonal. Not beginning at origin-vertex (0,0): (left,top)
Your problem is Thickness(10, -10, 36, 250). This arbitrary margin is cutting off the rest of the line. Here is a simplified version of your code that correctly draws a line from the top-left corner, to the bottom-right:
private void buttonStart_Click(object sender, RoutedEventArgs e)
{
Line line1 = new Line
{
Stroke = Brushes.Black,
StrokeThickness = 4,
X1 = 0,
Y1 = 0,
X2 = MainGrid.ActualWidth,
Y2 = MainGrid.ActualHeight
};
MainGrid.Children.Add(line1);
}
Note that Visibility = Visibility.Visible is left because Line (like most other elements) are visible by default.
You can do this much easier with a simple path statement - use Stretch="Fill" so there's no need to worry about dimensions
<Path Stroke="Black" StrokeThickness="4" Data="M 0,0 L 1,1" Stretch="Fill" />

How to set Thickness value for Margin in doubleanimation (code not XAML) [duplicate]

Does anyone know of an easy way to animate a movement from an Image's current location to a new location (X,Y) using WPF animation with no XAML, 100% programmatically? And with no reference to "this" (with RegisterName etc).
I am trying to make an extension class for Image to do animation stuff on it. It is easy enough to change the width and height properties through animation, but after searching for location animation of an object it suddenly becomes more advanced.
As it is an extension class I will only have a reference to the actual Image object and the X and Y I want to move it to.
public static void MoveTo(this Image targetControl, double X, double Y, double Width, double Height){
//code here
...
}
Update:
Thanks. Almost working. It seems The GetTop and GetLeft returns 'NaN' not explicitly set. Found the workaround in this post: Canvas.GetTop() returning NaN
public static void MoveTo(this Image target, double newX, double newY) {
Vector offset = VisualTreeHelper.GetOffset(target);
var top = offset.Y;
var left = offset.X;
TranslateTransform trans = new TranslateTransform();
target.RenderTransform = trans;
DoubleAnimation anim1 = new DoubleAnimation(0, newY - top, TimeSpan.FromSeconds(10));
DoubleAnimation anim2 = new DoubleAnimation(0, newX - left, TimeSpan.FromSeconds(10));
trans.BeginAnimation(TranslateTransform.YProperty, anim1);
trans.BeginAnimation(TranslateTransform.XProperty, anim2);
}
I had to swap two of the values (FROM) with 0. I assume that must be because in this context the upper left corner of the picture is the origin? But now it works.
Try this:
public static void MoveTo(this Image target, double newX, double newY)
{
var top = Canvas.GetTop(target);
var left = Canvas.GetLeft(target);
TranslateTransform trans = new TranslateTransform();
target.RenderTransform = trans;
DoubleAnimation anim1 = new DoubleAnimation(top, newY - top, TimeSpan.FromSeconds(10));
DoubleAnimation anim2 = new DoubleAnimation(left, newX - left, TimeSpan.FromSeconds(10));
trans.BeginAnimation(TranslateTransform.XProperty,anim1);
trans.BeginAnimation(TranslateTransform.YProperty,anim2);
}
Here it is... It changes the size and moves a MediaElement under the Canvas. Just input your parameters:
Storyboard story = new Storyboard();
DoubleAnimation dbWidth = new DoubleAnimation();
dbWidth.From = mediaElement1.Width;
dbWidth.To = 600;
dbWidth.Duration = new Duration(TimeSpan.FromSeconds(.25));
DoubleAnimation dbHeight = new DoubleAnimation();
dbHeight.From = mediaElement1.Height;
dbHeight.To = 400;
dbHeight.Duration = dbWidth.Duration;
story.Children.Add(dbWidth);
Storyboard.SetTargetName(dbWidth, mediaElement1.Name);
Storyboard.SetTargetProperty(dbWidth, new PropertyPath(MediaElement.WidthProperty));
story.Children.Add(dbHeight);
Storyboard.SetTargetName(dbHeight, mediaElement1.Name);
Storyboard.SetTargetProperty(dbHeight, new PropertyPath(MediaElement.HeightProperty));
DoubleAnimation dbCanvasX = new DoubleAnimation();
dbCanvasX.From = 0;
dbCanvasX.To = 5;
dbCanvasX.Duration = new Duration(TimeSpan.FromSeconds(.25));
DoubleAnimation dbCanvasY = new DoubleAnimation();
dbCanvasY.From = 0;
dbCanvasY.To = 5;
dbCanvasY.Duration = dbCanvasX.Duration;
story.Children.Add(dbCanvasX);
Storyboard.SetTargetName(dbCanvasX, mediaElement1.Name);
Storyboard.SetTargetProperty(dbCanvasX, new PropertyPath(Canvas.LeftProperty));
story.Children.Add(dbCanvasY);
Storyboard.SetTargetName(dbCanvasY, mediaElement1.Name);
Storyboard.SetTargetProperty(dbCanvasY, new PropertyPath(Canvas.TopProperty));
story.Begin(this);
<Viewbox Stretch="Uniform" StretchDirection="Both" SnapsToDevicePixels="True">
<Grid Width="640" Height="480" Name="MainLayout" SnapsToDevicePixels="True" Background="Black">
<Canvas Width="640" Height="480" Name="MainCanvas" SnapsToDevicePixels="True">
<MediaElement Height="171" HorizontalAlignment="Left" Name="mediaElement1" VerticalAlignment="Top" Width="337" LoadedBehavior="Manual" Margin="166,140,0,0" Canvas.Left="-162" Canvas.Top="-140" />
<Button Canvas.Left="294" Canvas.Top="196" Content="Button" Height="23" Name="button1" Width="75" Click="button1_Click" />
</Canvas>
</Grid>
</Viewbox>
UPDATE:
Instead of MediaElement use this line:
<Rectangle Height="171" HorizontalAlignment="Left" Name="mediaElement1" VerticalAlignment="Top" Width="337" Margin="166,140,0,0" Canvas.Left="-162" Canvas.Top="-140" Fill="{DynamicResource {x:Static SystemColors.MenuBarBrushKey}}" />
And don't forget to put the C# code to:
private void button1_Click(object sender, RoutedEventArgs e) {}
You can use MediaElement as well but you have to define a VideoClip to see something ;)
Please find a solution that uses the Left and Top properties of Canvas for the extension method. See the following code:
public static void MoveTo(this Image target, Point newP)
{
Point oldP = new Point();
oldP.X = Canvas.GetLeft(target);
oldP.Y = Canvas.GetTop(target);
DoubleAnimation anim1 = new DoubleAnimation(oldP.X, newP.X, TimeSpan.FromSeconds(0.2));
DoubleAnimation anim2 = new DoubleAnimation(oldP.Y, newP.Y , TimeSpan.FromSeconds(0.2));
target.BeginAnimation(Canvas.LeftProperty , anim1);
target.BeginAnimation(Canvas.TopProperty, anim2);
}
This code is based on #DeanChalk's answer.
It moves an Image contained within a Canvas (RFID_Token) diagonally from the top-right to the bottom-left, positioned centrally over another Image within a Canvas (RFID_Reader).
<Canvas>
<Canvas x:Name="RFID_Reader_Canvas">
<Image x:Name="RFID_Reader" Source="RFID-Reader.png" Height="456" Width="682" Canvas.Left="37" Canvas.Top="524"/>
</Canvas>
<Canvas x:Name="RFID_Token_Canvas">
<Image x:Name="RFID_Token" Source="RFID-Token.png" Height="268" Width="343" Canvas.Left="874" Canvas.Top="70"/>
</Canvas>
</Canvas>
var StartX = Canvas.GetLeft(RFID_Token);
var StartY = Canvas.GetTop(RFID_Token);
var EndX = RFID_Reader.Width / 2 + Canvas.GetLeft(RFID_Reader) - StartX - (RFID_Token.Width / 2);
var EndY = RFID_Reader.Height / 2 + Canvas.GetTop(RFID_Reader) - StartY - (RFID_Token.Height / 2);
var AnimationX = new DoubleAnimation(0, EndX, TimeSpan.FromSeconds(1));
var AnimationY = new DoubleAnimation(0, EndY, TimeSpan.FromSeconds(1));
var Transform = new TranslateTransform();
RFID_Token_Canvas.RenderTransform = Transform;
Transform.BeginAnimation(TranslateTransform.XProperty, AnimationX);
Transform.BeginAnimation(TranslateTransform.YProperty, AnimationY);
I kept having NaN or 0 values for my nested elements, here's a modified version of Danny's answer :
public void MoveTo(Canvas canvas, FrameworkElement target, FrameworkElement destination)
{
Point oldPoint = target.TransformToAncestor(canvas).Transform(new Point(0, 0));
Point newPoint = destination.TransformToAncestor(canvas).Transform(new Point(0, 0));
var EndX = destination.Width / 2 + newPoint.X - oldPoint.X - (target.Width / 2);
var EndY = destination.Height / 2 + newPoint.Y - oldPoint.Y - (target.Height / 2);
TranslateTransform trans = new TranslateTransform();
target.RenderTransform = trans;
DoubleAnimation anim1 = new DoubleAnimation(0, EndX, TimeSpan.FromSeconds(0.3));
DoubleAnimation anim2 = new DoubleAnimation(0, EndY, TimeSpan.FromSeconds(0.3));
trans.BeginAnimation(TranslateTransform.XProperty, anim1);
trans.BeginAnimation(TranslateTransform.YProperty, anim2);
}

Restrict Panning in specific border

I need help to bound the path or canvas in a boundary. What I want is, when I click the mouse-left-button, holding it and move for panning. It should not move when mouse pointer reach some boundary as BORDER. I'll add code here please help me out
XAML code:
<Border x:Name="OutMoastBorder" Height="820" Width="820" ClipToBounds="True" BorderThickness="2" BorderBrush="Black" Block.IsHyphenationEnabled="True">
<Border x:Name="clipBorder" Height="810" Width="810" BorderThickness="2" BorderBrush="Black" ClipToBounds="True">
<Canvas x:Name="CanvasPanel" Height="800" Width="800" Background="Beige">
</Canvas>
</Border>
</Border>
<Grid>
<Button Content="Original Size" Height="23" Name="btn_Original" Width="75" Click="btn_Original_Click" Margin="4,4,921,973" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="4,59,0,0" Name="txtNoOfZones" VerticalAlignment="Top" Width="120" MaxLength="2" PreviewTextInput="txtNoOfZones_PreviewTextInput" />
<Label Content="Enter a number below for No. of Zones" Height="28" HorizontalAlignment="Left" Margin="4,33,0,0" Name="label1" VerticalAlignment="Top" Width="220" FontFamily="Vijaya" FontSize="15" FontWeight="Bold" FontStyle="Normal" />
<Button Content="Zones" Height="23" HorizontalAlignment="Left" Margin="130,58,0,0" Name="btnNoOfZones" VerticalAlignment="Top" Width="41" Click="btnNoOfZones_Click" />
</Grid>
</Grid>
Code behind for zooming and panning:
void Zoom_MouseWheel(object sender, MouseWheelEventArgs e)
{
Point p = e.MouseDevice.GetPosition(((Path)sender));
Matrix m = CanvasPanel.RenderTransform.Value;
if (e.Delta > 0)
m.ScaleAtPrepend(1.1, 1.1, p.X, p.Y);
else
m.ScaleAtPrepend(1 / 1.1, 1 / 1.1, p.X, p.Y);
CanvasPanel.RenderTransform = new MatrixTransform(m);
// CanvasPanel.RenderTransformOrigin = new Point(0.5, 0.5);
}
private Point origin;
private Point start;
void Pan_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (((Path)sender).IsMouseCaptured) return;
((Path)sender).CaptureMouse();
start = e.GetPosition(clipBorder);
origin.X = CanvasPanel.RenderTransform.Value.OffsetX;
origin.Y = CanvasPanel.RenderTransform.Value.OffsetY;
}
void Pan_MouseLeftBtnUp(object sender, MouseButtonEventArgs e)
{
((Path)sender).ReleaseMouseCapture();
}
void Pan_MouseMove(object sender, MouseEventArgs e)
{
if (!((Path)sender).IsMouseCaptured) return;
Point p = e.MouseDevice.GetPosition(clipBorder);
Matrix m = CanvasPanel.RenderTransform.Value;
m.OffsetX = origin.X + (p.X - start.X);
m.OffsetY = origin.Y + (p.Y - start.Y);
CanvasPanel.RenderTransform = new MatrixTransform(m);
}
private const int NoOfSectors = 180;
private const int NoOfZones = 5;
void OnLoaded(object sender, RoutedEventArgs args)
{
const double PIES = 2 * Math.PI / NoOfSectors;
Point center = new Point(CanvasPanel.ActualWidth / 2, CanvasPanel.ActualHeight / 2); // center (x,y) Co-ordinates to get center of canvas
double radius = 0.1 * Math.Min(CanvasPanel.ActualWidth, CanvasPanel.ActualHeight);
for (int s = 0; s <= NoOfSectors; s++)
{
for (int z = 1; z <= NoOfZones; z++)
{
Path path = new Path();
PathGeometry pathGeo = new PathGeometry();
PathFigure pathFig = new PathFigure();
double radians = 2 * Math.PI * s / NoOfSectors;
double outerRadius = radius * z;
double innerRadius = radius * ((z - 1));
Size outerArcSize = new Size(outerRadius, outerRadius);
Size innerArcSize = new Size(innerRadius, innerRadius);
Point p1_1st_LineSegment; //------------------------------> Points variable, to store each iterate point in these.
Point p2_1st_ArcSegment;
Point p3_2nd_LineSegment;
Point p4_2nd_ArcSegment;
p1_1st_LineSegment = new Point(center.X + innerRadius * Math.Cos(radians - PIES), center.Y - innerRadius * Math.Sin(radians - PIES)); // point for LINE from Center
p2_1st_ArcSegment = new Point(center.X + innerRadius * Math.Cos(radians), center.Y - innerRadius * Math.Sin(radians)); // point for ARC after the 1st LINE formation
p3_2nd_LineSegment = new Point(center.X + outerRadius * Math.Cos(radians), center.Y - outerRadius * Math.Sin(radians)); // Point for 2nd LINE after forming both LINE abd ARC
p4_2nd_ArcSegment = new Point(center.X + outerRadius * Math.Cos(radians - PIES), center.Y - outerRadius * Math.Sin(radians - PIES)); // Point for 2nd ARC which is Counter-CLockwise that closes a path
pathFig.StartPoint = center;
pathFig.Segments.Add(new LineSegment(p1_1st_LineSegment, true));
pathFig.Segments.Add(new ArcSegment(p2_1st_ArcSegment, innerArcSize, 1, false, SweepDirection.Clockwise, true));
pathFig.Segments.Add(new LineSegment(p3_2nd_LineSegment, true));
pathFig.Segments.Add(new ArcSegment(p4_2nd_ArcSegment, outerArcSize, 1, false, SweepDirection.Counterclockwise, true));
pathFig.IsClosed = false; //false because, path has to be close with ARC, not with LINE
pathFig.IsFilled = true;
pathGeo.Figures.Add(pathFig); // binding data to a Geometry
pathGeo.FillRule = FillRule.Nonzero;
path.Data = pathGeo; // binding whole geometry data to a path
path.Stroke = Brushes.Black;
path.Fill = Brushes.Silver;
path.StrokeThickness = 0.1;
// CanvasPanel.RenderTransformOrigin = new Point(0.5, 0.5); //--------------------> this makes "Canvas" to be Zoom from center
CanvasPanel.Children.Add(path); // binding to a CanvasPanel as a children
path.MouseLeftButtonDown += MouseLeftButtonClick; // calling Mouse-click-event
path.MouseWheel += Zoom_MouseWheel;
path.MouseLeftButtonDown += Pan_MouseLeftButtonDown;
path.MouseLeftButtonUp += Pan_MouseLeftBtnUp;
path.MouseMove += Pan_MouseMove;
}
}
Please help me out.
Regards,
Viswanath.
PLz replace the MouseMove event with this following code in code behind.
void Pan_MouseMove(object sender, MouseEventArgs e)
{
if (!((Path)sender).IsMouseCaptured) return;
Point p = e.MouseDevice.GetPosition(clipBorder);
if (p.X > 0 && p.Y > 0 && p.X < clipBorder.ActualWidth && p.Y < clipBorder.ActualHeight)
{
Matrix m = CanvasPanel.RenderTransform.Value;
m.OffsetX = origin.X + (p.X - start.X);
m.OffsetY = origin.Y + (p.Y - start.Y);
CanvasPanel.RenderTransform = new MatrixTransform(m);
}
}
I have tested, this will surely solve this.
regards,
Viswa

Draw vertical lines in wpf

I have written following code for generating lines on canvas
XAML
<Canvas HorizontalAlignment="Left"
x:Name="canvas1" Height="219"
Margin="10,10,0,0" Grid.Row="1"
VerticalAlignment="Top" Width="365"/>
C#
private void Draw()
{
canvas1.Children.Clear();
for (int i = 0; i < data.Length; i++)
{
data[i] = i;
lines[i] = new Line()
{
X1 = leftMargin,
Y1 = i * scale,
X2 = i * scale,
Y2 = i * scale,
StrokeThickness = 2,
Stroke = new SolidColorBrush(Colors.Black)
};
canvas1.Children.Add(lines[i]);
}
}
But I want to draw lines as below.How I can rotatle the canvas to achieve the desired output
x = 0 and y = 0 is upper left corner (not lower left) so y is like up side down
private void Draw()
{
Line[] lines = new Line[100];
int scale = 3;
canvas1.Children.Clear();
int yStart = 290;
for (int i = 0; i < lines.Length; i++)
{
//data[i] = i;
lines[i] = new Line()
{
X1 = i * scale,
Y1 = yStart,
X2 = i * scale,
Y2 = 300 - (i * scale),
StrokeThickness = 1,
Stroke = new SolidColorBrush(Colors.Black)
};
canvas1.Children.Add(lines[i]);
}
}
<Canvas.RenderTransform>
<RotateTransform CenterX="110" CenterY="183" Angle="270" />
</Canvas.RenderTransform>
or by code:
Canvas.RenderTransform = new RotateTransform(270, 109.5, 182.5);
Something like this?
If you want to rotate the canvas, you can simply apply a transform on it:
<Canvas.RenderTransform>
<RotateTransform CenterX="110" CenterY="183" Angle="270" />
</Canvas.RenderTransform>
If you want to do as John Willemse suggested change your code to this :
X1 = i * scale,
Y1 = bottomMargin,
X2 = i * scale,
Y2 = i * scale,

Categories