Draw and move line segments in C#

example

This example lets the user draw and move line segments. It lets you perform three different operations depending on what is below the mouse.

  • When the mouse is over a segment, the cursor changes to a hand. Then you can then click and drag to move the segment.
  • When the mouse is over a segment’s end point, the cursor changes to an arrow. Then you can then click and drag to move the end point.
  • When the mouse is over nothing, you can click and drag to draw a new line segment.

The program handles all of these cases using MouseDown, MouseMove, and MouseUp events, but handling all of the possible combinations in one set of event handlers would be confusing. To make things easier to manage, the program uses separate MouseMove and MouseUp event handlers to perform its different tasks.

This post is divided into the following sections, which correspond to the program’s basic states.

Drawing

The program stores the coordinates of the segments’ end points in the lists Pt1 and Pt2.

// The points that make up the line segments.
private List Pt1 = new List<Point>();
private List Pt2 = new List<Point>();

While you’re drawing a new segment, the variable IsDrawing is true and the program stores the new segment’s end points in variables NewPt1 and NewPt2.

// Points for the new line.
private bool IsDrawing = false;
private Point NewPt1, NewPt2;

The Paint event handler simply loops through the Pt1 and Pt2 lists, drawing the segments and their end points. It then draws the new line (if you’re drawing one).

// Draw the lines.
private void picCanvas_Paint(object sender, PaintEventArgs e)
{
    // Draw the segments.
    for (int i = 0; i < Pt1.Count; i++)
    {
        // Draw the segment.
        e.Graphics.DrawLine(Pens.Blue, Pt1[i], Pt2[i]);
    }

    // Draw the end points.
    foreach (Point pt in Pt1)
    {
        Rectangle rect = new Rectangle(
            pt.X - object_radius, pt.Y - object_radius,
            2 * object_radius + 1, 2 * object_radius + 1);
        e.Graphics.FillEllipse(Brushes.White, rect);
        e.Graphics.DrawEllipse(Pens.Black, rect);
    }
    foreach (Point pt in Pt2)
    {
        Rectangle rect = new Rectangle(
            pt.X - object_radius, pt.Y - object_radius,
            2 * object_radius + 1, 2 * object_radius + 1);
        e.Graphics.FillEllipse(Brushes.White, rect);
        e.Graphics.DrawEllipse(Pens.Black, rect);
    }

    // If there's a new segment under constructions, draw it.
    if (IsDrawing)
    {
        e.Graphics.DrawLine(Pens.Red, NewPt1, NewPt2);
    }
}

Not Moving Anything

If the mouse moves while you’re not moving a segment or end point, the following event handler executes.

// The mouse is up. See whether we're over an end point or segment.
private void picCanvas_MouseMove_NotDown(object sender,
    MouseEventArgs e)
{
    Cursor new_cursor = Cursors.Cross;

    // See what we're over.
    Point hit_point;
    int segment_number;

    if (MouseIsOverEndpoint(e.Location, out segment_number,
        out hit_point))
            new_cursor = Cursors.Arrow;
    else if (MouseIsOverSegment(e.Location, out segment_number))
        new_cursor = Cursors.Hand;

    // Set the new cursor.
    if (picCanvas.Cursor != new_cursor)
        picCanvas.Cursor = new_cursor;
}

This code calls the MouseIsOverEndPoint and MouseIsOverSegment methods described later to see if the mouse is over anything interesting. It then displays the appropriate cursor. (Arrow if over an endpoint, hand if over a segment, and cross if over nothing.)

If you’re not moving anything and you press the mouse down, the following event handler executes.

