3

I have a winforms application

Here is my code

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApplication12
{
    public partial class Form1 : Form
    {
        Graphics gr;
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            gr = this.CreateGraphics();

            MyLine myline = new MyLine();
            myline.P1 = new Point(100, 0);
            myline.P2 = new Point(200, 80);

            gr.DrawLine(new Pen(Color.Red), myline.P1,myline.P2);


            Rectangle r = new Rectangle(0, 0, 50, 50);


            gr.DrawRectangle(new Pen(Color.Teal, 5), r);

            if (r.Contains(0,25)) MessageBox.Show("within");

        }

        private void btnClear_Click(object sender, EventArgs e)
        {
            gr.Clear(this.BackColor);
        }


    }
}

class MyLine
{    
    public Point P1 {get; set;}
    public Point P2 { get; set; }
}

My problem is this..

I can draw a rectangle, and I can see whether a point is within it.

So I could extend the program to say "yes" when a click on the form is within the rectangle. The Rectangle has a Contains function which is great.

But I want to do the same for Line.

The problem, is that winforms has no Line class. I could write my own Line class, but the problem remains.. how to find whether a click landed on it?

I notice that WPF has such a class How do I recognize a mouse click on a line?

But i'm using winforms.

Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
barlop
  • 12,887
  • 8
  • 80
  • 109
  • @MarkHall You say a rectangle of height one... Though That won't work for a diagonal line, unless there's a way to turn the rectangle – barlop Dec 09 '15 at 06:47
  • I understand perhaps I don't need a global and could use Graphics gr separately in each of the two functions – barlop Dec 19 '15 at 01:51
  • note- re reza's answer, for rectangle you can do `bool b = gp.IsVisible(point) || gp.IsOutlineVisible(point, pen);` e.g. http://stackoverflow.com/questions/4816297/how-to-know-if-a-graphicspath-contains-a-point-in-c-sharp/34378138#34378138 – barlop Dec 20 '15 at 06:08
  • also relevant - the idea of having a list of all your objects scanned through when the form or panel or button is clicked, to see which shape was clicked. And to have your own object for each shape, and that object will have an isHit method as in the answer here http://stackoverflow.com/questions/1279091/how-to-detect-a-click-of-a-dynamically-drawn-graphic – barlop Dec 20 '15 at 06:31

2 Answers2

6

Using GraphicsPath.IsOutlineVisible method you can determine whether the specified point is under the outline of the path when drawn with the specified Pen. You can set width of the pen.

So you can create a GraphicsPath and then add a line using GraphicsPath.AddLine to the path and check if the path contains the point.

Example:

The below method, checks if the p is on the line with end points p1 and p2 using the specified width.

You can use wider width to increase the tolerance or if the line is wider than 1:

