How to draw line and select it in Panel

Here is a suitable Line class:

class Line
{
    public Color LineColor { get; set; }
    public float Linewidth { get; set; }
    public bool Selected { get; set; }
    public Point Start { get; set; }
    public Point End { get; set; }

    public Line(Color c, float w, Point s, Point e)
    { LineColor = c; Linewidth = w; Start = s; End = e;    }

    public void Draw(Graphics G)
    { using (Pen pen = new Pen(LineColor, Linewidth)) G.DrawLine(pen, Start, End); }

    public bool HitTest(Point Pt)
    {
        // test if we fall outside of the bounding box:
        if ((Pt.X < Start.X && Pt.X < End.X) || (Pt.X > Start.X && Pt.X > End.X) ||
            (Pt.Y < Start.Y && Pt.Y < End.Y) || (Pt.Y > Start.Y && Pt.Y > End.Y)) 
            return false;
        // now we calculate the distance:
        float dy = End.Y - Start.Y;
        float dx = End.X - Start.X;
        float Z = dy * Pt.X - dx * Pt.Y + Start.Y * End.X - Start.X * End.Y;
        float N = dy * dy + dx * dx;
        float dist = (float)( Math.Abs(Z) / Math.Sqrt(N));
        // done:
        return dist < Linewidth / 2f;
    }

}

Define a List for the lines, probably at class level:

    List<Line> lines = new List<Line>();

Here is how you can initialize it with a few lines:

for (int i = 0; i < 20; i++) lines.Add(new Line(Color.Black, 4f, 
    new Point(R.Next(panel1.Width), R.Next(panel1.Height)), 
    new Point(R.Next(panel1.Width), R.Next(panel1.Height))));

Here is the result of clicking on a crossing:

enter image description here

Whenever you add, change or remove a line you need to make the Panel reflect the news by triggering the Paint event:

panel1.Invalidate();

Here is the Paint event of the Panel:

private void panel1_Paint(object sender, PaintEventArgs e)
{
    e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
    foreach (Line L in lines) L.Draw(e.Graphics);
}

In the MouseClick event you do the test:

private void panel1_MouseClick(object sender, MouseEventArgs e)
{
    foreach(Line L in lines) 
            L.LineColor = L.HitTest(e.Location) ?  Color.Red : Color.Black;
    panel1.Invalidate();
}

To avoid flicker don’t use the basic Panel class as it isn’t doublebuffered. Instead use either a PictureBox or a Label (with AutoSize=false) or a doublebuffered Panel subclass:

class DrawPanel : Panel 
{     public DrawPanel ()   { DoubleBuffered = true; }   }

Notes:

  • There is no such thing as a ‘Line’ in WinForms, only pixels of various colors. So to select a line you need to store it’s two endpoints’ coordinates and then find out if you have hit it when clicking.

  • The above example shows how to do it in math.

  • Instead one could test each line by drawing it onto a bitmap and test the pixel the mouse has clicked. But drawing those bitmaps would have to do math behind the scenes as well and also allocate space for the bitmaps, so the math will be more efficient..

  • Yes the Line class looks a little long for such a simple thing a s a line but look how short all the event codes now are! That’s because the responsiblities are where they belong!

  • Also note the the first rule of doing any drawing in WinForms is: Never cache or store a Grahics object. In fact you shouldn’t ever use CreateGraphics in the first place, as the Graphics object will never stay in scope and the graphics it produces will not persist (i.e. survive a Minimize-maximize sequence)..

  • Also note how I pass out the e.Graphics object of the Paint event’s parameters to the Line instances so they can draw themselves with a current Graphics object!

  • To select thinner lines it may help to modify the distance check a little..

  • The Math was taken directly form Wikipedia.

Leave a Comment