Thursday, April 7, 2022

8-How to move components with the mouse

In this post, we will add the code to "move" the components with the mouse using mouse events associated with the schematicCanvas:

  • Mouse Down
  • Mouse Move
  • Mouse Up
During the mouse move event, we will capture the mouse location, check to see if there is a component at that location, if there is we will capture that component. During the mouse move event, we will check to see if there is a component to move, if there is we will update the component location to give the appearance that it is moving with the mouse. On the mouse down event, we will set the final component location and stop the current component move operation.

How do we determine if there is a component at the mouse locations? We will create a "hit test" method that will loop over all components in the component list, check to see if the mouse location is within the bounds of the component and return a true/false indicator to the mouse down event. The component bounding box can be defined by its width and height. For example, all of the components we have drawn so far are 100x100 pixels. Note that "hit test" code is used routinely in game development and can be applied here.

First, we need to instrument the Comp.cs class so that each component has a location, width, and height that can be used in the mouse events. We will add these attributes as public variables but in a future post they will be changed to public "properties" so they will show up in the Property Grid widget. Let's initialize these attributes in the Comp.cs constructor also. We will set the initial location to (100,100). Add the following code to the Comp.cs class:

    // Component location, width, and height attributes
    public Point loc;
    public int width;
    public int height;
 
    public Comp()
    {
        loc = new Point(100, 100);
        width = 100;
        height = 100;
    }

Next, we need to make an architectural change to all of the component Draw() methods to remove the "magic numbers" and use the new class attributes. Magic numbers are numbers that are hard coded and "buried" in the code. This is hard to maintain in the future so we will improve the code now. Here is the Draw() method for the Comp.cs base class:

public virtual void Draw(Graphics gr)
{
    // Draw a simple rectangle with black border and no fill color
    gr.DrawRectangle(pen, loc.X, loc.Y, width, height);
}

The rectangle and circle are similar and left as an exercise. The Triangle is a bit more challenging and is shown below:

class Triangle : Comp
{
    // Define 3 point attributes for the triangle
    Point p1, p2, p3;
 
    public Triangle()
    {
        loc = new Point(550, 100);
    }
 
    public override void Draw(Graphics gr)
    {
        // Initialize the points to the position of each corner of the triangle
        p1 = new Point(loc.X, loc.Y + height);
        p2 = new Point(loc.X + width, loc.Y + height);
        p3 = new Point(loc.X + width / 2, loc.Y);
 
        // Draw a simple triangle with black border and no fill color
        gr.DrawLine(pen, p1, p2);
        gr.DrawLine(pen, p2, p3);
        gr.DrawLine(pen, p3, p1);
    }
}

The code for calculating the triangle points has been moved to the Draw() method such that they are updated each time it is moved by the mouse events. Note that the equations for calculating the points are much clearer when defined with attributes rather than "magic numbers". They are also much easier to maintain since we simply need to update the Triangle location and all points are calculated from it.

Mouse Events

We will now modify the EngineeringToolsMainForm to support mouse events. Define a boolean variable to tell if a mouse move event is in operation called isMouseDown. This "flag" will be set to false initially.  Add a new Point attribute called NewPt1 to capture the mouse location. Add another Point attribute called offset that will be used to move the selected component to the mouse location during move operations. Finally, define a new component called tempComp to store the selected component during the move operation.

// Mouse event attributes
bool isMouseDown = false;
private Point NewPt1, offset;
Comp tempComp = new Comp();

Next, add the following "helper" hit test method to the EngineeringToolsMainForm:

/* **************************** Helper Functions **************************** */
private bool hitTest(Comp comp)
{
    bool hit;
 
    hit = false;

    if ((NewPt1.X >= comp.loc.X && NewPt1.X <= comp.loc.X + comp.width) &&
        (NewPt1.Y >= comp.loc.Y && NewPt1.Y <= comp.loc.Y + comp.height))
    {

        Debug.WriteLine("Hit!");
        hit = true;
    }
    else
    {
        Debug.WriteLine("No Hit!");
        hit = false;
    }
    return hit;
}

This method assumes that the position of the mouse has been captured by the mouse down event in NewPt1. The mouse x position is tested to see if it is between the component x location and the component x+width location. The mouse y position is tested to see if it is between the component y location and the component y+height location. We can think of these coordinates as defining the "bounding box" containing the component. Note the use of Debug in the System.Diagnostics library to write text to the output window while the program is running indicating if a component has been hit.

Add the mouse events to the schematicCanvas on the main form. Add the following code for each event:

private void schematicCanvas_MouseDown(object sender, MouseEventArgs e)
{
    isMouseDown = true;
    NewPt1.X = e.X;
    NewPt1.Y = e.Y;
 
    // 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;
        }
    }
}

In the MouseDown event, we set the isMouseDown flat to true to begin a mouse move operation. We capture the mouse position in NewPt1. Then we loop over all components use a foreach loop and perform a hit test to see if there is a component selected by the mouse. If there is, the component is captured in tempComp and the offsets between the mouse location and component location are calculated.

private void schematicCanvas_MouseMove(object sender, MouseEventArgs e)
{
    if (isMouseDown == true)
    {
        if (tempComp != null)
        {
            int x = e.X;
            int y = e.Y;
            tempComp.loc = new Point(x - offset.X, y - offset.Y);
            schematicCanvas.Invalidate();
        }
    }
}

In the mouse move event, we check to see if isMouseDown is true and if them tempComp contains a component, if so, we capture the mouse posiition again and update the component location using the offsets. We invalidate the schematicCanvas to display the movement of the component while the mouse is moved in real time.

private void schematicCanvas_MouseUp(object sender, MouseEventArgs e)
{
    isMouseDown = false;
    tempComp = null;
}

Finally, in the mouse up event, we simply turn off the move operation by setting the isMouseDown flag to false and set the tempComp to null indicating that no component is currently selected. Run the application, create a couple of components, then select and move the components to ensure that the mouse events are working.


Now we are getting somewhere. Just to recap, we now have a small diagram application that can draw rectangles, circles, and triangles. We can select and move the shapes anywhere on the diagram using the mouse. The source code for this post is on GitHub.

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...