// See what we're over and start doing whatever is appropriate.
private void picCanvas_MouseDown(object sender, MouseEventArgs e)
{
    // See what we're over.
    Point hit_point;
    int segment_number;

    if (MouseIsOverEndpoint(e.Location, out segment_number,
        out hit_point))
    {
        // Start moving this end point.
        picCanvas.MouseMove -= picCanvas_MouseMove_NotDown;
        picCanvas.MouseMove += picCanvas_MouseMove_MovingEndPoint;
        picCanvas.MouseUp += picCanvas_MouseUp_MovingEndPoint;

        // Remember the segment number.
        MovingSegment = segment_number;

        // See if we're moving the start end point.
        MovingStartEndPoint =
            (Pt1[segment_number].Equals(hit_point));

        // Remember the offset from the mouse to the point.
        OffsetX = hit_point.X - e.X;
        OffsetY = hit_point.Y - e.Y;
    }
    else if (MouseIsOverSegment(e.Location, out segment_number))
    {
        // Start moving this segment.
        picCanvas.MouseMove -= picCanvas_MouseMove_NotDown;
        picCanvas.MouseMove += picCanvas_MouseMove_MovingSegment;
        picCanvas.MouseUp += picCanvas_MouseUp_MovingSegment;

        // Remember the segment number.
        MovingSegment = segment_number;

        // Remember the offset from the mouse
        // to the segment's first point.
        OffsetX = Pt1[segment_number].X - e.X;
        OffsetY = Pt1[segment_number].Y - e.Y;
    }
    else
    {
        // Start drawing a new segment.
        picCanvas.MouseMove -= picCanvas_MouseMove_NotDown;
        picCanvas.MouseMove += picCanvas_MouseMove_Drawing;
        picCanvas.MouseUp += picCanvas_MouseUp_Drawing;

        IsDrawing = true;
        NewPt1 = new Point(e.X, e.Y);
        NewPt2 = new Point(e.X, e.Y);
    }
}

This method uses the MouseIsOverEndPoint and MouseIsOverSegment methods to see if the mouse is over anything interesting. If the mouse is over an end point or segment, the code starts moving that object.

Notice how the code uninstalls the picCanvas_MouseMove_NotDown event handler and installs new MouseMove and MouseUp event handlers for the operation it is starting.

The following code shows the MouseIsOverEndPoint and MouseIsOverSegment methods.

// See if the mouse is over an end point.
private bool MouseIsOverEndpoint(Point mouse_pt,
    out int segment_number, out Point hit_pt)
{
    for (int i = 0; i < Pt1.Count; i++ )
    {
        // Check the starting point.
        if (FindDistanceToPointSquared(mouse_pt, Pt1[i]) <
            over_dist_squared)
        {
            // We're over this point.
            segment_number = i;
            hit_pt = Pt1[i];
            return true;
        }

        // Check the end point.
        if (FindDistanceToPointSquared(mouse_pt, Pt2[i]) <
            over_dist_squared)
        {
            // We're over this point.
            segment_number = i;
            hit_pt = Pt2[i];
            return true;
        }
    }

    segment_number = -1;
    hit_pt = new Point(-1, -1);
    return false;
}

// See if the mouse is over a line segment.
private bool MouseIsOverSegment(Point mouse_pt,
    out int segment_number)
{
    for (int i = 0; i < Pt1.Count; i++)
    {
        // See if we're over the segment.
        PointF closest;
        if (FindDistanceToSegmentSquared(
            mouse_pt, Pt1[i], Pt2[i], out closest)
                < over_dist_squared)
        {
            // We're over this segment.
            segment_number = i;
            return true;
        }
    }

    segment_number = -1;
    return false;
}

These methods simply call the FindDistanceToPointSquared and FindDistanceToSegmentSquared methods. FindDistanceToPointSquared is trivial. For a description of how FindDistanceToSegmentSquared works, see the post Find the shortest distance between a point and a line segment in C#.

The program tests the square of the distance so it doesn’t need to calculate square roots, which are relatively slow. Note that x < y if and only if x2 < y2, so this test still determines whether an object is within the required distance of the mouse.

Drawing a New Segment

The following code shows the MouseMove and MouseUp event handlers that are active when you’re drawing a new segment.

// We're drawing a new segment.
private void picCanvas_MouseMove_Drawing(object sender,
    MouseEventArgs e)
{
    // Save the new point.
    NewPt2 = new Point(e.X, e.Y);

    // Redraw.
    picCanvas.Invalidate();
}

// Stop drawing.
private void picCanvas_MouseUp_Drawing(object sender,
    MouseEventArgs e)
{
    IsDrawing = false;

    // Reset the event handlers.
    picCanvas.MouseMove -= picCanvas_MouseMove_Drawing;
    picCanvas.MouseMove += picCanvas_MouseMove_NotDown;
    picCanvas.MouseUp -= picCanvas_MouseUp_Drawing;

    // Create the new segment.
    Pt1.Add(NewPt1);
    Pt2.Add(NewPt2);

    // Redraw.
    picCanvas.Invalidate();
}

When the mouse moves, the MouseMove event handler updates the value of NewPt2 to hold the mouse’s current position. It then invalidates the program’s PictureBox so its Paint event handler draws the current segments and the new one in progress.

