Thursday, April 7, 2022

10-How to draw a line with the mouse

In this post, we will switch gears and setup a line drawing capability in Test Diagram which will be used for wires that interconnect components in the Engineering Tools application. We will create a new Wire class for this purpose. We will add a button that sets a line drawing mode. The mouse event handlers will be modified to detect if we are in line drawing mode using a flag. The mouse down event will capture the start point of the line, the mouse move event will draw a so-called "rubberband" line in real time. The mouse up event will capture the end point of the line. If the line is not horizontal or vertical, we will add a rectilinear line capability to draw a "L-shaped" line. Finally, during the line draw opertion, we will add end caps or highlights (small red squares) on the ends of the line to indicate the start and end points. The end caps will disappear after the line is drawn. The line will be added to the component list so that it is drawn in the schematicCanvas Paint event. In a future post, we will discuss how to "traverse" a diagram to perform analysis. The components will capture the output wires or lines in a local list.

Wire Class

Create a new class called Wire which is derived from the Comp base class. The Wire class requires 4 attributes:

// Create wire start and end points
public Point Pt1 = new Point();
public Point Pt2 = new Point();
 
// End caps variables
private const int endcap_radius = 3;
public bool endcapsVisible = false;

Pt1 and Pt2 define the start point and end point, respectively. The endcap_radius defines the size of the endcaps. The endcapsVisible variable is a flag to set the encap visibility.

Here is the Wire Draw() method:

public override void Draw(Graphics gr)
{
    if (Pt1.X != Pt2.X || Pt1.Y != Pt2.Y)
    {
        // Draw L-shaped wire
        gr.DrawLine(pen, Pt1.X, Pt1.Y, Pt1.X, Pt2.Y); // Vertical line
        gr.DrawLine(pen, Pt1.X, Pt2.Y, Pt2.X, Pt2.Y); // Horizontal line
    }
    else
    {
        // Draw straight wire
        gr.DrawLine(pen, Pt1, Pt2);
    }
 
    // Draw the wire end caps
    drawEndCaps(gr);
}

The draw method checks to see if the wire is not horizontal or vertical, if not, it draws an L-shaped line. Otherwise, it draws a straight line. Finally, the endcaps are drawn using the following drawEndCaps() method.

private void drawEndCaps(Graphics gr)
{
    if (this.endcapsVisible)
    {
        // Draw custom end cap for Pt1
        gr.DrawRectangle(Pens.Red, Pt1.X - endcap_radius, Pt1.Y - endcap_radius,
                2 * endcap_radius, 2 * endcap_radius);     // Rectangular end cap
 
        // Draw custom end cap for Pt2
        gr.DrawRectangle(Pens.Red, Pt2.X - endcap_radius, Pt2.Y - endcap_radius,
                2 * endcap_radius, 2 * endcap_radius);    // Rectangular end cap
    }
}

If the end cap is visible, the drawEndCaps() method draws a rectangle at the line start and end points. The size of the endcap is defined by the endcap_radius variable.

Mouse Event modifications to draw lines

New line drawing attributes for the TestDiagramMainForm include a lineDrawing flag to indicate if the user has selected the line drawing mode. NewPt2 is used to store the end point of the line. NewWire is a temporary wire component used during line drawing operation.

// Mouse event attributes
bool isMouseDown = false;
bool lineDrawing = false;
private Point NewPt1, NewPt2, offset;
Comp tempComp = new Comp();
 
// The wire we are drawing
private Wire NewWire = null;

Changes to the Mouse Down event handler:

private void schematicCanvas_MouseDown(object sender, MouseEventArgs e)
{
    isMouseDown = true;
 
    // Snap the start point to the Grid
    int x = e.X;
    int y = e.Y;
    SnapToGrid(ref x, ref y);
    NewPt1.X = x;
    NewPt1.Y = y;
 
    if (!lineDrawing)
    {
        // Iterate over the component list and test each to see if it is "hit"
        foreach (Comp comp in comps)
        {
            if (hitTest(comp))
            {
                tempComp = comp;
                offset.X = NewPt1.X - comp.loc.X;
                offset.Y = NewPt1.Y - comp.loc.Y;
            }
        }
    }
    if (lineDrawing)
    {
        // Snap the line end point to the Grid
        NewPt2 = new Point(x, y);
 
        // Create a new wire and add it to the schematic wires list
        NewWire = new Wire(); // Use the constructor with no parameters
        NewWire.Pt1 = NewPt1;
        NewWire.Pt2 = NewPt2;
        NewWire.endcapsVisible = true;
        comps.Add(NewWire);
 
        // Debugs to show the line points on the console
        Debug.WriteLine("Line Start Points: (" + NewPt1.X + ", " + NewPt1.Y + " )");
    }
}

The event handler now checks to see if a line drawing operation is active. If it is, the code captures both the start and end points of the line and adds a Wire component to the component list.

Changes to the Mouse Move event handler:

private void schematicCanvas_MouseMove(object sender, MouseEventArgs e)
{
    if (isMouseDown == true)
    {
        int x = e.X;
        int y = e.Y;
        SnapToGrid(ref x, ref y);
 
        if (!lineDrawing)
        {
            if (tempComp != null)
            {
                tempComp.loc = new Point(x - offset.X, y - offset.Y);
            }
        }
        if (lineDrawing)
        {
            if (NewWire == null) return;
            NewPt2 = new Point(x, y);
            NewWire.Pt2 = NewPt2; 
        }
 
        schematicCanvas.Invalidate(); // Refresh the drawing canvas pictureBox
    }
}

Mouse move checks to see if we are in line drawing mode, if so, the end point of the line is updated resulting in a "rubberband" line drawn by the user as the mouse is moved.

Changes to the Mouse Up event:

private void schematicCanvas_MouseUp(object sender, MouseEventArgs e)
{
    isMouseDown = false;
    tempComp = null;
 
    if (lineDrawing)
    {
        int x = e.X;
        int y = e.Y;
        SnapToGrid(ref x, ref y);
        NewPt1.Y = y;
 
        // Update the new wire end points
        NewWire.Pt2 = NewPt2;
        NewWire.endcapsVisible = false;
 
        // Terminate any further updates to the new wire, this makes it permanent in the schematic wires list
        NewWire = null;
 
        // Redraw.
        schematicCanvas.Invalidate(); // Refresh the drawing canvas pictureBox
 
        // Debugs to show the line points on the console
        Debug.WriteLine("Line End Points: (" + NewPt2.X + ", " + NewPt2.Y + " )");
    }
}

The mouse up event checks to see if line drawing mode is set, if it is the end point of the line is snapped to the grid and set. End cap visibility is set to false. Note that line drawing mode is not turned off which allows the application to remain in line drawing mode until the user clicks the Wire Mode button.

Wire Mode Button

Add a new button to the Test Diagram form:
  • Text: Wire Mode
  • Name: btnWireMode
Double click on the button to code the Click event handler as follows:

private void btnWireMode_Click(object sender, EventArgs e)
{
    lineDrawing = !lineDrawing;
}

Note that the lineDrawing boolean is toggled by this code. If the value is true, it will be set to false and vice-versa.


We now have a line drawing capability. The full source code is available in this GitHub commit.

We can now create super simple diagrams. We could add alot more capability to create a fully functional diagram application but our goal is to create engineering diagrams with analysis. Time to move on to a Digital Circuit Simulator.



No comments:

Post a Comment

34-Microwave Tools with Analysis (Series Final Post)

In this final blog post, I have integrated Y-Matrix analysis into the Microwave Tools project. This version has addition Lumped, Ideal, Micr...