I'm having performance issues while drawing a Polyline using mouse down, move, and up events in C# WPF. Please see code. My code is working fine. The problem which I'm facing is, that when ever I want to draw a continuous line after some time the movement of line become slower. I'd like to know if you have a better solution to draw a line in a faster way or if there is a better solution to improve the existing code.
Thanks.
Polyline freeLine;
Point _startPoint;
public void canvas_MouseDown(object sender, MouseButtonEventArgs e)
{
_startPoint = e.GetPosition(canvas);
freeLine = new Polyline();
freeLine.StrokeDashCap = PenLineCap.Square;
freeLine.Stroke = color;
freeLine.StrokeThickness = thickness;
canvas.Children.Add(freeLine);
freeLine.StrokeLineJoin = PenLineJoin.Round;
}
public void canvas_MouseMove(object sender, MouseButtonEventArgs e)
{
Point currentPoint = e.GetPosition(canvas1);
if (_startPoint != currentPoint)
{
arrowfreeLine.Points.Add(currentPoint1);
}
}
public void canvas_MouseUp(object sender, MouseButtonEventArgs e)
{
freeLine = null;
}
What you can do is reduce the number of points by discarding those that are very close to each other. In the example below I remove all points that are within 10 pixels of the previous significant point.
private Brush _color = new SolidColorBrush(Colors.Red);
private double _thickness = 4.0;
private int _previousSignificantPoint = 0;
private Polyline _freeLine;
private void canvas_MouseDown(object sender, MouseButtonEventArgs e)
{
// Capture mouse
canvas.CaptureMouse();
Point startPoint = e.GetPosition(canvas);
_freeLine = new Polyline
{
StrokeDashCap = PenLineCap.Square,
StrokeLineJoin = PenLineJoin.Round,
Stroke = _color,
StrokeThickness = _thickness
};
// Add first point
_freeLine.Points.Add(startPoint);
// make it the first "significant" point
_previousSignificantPoint = 0;
canvas.Children.Add(_freeLine);
}
private void canvas_MouseMove(object sender, MouseEventArgs e)
{
// Make sure the mouse is pressed and we have a polyline to work with
if (_freeLine == null) return;
if (e.LeftButton != MouseButtonState.Pressed) return;
// Get previous significant point to determine distance
Point previousPoint = _freeLine.Points[_previousSignificantPoint];
Point currentPoint = e.GetPosition(canvas);
// If we have a new significant point (distance > 10) remove all intermediate points
if (Distance(currentPoint, previousPoint) > 10)
{
for(int i = _freeLine.Points.Count - 1; i > _previousSignificantPoint; i--)
_freeLine.Points.RemoveAt(i);
// and set the new point as the latest significant point
_previousSignificantPoint = _freeLine.Points.Count;
}
_freeLine.Points.Add(currentPoint);
}
private void canvas_MouseUp(object sender, MouseButtonEventArgs e)
{
// release mouse capture
canvas.ReleaseMouseCapture();
_freeLine = null;
}
private static double Distance(Point pointA, Point pointB)
{
return Math.Sqrt(Math.Pow(pointA.X - pointB.X, 2) + Math.Pow(pointA.Y - pointB.Y, 2));
}
Result:
Related
Hey so I'm currently using this post to create rectangles on a canvas and was wandering how i could make the drawn rectangle 2px larger than the actual rectangle recorded. Is this possible and if so how would i do this have been trying to work it out for a while now would be really useful to finally have a solution.
Point startPos; // mouse-down position
Point currentPos; // current mouse position
bool drawing; // busy drawing
List<Rectangle> rectangles = new List<Rectangle>(); // previous rectangles
private Rectangle getRectangle() {
return new Rectangle(
Math.Min(startPos.X, currentPos.X),
Math.Min(startPos.Y, currentPos.Y),
Math.Abs(startPos.X - currentPos.X),
Math.Abs(startPos.Y - currentPos.Y));
}
private void canevas_MouseDown(object sender, MouseEventArgs e) {
currentPos = startPos = e.Location;
drawing = true;
}
private void canevas_MouseMove(object sender, MouseEventArgs e) {
currentPos = e.Location;
if (drawing) canevas.Invalidate();
}
private void canevas_MouseUp(object sender, MouseEventArgs e) {
if (drawing) {
drawing = false;
var rc = getRectangle();
if (rc.Width > 0 && rc.Height > 0) rectangles.Add(rc);
canevas.Invalidate();
}
}
private void canevas_Paint(object sender, PaintEventArgs e) {
if (rectangles.Count > 0) e.Graphics.DrawRectangles(Pens.Black, rectangles.ToArray());
if (drawing) e.Graphics.DrawRectangle(Pens.Red, getRectangle());
}
just for reference this code came from this post
https://stackoverflow.com/questions/4060446/how-to-draw-rectangle-on-mousedown-move-c-sharp
In the getRectangle method you just need to change the values that you use in the constructor for the Rectangle. Since the third one refers to the width property, you add the number of pixels to Math.Abs(startPos.X - currentPos.X).After this the check if (rc.Width > 0) is useless for obvious reasons.
I have a Windows Form, My task is to add drag function to the form. "VendorMasterList" is a label. I have added mouse move, mouse down and mouse up events to that label.
If i tried to drag, the form moves down and then only able to drag the way i want. My question is why its going down?This is my code
private Point startPoint = new Point(0, 0);
private bool isDragging = false;
private void lblHeader_MouseDown(object sender, MouseEventArgs e)
{
isDragging = true; // _dragging is your variable flag
startPoint = new Point(e.X, e.Y);
}
private void lblHeader_MouseUp(object sender, MouseEventArgs e)
{
isDragging = false;
}
private void lblHeader_MouseMove(object sender, MouseEventArgs e)
{
if (isDragging)
{
Point p = PointToScreen(e.Location);
this.Location = new Point(p.X - this.startPoint.X, p.Y - this.startPoint.Y);
}
}
Is there any way to fix this?
Newly added image
I haven' tried your code, but what I see is only partially correct :-)
You save the initial coordinate, which is relative to the upper left corner of the control that's clicked, but you need to save screen coordinates.
Then the mouse is moved and again you get a point relative to that corner. You're missing a few relevant things:
You need to calculate a delta between the two points based on the screen coordinates, otherwise moving the window underneath the cursor gives you wrong values.
You need to add this delta to the form's current location
You need to save the new mouse position to calculate the next delta correctly
The current mouse position based on screen coordinates can be obtained through Cursor.Position.
So your code should read:
private void lblHeader_MouseDown(object sender, MouseEventArgs e)
{
isDragging = true; // _dragging is your variable flag
startPoint = Cursor.Position;
}
private void lblHeader_MouseUp(object sender, MouseEventArgs e)
{
isDragging = false;
}
private void lblHeader_MouseMove(object sender, MouseEventArgs e)
{
if (isDragging)
{
Point p = Cursor.Position;
int deltaX = p.X - startPoint.X;
int deltaY = p.Y - startPoint.Y;
startPoint = p;
this.Left += deltaX;
this.Top += deltaY;
}
}
I just placed a panel on a form and implented the above, which worked.
#IInspector is right when he says that this approach is actually not very good, as it doesn't take into account that Windows can actually handle all this for you.
An alternative approach (and the better one) I'd take is:
Override the WndProc method as shown below
Done. No more mouse handling
The following is an example of how you could override WndProc to do what you need:
protected override void WndProc(ref Message m)
{
const UInt32 WM_NCHITTEST = 0x0084;
const UInt32 HTCAPTION = 0x2;
bool handled = false;
if (m.Msg == WM_NCHITTEST)
{
if (<cursor is within the caption area>)
{
m.Result = (IntPtr)HTCAPTION;
handled = true;
}
}
if (!handled)
base.WndProc(ref m);
}
To restrict the area in which the window can be moved, you could use the following. Of course, #IInspectable will disagree, but you went this road :-)
private void lblHeader_MouseMove(object sender, MouseEventArgs e)
{
if (isDragging)
{
Point p = Cursor.Position;
int deltaX = p.X - startPoint.X;
int deltaY = p.Y - startPoint.Y;
startPoint = p;
int fX = this.Left + deltaX;
int fY = this.Top + deltaY;
if (fX >= 0 && fX + this.Width < Area.Width) this.Left = fX;
if (fY >= 0 && f> + this.Height < Area.Height) this.Top = fY;
}
}
I'm creating this program in which the user drags/drops a text block into a rectangle, and then store the contents of that specific textblock when he/she drops it into the rectangle.
I figured out how to do the drag/drop, but I just can't figure out how to check if the rectangle contains the textblock.
Here's the code so far:
bool captured = false;
double x_shape, x_canvas, y_shape, y_canvas;
UIElement source = null;
private void shape_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
source = (UIElement)sender;
Mouse.Capture(source);
captured = true;
x_shape = Canvas.GetLeft(source);
x_canvas = e.GetPosition(LayoutRoot).X;
y_shape = Canvas.GetTop(source);
y_canvas = e.GetPosition(LayoutRoot).Y;
}
private void shape_MouseMove(object sender, MouseEventArgs e)
{
if (captured)
{
double x = e.GetPosition(LayoutRoot).X;
double y = e.GetPosition(LayoutRoot).Y;
x_shape += x - x_canvas;
Canvas.SetLeft(source, x_shape);
x_canvas = x;
y_shape += y - y_canvas;
Canvas.SetTop(source, y_shape);
y_canvas = y;
}
}
private void shape_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Mouse.Capture(null);
captured = false;
}
private void rectangle1_MouseEnter(object sender, MouseEventArgs e)
{
if (Mouse.Capture(null))
{
textBox1.Text = "test";
}
}
The "Shape" events apply to the textblock by the way, just for clarification.
I tried to find a shortcut and make it so with the rectangle1_MouseEnter event that if the mouse isn't clicked, then store the values (didn't include the code for the storing values). However, the problem is, this idea doesn't work because textBox1.Text="test" isn't registered, and I don't see why it isn't.
Download the project
I am trying to make a panel with a background color which should be able to be drawn in runtime when the user holds down left mouse button and moves it around. All works find when the user is starting from top left and go to bottom right just like the image shows:
But I want the user to be able to make the panel from bottom right to top left. Just like when you select something on your computer with your mouse
Here is my code for now:
public void parent_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
Point tempLoc = e.Location;
this.Location = new Point
(
Math.Min(this.Location.X, tempLoc.X),
Math.Min(this.Location.Y, tempLoc.Y)
);
this.Size = new Size
(
Math.Abs(this.Location.X - tempLoc.X),
Math.Abs(this.Location.Y - tempLoc.Y)
);
this.Invalidate();
}
}
I think this is where I go wrong, and I simply can't find the right algorithm for it:
this.Size = new Size
(
Math.Abs(this.Location.X - tempLoc.X),
Math.Abs(this.Location.Y - tempLoc.Y)
);
But if I use a rectangle it works fine, but I want my panel to be able to do it as well.
You need to just check the minimums and maximums of your starting point versus your mousemoving point. The problem with the code is you are using the control location as a starting point, but if you move the mouse from bottom-right to top-left, your location needs to change. A control can't have a negative size.
Here is how I re-wrote it (I removed unnecessary stuff for testing):
public class SelectionTool : Panel {
Form parent;
Point _StartingPoint;
public SelectionTool(Form parent, Point startingPoint) {
this.DoubleBuffered = true;
this.Location = startingPoint;
//this.endingPoint = startingPoint;
_StartingPoint = startingPoint;
this.parent = parent;
this.parent.Controls.Add(this);
this.parent.MouseMove += new MouseEventHandler(parent_MouseMove);
this.BringToFront();
this.Size = new Size(0, 0);
}
public void parent_MouseMove(object sender, MouseEventArgs e) {
if (e.Button == MouseButtons.Left) {
int minX = Math.Min(e.Location.X, _StartingPoint.X);
int minY = Math.Min(e.Location.Y, _StartingPoint.Y);
int maxX = Math.Max(e.Location.X, _StartingPoint.X);
int maxY = Math.Max(e.Location.Y, _StartingPoint.Y);
this.SetBounds(minX, minY, maxX - minX, maxY - minY);
this.Invalidate();
}
}
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
this.BackColor = Color.Blue;
}
}
Here is the code I used to test it on a form:
private SelectionTool _SelectPanel = null;
private void Form1_MouseMove(object sender, MouseEventArgs e) {
if (e.Button == System.Windows.Forms.MouseButtons.Left) {
if (_SelectPanel == null)
_SelectPanel = new SelectionTool(this, e.Location);
}
}
private void Form1_MouseUp(object sender, MouseEventArgs e) {
if (_SelectPanel != null) {
_SelectPanel.Dispose();
_SelectPanel = null;
}
}
I am trying to move a drawn line by grabbing it with the mouse.
The line have already been drawn with Graphics.DrawLine(Pen P, Point A, Point B).
There is absolutely no problems with creating the Line and drawing it on the form.
I've tried:
Adding the line to a GraphicsPath - This does not even draw the line OnPaint.
Checking if MouseEventArgs e.Location is on the line with some basic algebra (calculations which I have thrown away as of now)
So to sum it up: I want to grab the line and drag it somewhere but I can't even check if e.Location even is on the Line, how do I do this?
EDIT: This is how the code looks when I'm using the GraphicsPath.
When I don't use the GraphicsPath I have:
if (s.thisShape == ShapeType.Line) {
g.DrawLine(pen, s.p1, s.p2);
} else { ... }`
in the drawingShapes method.
From the drawStuff : Usercontrol class:
private void drawStuff_MouseDown(object sender, MouseEventArgs e)
{
pointRegion = e.Location;
for (int i = 0; i < Shapes.Count; i++)
{
if (Shapes[i].Region.IsVisible(pointRegion))
{
isDragging = true;
count = i;
break;
}
}
}
private void drawStuff_MouseMove(object sender, MouseEventArgs e)
{
if (isDragging)
{
Shapes[count].moveWithDiff(pointRegion, e.Location);
pointRegion = e.Location;
Refresh();
}
}
private void drawStuff_MouseUp(object sender, MouseEventArgs e)
{
isDragging = false;
Refresh();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
drawShapes(e.Graphics);
}
private void drawShapes(Graphics g)
{
temporaryPen = pennaLeft;
foreach (Shape s in Shapes)
{
g.FillRegion(temporaryPen, s.Region);
}
}
From the Shape : Usercontrol class:
public void moveWithDiff(Point pr, Point mp)
{
Point p = new Point();
if (this.thisShape == ShapeType.Line)
{
p.X = mp.X - pr.X;
p.Y = mp.Y - pr.Y;
this.p1.X += p.X;
this.p1.Y += p.Y;
this.p2.X += p.X;
this.p2.Y += p.Y;
}
RefreshPath();
}
private void RefreshPath()
{
gPath = new GraphicsPath();
switch (thisShape)
{
case ShapeType.Line:
gPath.AddLine(this.p1, this.p2);
break;
}
this.Region = new Region(gPath);
}
Now this doesn't even draw the line, however with said if statement in drawingShapes() It draws perfectly but I can not drag it somewhere else.
Let's start with the basics, getting a line on the screen. I created a custom class to handle some of the functions I want available to me for this process:
public class MyLine
{
public Pen pen { get; set; }
public Point Start { get; set; }
public Point End { get; set; }
public MyLine(Pen p, Point p1, Point p2)
{
pen = p;
Start = p1;
End = p2;
}
public float slope
{
get
{
return (((float)End.Y - (float)Start.Y) / ((float)End.X - (float)Start.X));
}
}
public float YIntercept
{
get
{
return Start.Y - slope*Start.X;
}
}
public bool IsPointOnLine(Point p, int cushion)
{
float temp = (slope * p.X + YIntercept);
if (temp >= (p.Y-cushion) && temp <=(p.Y+cushion))
{
return true;
}
else
{
return false;
}
}
}
This class provides a few helper functions that will make our life easier. We have properties that return the slope and the Y-intercept, so we can determine if a certain point is on the line. We then provide a helper function IsPointOnLine() that takes a point and a cushion. The cushion is used to simply allow for a user to click close enough to the line to get it to return true.
Next I am going to instantiate the line and draw it in the Form's paint event:
MyLine m;
private void Form1_Load(object sender, EventArgs e)
{
m= new MyLine(Pens.Black, new Point(20, 20), new Point(40, 40));
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawLine(m.pen, m.Start, m.End);
}
Now you should be able to run your application and see a line that goes from 20,20 to 40,40 on the screen.
Now I want to handle the mouse interaction with the line, so on MouseDown, we will see if the click point intersects the line and if it is set a flag and keep our deltas from the endpoints. On the MouseMove event, we will see if the line has been clicked but not released and reset the coordinates appropriately. In the MouseUp event, we simple reset our flag:
Point deltaStart;
Point deltaEnd;
bool dragging = false;
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left && m.IsPointOnLine(e.Location, 5))
{
dragging = true;
deltaStart = new Point(m.Start.X - e.Location.X, m.Start.Y - e.Location.Y);
deltaEnd = new Point(m.End.X - e.Location.X, m.End.Y - e.Location.Y);
}
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (dragging && deltaStart != null && deltaEnd != null )
{
m.Start = new Point(deltaStart.X + e.Location.X, deltaStart.Y + e.Location.Y);
m.End = new Point(deltaEnd.X + e.Location.X, deltaEnd.Y + e.Location.Y);
this.Refresh();
}
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
dragging = false;
}
Now you should be able to click within 5 pixels of the line and have it move with your mouse.
Note, there are some spots in the code that need additional error handling, especially to handle division by 0 errors.
I suggest you create a rectangle that is the width of the line and the length of the line, then you can use
if(rectangle.Contains(Point p))
{
// do your move
}
You can also inflate the rectangle to make it easier to grab with:
rectangle.Inflate(1, 1);