When you release the mouse, the MouseUp event handler restores the “not moving anything” event handlers, adds the new segment’s points to the Pt1 and Pt2 lists, and invalidates the PictureBox to redraw.

Moving an End Point

The following code shows the MouseMove and MouseUp event handlers that are active when you’re moving an end point.

// We're moving an end point.
private void picCanvas_MouseMove_MovingEndPoint(object sender,
    MouseEventArgs e)
{
    // Move the point to its new location.
    if (MovingStartEndPoint)
        Pt1[MovingSegment] =
            new Point(e.X + OffsetX, e.Y + OffsetY);
    else
        Pt2[MovingSegment] =
            new Point(e.X + OffsetX, e.Y + OffsetY);

    // Redraw.
    picCanvas.Invalidate();
}

// Stop moving the end point.
private void picCanvas_MouseUp_MovingEndPoint(object sender,
    MouseEventArgs e)
{
    // Reset the event handlers.
    picCanvas.MouseMove += picCanvas_MouseMove_NotDown;
    picCanvas.MouseMove -= picCanvas_MouseMove_MovingEndPoint;
    picCanvas.MouseUp -= picCanvas_MouseUp_MovingEndPoint;

    // Redraw.
    picCanvas.Invalidate();
}

When the mouse moves, the MouseMove event handler updates the position of the point you are moving and then invalidates the PictureBox to make it redraw. The MouseUp event handler simply restores the “not moving anything” event handlers and redraws.

Moving a Line Segment

The following code shows the MouseMove and MouseUp event handlers that are active when you’re moving an end point.

// We're moving a segment.
private void picCanvas_MouseMove_MovingSegment(object sender,
    MouseEventArgs e)
{
    // See how far the first point will move.
    int new_x1 = e.X + OffsetX;
    int new_y1 = e.Y + OffsetY;

    int dx = new_x1 - Pt1[MovingSegment].X;
    int dy = new_y1 - Pt1[MovingSegment].Y;

    if (dx == 0 && dy == 0) return;

    // Move the segment to its new location.
    Pt1[MovingSegment] = new Point(new_x1, new_y1);
    Pt2[MovingSegment] = new Point(
        Pt2[MovingSegment].X + dx,
        Pt2[MovingSegment].Y + dy);

    // Redraw.
    picCanvas.Invalidate();
}

// Stop moving the segment.
private void picCanvas_MouseUp_MovingSegment(object sender,
    MouseEventArgs e)
{
    // Reset the event handlers.
    picCanvas.MouseMove += picCanvas_MouseMove_NotDown;
    picCanvas.MouseMove -= picCanvas_MouseMove_MovingSegment;
    picCanvas.MouseUp -= picCanvas_MouseUp_MovingSegment;

    // Redraw.
    picCanvas.Invalidate();
}

When the mouse moves, the MouseMove event handler updates the positions of the segment’s end points and redraws to show the new position. The MouseUp event handler simply restores the “not moving anything” event handlers and redraws.

What Next?

There are lots of other features you can add to a drawing program such as this one. You might want to add:

  • Other drawing tools such as polylines, polygons, scribbles, rectangles, ellipses, and so forth.
  • A different selection model so, for example, the user must select an object before seeing and moving its end points.
  • Grab handles that let the user resize a selected object. (You don’t need this if you’re only drawing line segments and you can move their end points.)
  • Snap-to-grid features.
  • Alignment tools such as Align Tops and Align Middles.
  • The ability to save and restore pictures.
  • The ability to remove objects and change their stacking order.
  • Shapes with different foreground and background colors.

I may get to some of these in future examples. (A better approach for those more flexible features would be to use classes to represent the objects in the drawing.)

Note also that building custom drawing tools is one of my favorite types of consulting, so if you want one built, email me.


Download Example   Follow me on Twitter   RSS feed   Donate




This entry was posted in drawing, graphics and tagged , , , , , , , , , , , , . Bookmark the permalink.