//using System.Drawing;
//using System.Drawing.Drawing2D;
bool IsOnLine(Point p1, Point p2, Point p, int width = 1)
{
    using (var path = new GraphicsPath())
    {
        using (var pen = new Pen(Brushes.Black, width))
        {
            path.AddLine(p1, p2);
            return path.IsOutlineVisible(p, pen);
        }
    }
}
Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
  • I have a form with a label (label1) drawn on it. I don't seem to be getting true returned / written into the text of the label. http://pastebin.com/raw.php?i=mKt8qr8k that's even when i've given 10 as an argument to width. Also, is width=1 directly equivalent to the size of the argument to pen? e.g. if pen is new Pen(Color.Black,10) then would that correspond to an argument for width, of 10? – barlop Dec 10 '15 at 02:37
  • The problem is with your code. You should use `MouseDown` event and pass `e.Location` as `p`. The method is tested and works properly. using `width= 1` checkes if the mouse is exactly on line, but it's hard to click on a line with width=1, so you can use 3 for example as width. – Reza Aghaei Dec 10 '15 at 09:41
  • Since you can add any shape to a path, you can use `GraphicsPath.IsOutlineVisible` to check if the point is on borders of any shape. Also you can use [GraphicsPath.IsVisible](https://msdn.microsoft.com/en-us/library/d20k495d(v=vs.110).aspx) to check if a shape contains the point. – Reza Aghaei Dec 10 '15 at 17:27
  • Turned out the way I had done it(that pastebin link) using your code but without MouseDown function, was fine, except for one bug, which was from your own code. It should be path.AddLine(p1,p2) but you did (p1,p1). You probably would want to correct that. I'm impressed though that you got it almost right without even testing though! And BTW I had no problem clicking for width=1 Also I am glad to see that the width parameter corresponds exactly to the thickness of the line. – barlop Dec 10 '15 at 21:33
  • Thank you for comment and point to that mistake, the mistake was made after renaming the parameters here :), I corrected this. – Reza Aghaei Dec 10 '15 at 21:36
  • It's worth writing a note at the end about the correction, that way the comments make more sense. (I had made an edit of your post that corrected it and has a note at the end, though my edit went into a queue for review, perhaps due to my low rep on SO) – barlop Dec 11 '15 at 00:04
  • Dear @barlop feel free to edit the answer and I'll approve the edit, or let me know if you prefer writing a note by me :) – Reza Aghaei Dec 11 '15 at 00:09
  • I notice you use "using" when e.g. creating a pen, though this msdn link doesn't https://msdn.microsoft.com/en-us/library/ftzx0xf5(v=vs.90).aspx Why do you use using when the msdn link doesn't? Is it that important? – barlop Dec 11 '15 at 16:57
  • In that msdn link, it calls `myPen.Dispose();` after using the pen. Also we used `using` to automaticcaly dispose the pen when the using block closed. In fact [using Statement](https://msdn.microsoft.com/en-us/library/yh598w02.aspx) *Provides a convenient syntax that ensures the correct use of IDisposable objects.* – Reza Aghaei Dec 11 '15 at 17:04
  • you could add that you need to do `using System.Drawing.Drawing2D;` ` – barlop Dec 20 '15 at 03:43
1

I implemented a simple Line class to check if a dot fall on the line.
You can capture a mouse position out of Form_Click event

Here's the snippet

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsApplication
{
    public partial class Form1 : Form
    {
        Line myLine;
        int x1 = 10;
        int x2 = 40;
        int y1 = 0;
        int y2 = 30;
        public Form1()
        {
            InitializeComponent();
            myLine = new Line() { Start = new Point(x1, y1), Stop = new Point(x2, y2), Epsilon = 10 };
        }

        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            Pen pen = new Pen(Color.FromArgb(255, 0, 0, 0));
            e.Graphics.DrawLine(pen, x1, y1, x2, y2);
            pen.Dispose();
        }

        private void Form1_Click(object sender, EventArgs e)
        {
            MouseEventArgs me = (MouseEventArgs)e;
            bool contain = myLine.contain(new Point(me.X,me.Y));
        }
    }

    public class Line
    {
        public Point Start { get; set; }
        public Point Stop { get; set; }
        public float Epsilon { get; set; }

        public bool contain(Point p)
        {
            // y = mx + c
            float m = (Stop.Y - Start.Y) / (Stop.X - Start.X);
            float c = Stop.Y - (m * Stop.X);
            return p.X >= Math.Min(Start.X, Stop.X)
                && p.X <= Math.Max(Start.X, Stop.X)
                && p.Y >= Math.Min(Start.Y, Stop.Y)
                && p.Y <= Math.Max(Start.Y, Stop.Y)
                && Math.Abs(Math.Abs(p.Y) - Math.Abs((m * p.X) + c)) < epsilon; //with relax rules
                //&& (p.Y == (m*p.X)+c); // strict version
        }
    }

UPDATE
careful of a case where X1 == X2. It will throw exception.

TLJ
  • 4,525
  • 2
  • 31
  • 46
  • thanks. I figured it'd involve some differentiation..looks like you did some dy/dx. I notice it doesn't work for thicker lines though e.g. argument of 15. `Pen pen = new Pen(Color.FromArgb(255, 0, 0, 0),15);` – barlop Dec 10 '15 at 02:21
  • I like the `MouseEventArgs me = (MouseEventArgs)e;` in form1_click and then `me.X` `me.Y` as an interesting alternative to a mouseup function – barlop Dec 10 '15 at 21:51