Draw “marching ants” dashed lines in C#

[dashed lines]

The example Draw dashed lines that are visible on any background in C# shows how to draw lines that are visible above any backgrounds. If the background is very cluttered, however, it may still be somewhat hard to see the lines. This example shows another approach. It draws two-colored dashed lines where the dashes move like a line of marching ants.

The basic idea is simple. The program uses a Pen that has a dash pattern. When a Timer fires, the program sets the Pen object’s DashOffset to a new value and redraws the dashed lines.

The DashOffset value gives the distance from the beginning of the line to the start of the first dash. All of the later dashes follow the first one so they are also offset. The DashOffset value increases over time making it look like the dashes are moving.

The following code shows the variables that the program uses to manage its rectangles.

// Previously selected rectangles.
private List<Rectangle> Rectangles = new List<Rectangle>();

// The rectangle we are selecting.
private Rectangle NewRectangle;

// Variables used to select a new rectangle.
private int StartX, StartY, EndX, EndY;
private bool SelectingRectangle = false;

The Rectangles list holds Rectangle objects that have already been drawn. The program uses the other variables to let the user select a new Rectangle by clicking and dragging.

The following code shows how the program lets the user select a new rectangle.

// Start selecting a rectangle.
private void picCanvas_MouseDown(object sender, MouseEventArgs e)
{
    // Save the current point.
    StartX = e.X;
    StartY = e.Y;
    EndX = e.X;
    EndY = e.Y;

    // Make a new selection rectangle.
    NewRectangle = new Rectangle(
        Math.Min(StartX, EndX),
        Math.Min(StartY, EndY),
        Math.Abs(StartX - EndX),
        Math.Abs(StartY - EndY));

    // Start marching.
    SelectingRectangle = true;
    tmrMarch.Enabled = true;
}

// Continue selecting a rectangle.
private void picCanvas_MouseMove(object sender, MouseEventArgs e)
{
    if (!SelectingRectangle) return;

    // Save the current point.
    EndX = e.X;
    EndY = e.Y;

    // Make a new selection rectangle.
    NewRectangle = new Rectangle(
        Math.Min(StartX, EndX),
        Math.Min(StartY, EndY),
        Math.Abs(StartX - EndX),
        Math.Abs(StartY - EndY));
    
    // Redraw.
    Refresh();
}

// Finish selecting a rectangle.
private void picCanvas_MouseUp(object sender, MouseEventArgs e)
{
    if (!SelectingRectangle) return;
    SelectingRectangle = false;
    tmrMarch.Enabled = false;
    if ((StartX == EndX) || (StartY == EndY)) return;

    // Save the newly selected rectangle.
    Rectangles.Add(new Rectangle(
        Math.Min(StartX, EndX),
        Math.Min(StartY, EndY),
        Math.Abs(StartX - EndX),
        Math.Abs(StartY - EndY)));

    // Redraw.
    Refresh();
}

When the user presses the mouse down, the MouseDown event handler records the mouse’s position, sets SelectingRectangle to true, and enables the Timer named tmrMarch.

When the mouse moves, the MouseMove event handler records the mouse’s new position, updates the NewRectangle variable to represent the newly selected rectangle, and refreshes the form to make the program execute the Paint event handler described shortly.

When the user releases the mouse, the MouseUp event handler adds the newly selected rectangle to the Rectangles list and refresh the form.

The following code shows the tmrMarch Timer component’s Tick event handler.

// Redraw.
private void tmrMarch_Tick(object sender, EventArgs e)
{
    Refresh();
}

This event handler simply refreshes the form to make it execute the following Paint event handler.

// Parameters for drawing the dashed rectangle.
private int Offset = 0;
private int OffsetDelta = 2;
private float[] DashPattern = { 5, 5 };

// Draw the rectangles.
private void picCanvas_Paint(object sender, PaintEventArgs e)
{
    Offset += OffsetDelta;

    // Draw previously selected rectangles.
    for (int i = 0; i < Rectangles.Count; i++)
    {
        e.Graphics.FillRectangle(
            RectangleBrushes[i % RectangleBrushes.Length],
            Rectangles[i]);
        e.Graphics.DrawRectangle(Pens.Black, Rectangles[i]);
    }

    // Draw the new rectangle.
    if (SelectingRectangle)
    {
        e.Graphics.DrawRectangle(NewRectangle, Color.Yellow,
            Color.Red, 2f, Offset, DashPattern);
    }
}

This is where the most interesting code is. The variable Offset holds the dashed line’s current DashOffset value. The value OffsetDelta gives the amount by which Offset is adjusted every time the Paint event handler executes. You can change OffsetDelta and the Timer component’s Interval property (I have it set to 250 milliseconds or 1/4 second) to change the speed of the ants.

The DashPattern array determines the dash pattern used by the dashed lines. In this example, the pattern draws 5 units, skips 5 units, and then repeats.

The Paint event handler first increaes Offset by the value OffsetDelta. It then draws the previously selected rectangles. It uses the brushes in the RectangleBrushes array to give the rectangles colors.

If the user is currently selecting a rectangle, the program then calls the following DrawRectangle extension method to draw a rectangle with two-color dashed lines. (As you can probably guess, I made the extension method to make drawing rectangles with two-color dashed lines a bit easier.)

// Draw a two-color dashed rectangle.
public static void DrawRectangle(this Graphics gr,
    Rectangle rect, Color color1, Color color2,
    float thickness, float offset, float[] dash_pattern)
{
    using (Pen pen = new Pen(color1, thickness))
    {
        gr.DrawRectangle(pen, rect);

        pen.DashPattern = dash_pattern;
        pen.DashOffset = offset;
        pen.Color = color2;
        gr.DrawRectangle(pen, rect);
    }
}

This method begins by drawing the rectangle in its first color parameter. Next it creates a Pen, gives if the DashPattern defined by its dash_pattern parameter, and sets its DashOffset. It then draws the new rectangle over the old one, this time using the dashed pen.


Download Example   Follow me on Twitter   RSS feed   Donate




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

4 Responses to Draw “marching ants” dashed lines in C#

  1. Reddy says:

    Hi,

    I am new to C#.I have issue with this following code. I am trying to detect USB.For this I am
    using,
    SetupDiGetDeviceRegistryProperty(hDevInfo, ref da,SPDRP_HARDWAREID, out RegType, null, 0, Requiredsize);
    I am getting error “The name SPDRP_HARDWAREID” does not exist in current context”. Other variables are OK.I tried to define it as unsigned enum.But it didn’t worked.DO you have any idea what this could be.Let me know.

    Thanks,
    Rreddy

  2. Rod Stephens says:

    I’m not sure what’s wrong with your code but you might try using WMI to get this information instead. I’ll post an example next week, probably on Wednesday. Check the blog or index page then.

  3. Someone says:

    Thanks very much for this code,
    I was planning to find a way to do exactly this for a UserControl i am creating.
    An tiny improvement though would be to use the revese color of the shape color when drawing line

  4. Rod Stephens says:

    Unfortunately the GDI+ drawing methods used by .NET cannot do this.

    You could do it by hand. You would need to figure out which pixels to draw, examine the corresponding pixels on the background, and then calculate the color you want for each pixel in the line. That would be hard and fairly slow.

    You could also use GDI drawing methods. See the second answer to this post:

    This is possible but a hassle and easy to mess up. I think most .NET developers just use a fixed color or dashed lines as in this example. Instead of redrawing the lines to remove them, you just redraw the whole picture.

Leave a Reply

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