29 Responses to Draw and move line segments in C#

  1. Goper says:

    Thank you for this. 🙂 But do you have a code to delete a specific line much appreciated…

    • RodStephens says:

      This example doesn’t have a selection model. In other words, you don’t select a segment and then manipulate it. You just click and drag a segment or an end point. Because you can’t select a segment, it’s not clear how you define deleting one.

      Probably creating a selection model where you can click and drag to select multiple segments, Shift+Click to add segments to the selection, and Ctrl+Click to toggle a segment’s selection is the best approach, but that would mean some major changes. I can think of a couple of alternatives.

      • You could have a radio button to let the user enter deletion mode. Then if you clicked on a segment, the program would delete it.
      • You could delete the segment if the user dragged it off of the form.
      • You could delete a segment if you dragged it onto a trashcan icon.

      Here’s an example that takes the last approach:

  2. Pingback: Draw, move, and delete line segments in C# - C# HelperC# Helper

  3. jayson says:

    can you post the whole source code please?

  4. jayson says:

    i cant find out if the cursor is in the bounds of the circle of an endpoint
    please help

  5. John says:

    Hi Rod,
    Excellent, I can draw my PCB tracks now.
    How can you get them to draw fullsize when printed, I have used
    e.Graphics.PageUnit = GraphicsUnit.Millimeter;
    but I dont know how to scale them properly or what i have to do to the lines
    any pointers would be helpful.

    Thank you

  6. Pingback: Print at full scale in C# - C# HelperC# Helper

  7. Sophie says:

    thank you so much for the beautiful tutorial!!

    but how do you create the extra mouse event handlers!? just make them up? or

    • RodStephens says:

      Do you mean the ones that are added and removed with += and -=? You can just type them in, but usually I use the Properties window to create an event handler at design time, copy its code, and remove the original from the control so the compiler doesn’t get all confused.

      Alternatively you can type something like:

          picCanvas.MouseMove +=

      Then IntelliSense will let you press Tab to create the new assignment statement and then Tab again to create an empty event handler. The assignment statement tends to be decorated with unnecessary extra stuff (like new FormClosingEventHandler(...)), but you can remove that if you don’t like it. (Or leave it if you do like it.)

  8. Keith Dudley says:

    Thanks you for your tutorials. I modified some of your tutorials on my end to fit my needs. Basically I have a triangle which has end points. I am try to drag end points to a new location. Could you assist me. Your tutorials are setup to move line segments. I want to be able to drag end points. Do you have a tutorial where you dragging endpoints instead.

    • RodStephens says:

      This example also lets you move end points. Just remove the code for moving segments. You may also need to modify the code that manages the triangle’s end points to keep track of them (if you haven’t already done that).

  9. Allen Kuo says:

    thank you for this example, Draw and move end points is very useful to me. And do you have the code (The ability to save and restore pictures). I really want to know how to implement the feature,Could you assist me?

  10. Alejandro says:

    hi Rod, thanks for you exercise, but have a problem, because when make zoom in and out dont cant resize the line, you can help me with this or have a solution, thanks

    • RodStephens says:

      Do you mean you wrote a program that lets you zoom and then you can’t resize the lines? Probably it’s because the drawing and mouse are working in different coordinates. For example, suppose you scale by a factor of 2. Then if the mouse is at (10, 30), it’s over the point (20, 60) in the drawing coordinates.

      You’ll need to use a transformation to convert the mouse’s position from screen coordinates in pixels into the drawing’s coordinates.

      • Alejandro says:

        i can zoom in/out in the lines, but in the momento of resize of line i can´t resize and the funcion that use for zoom/out is:

        – e.Graphics.ScaleTransform(zoom, zoom) this is for line and for image is:

        – image.Size = new Size(image.Width + 50, image.Height + 50);

  11. Charu says:

    hi how to select all lines , or rectangles drawn on picture box , i want them to move by selecting all.

    • RodStephens says:

      First create a list or collection to hold the selected objects. Replace all of the places where the program uses the selected object with code that loops through the collection.

      Then you need to change the selection model so the use can select multiple objects. For example, you could make Shift-Click add an object to the list. Or you could have click-and-drag select objects that are intersected by the selection rectangle.

      The idea is straightforward but the details can be tricky depending on which selection model you use.

  12. Marcos Vinicios says:

    Mr. Rod Stephens,

    I cant build a similar application following the steps that you wrote here.
    Could you post the whole code in C#? It will help me a lot to finish my final job in university.

    Thank you very much!
    Marcos.

    • RodStephens says:

      You can download the example program by clicking the Download button at the bottom of the post.

      All posts only include the most interesting code. There’s always other stuff that you need to make an example work, so you need to download the example to see the whole thing.

Leave a Reply

Your email address will not be published. Required fields are marked *