Overview
Deleting a component involves deleting all references which includes references in the component list and wire lists. For example, lets assume that we have simple circuit consisting of input port, resistor, output port, wire connecting input port to resistor and wire connecting resistor to output port.
We want to delete the resistor component. It is found in three places:
- Pin wire list has a wire with the resistor as the output component.
- The resistor is an item in the circuit component list.
- The resistor has a wire with itself as the input component.
Deleting a component will also delete all connected wires. If we remove the resistor from the circuit component list, the output wire will be removed also. We will need to remove the input wire from the input port wire list.
To delete a wire, it is found in one place:
- The input component wire list
For example, the wire from the input port to the resistor is found in the input port wire list. The wire from the resistor to the output port, is found in the resistor wire list. Remember that a component wire list contains a list of output wires.
From a user interface standpoint, it would be nice to "select" a component or wire, hit the delete key, ask the user to confirm that the selected component should be deleted, if confirmed, remove it and redraw the canvas.
To test component delete, we will reuse much of the TestRotation project source code as the starting point such that we test rotation and delete. Create a new project called TestDelete using C# WinForms and set as the startup project. Rename form1.cs to TestDeleteMainForm.cs.
UI Setup
Set the form properties:
- Text: Test Delete
- BackColor: PowerderBlue
- KeyPreview: True
Add a flow layout panel to the form:
- Dock: Top
- BackColor: Black
- Height: 44
Add a button to the panel:
- Text: Init Ckt
- Name: btnInit
- Size: 82, 35
Copy the Init Ckt button and paste into the panel
- Text: Select Mode
- Name: btnSelect
- Size: 126, 35
Add a pictureBox to the form:
- Name: schematicCanvas
Class Organization
Add 5 folders in the Solution Explorer for class organization of the project:
- Circuits
- Components
- Ideal
- Lumped
- Wires
Add 6 new classes to the folders:
- Circuits: Circuit.cs class
- Components: Comp.cs class
- Ideal: InPort.cs class
- Ideal: OutPort.cs class
- Lumped: RES.cs class
- Wires: Wire.cs class
Circuit Class
The component list will be moved from the MainForm in TestRotation to the Circuit class:
// C# Libraries
using System;
// Microwave Tools Libraries
using TestDelete.Components;
namespace TestDelete.Circuits
    public class Circuit
    {
        public List<Comp> comps = new List<Comp>();
}
Comp.cs Base Class
Copy the attributes from TestRotation Comp.cs file and add the following parameters
       // Protected variables - available to derived subclasses
        protected int compSize = 60;
        protected int bodyL = 40;  // Length of the component body (without leads)
        protected int compL = 60;  // Length of the component with leads
 
        // public variables
        public List<Wire> wires = new List<Wire>();
        // End caps variables
        protected const int
endcap_radius = 3;
        // Selection flag
        public bool
isSelected = false;
These additional attributes will be used to draw the components, create an output wire list, define the Pin and Pout locations, create end caps (small red rectangles), and provide a flag for when the component is selected.
Add a default constructor and the Draw() virtual method.
       // Constructors
        public Comp()
 
        }
 
        // Polymorphism: Virtual methods used in circuit component
iteration
        public virtual void Draw(Graphics gr) { /* Do nothing */ }
Add two new helper methods called checkSelect() and drawSelectRect(). The checkSelect() method will change the component drawPen to Red when it is selected. The drawSelectRect() method will add end caps to the leads at the point p1.
       public void checkSelect()
            if (isSelected)
                drawPen = new Pen(Color.Black);
 
        public void drawSelectRect(Graphics gr, Point p1)
            if (isSelected)
                Rectangle
rect1 = new Rectangle(
                     2 *
endcap_radius, 2 * endcap_radius);
               
gr.DrawRectangle(Pens.Red, rect1);    
// Rectangular end cap
            }
        }
Input Port Class (InPort.cs)
// C# Libraries
using System;
namespace TestDelete.Components.Ideal
    public class InPort : Comp
        public InPort()
 
        }
 
        public InPort(double value,
Point pt)
            Loc.X = pt.X;
            Loc.Y = pt.Y;
            Width = 60;
            Height = 60;
            Value = value;
            boundBox = new Rectangle(Loc.X, Loc.Y, Width,
Height);
            Pout = new Point(Loc.X + compSize, Loc.Y +
halfCompSize);
This class constructor initializes the input port location, width, height, value, bounding box and efines the location of the output pin (Pout).
        // Let the InPort draw itself called from the canvas paint event
        public override void
Draw(Graphics gr)
            // Component selection
            checkSelect();
            drawSelectRect(gr,
new Point(Loc.X + compSize, Loc.Y+30));
            GraphicsPath gp
= new GraphicsPath();
            Point p1 = new Point(Loc.X + compSize, Loc.Y +
halfCompSize);             // Assume p1 is the end of the lead at the output of Pin
            Point p2 = new Point(p1.X - leadL, p1.Y);
            gp.AddLine(p1, p2);
            gp.AddLine(p2,
p3);
            gp.AddLine(p3,
p4);
            gp.AddLine(p4,
p5);
            gp.AddLine(p5,
p6);
 
            gp.AddLine(p6,
p2);
 
            // Draw the component text
            compText = "Pin";
 
            // Set rotation angle
            if (isRotated)
            else
                angle = 0;
 
            // Rotate the component by angle deg
            PointF
rotatePoint = new
PointF(Loc.X + 30, Loc.Y + 30); // Rotate about component
center point
            Matrix myMatrix
= new Matrix();
            gr.Transform =
myMatrix;
 
            // Update the bounding box location
            boundBox = new Rectangle(Loc.X, Loc.Y, Width,
Height);
            // Draw the component path
            gr.DrawPath(drawPen,
gp);
 
            // Draw the bounding box for debug
            //gr.DrawRectangle(redPen, boundBox);
 
            gp.Dispose();
        }
    }
}
Resistor Class (RES.cs)
// C# Libraries
using System;
namespace TestDelete.Components.Lumped
    public class RES : Comp
        public RES()
 
        }
 
        public RES(double value,
Point pt)
            Loc.X = pt.X;
            Loc.Y = pt.Y;
            Width = 60;
            Height = 60;
            Value = value;
            boundBox = new Rectangle(Loc.X, Loc.Y, Width,
Height);
            Pin = new Point(Loc.X, Loc.Y + 30);
 
        public override void
Draw(Graphics gr)
            // Component selection
            checkSelect();
           
drawSelectRect(gr, new
Point(Loc.X, Loc.Y + 30));
            GraphicsPath gp
= new GraphicsPath();
            // Draw the input leads
           
gp.AddLine(Loc.X, Loc.Y+30, Loc.X + leadL, Loc.Y + 30);
 
            // Draw the resistor body
            for (int i = 1; i < 5; i++)
               
gp.AddLine(Loc.X + leadL * (i), Loc.Y + 30, Loc.X + leadL * (i) + 3,
Loc.Y - leadL + 30);
               
gp.AddLine(Loc.X + leadL * (i) + 3, Loc.Y - leadL + 30, Loc.X + leadL *
(i) + 6, Loc.Y + leadL + 30);
               
gp.AddLine(Loc.X + leadL * (i) + 6, Loc.Y + leadL + 30, Loc.X + leadL *
(i + 1), Loc.Y + 30);
            }
 
            // Draw the output leads
            gp.AddLine(Loc.X
+ bodyL + leadL, Loc.Y + 30, Loc.X + compL, Loc.Y + 30);
 
            // Draw the component text
            compText = "R = " + this.Value + "Ω";
 
            // Set rotation angle
            if (isRotated)
            else
                angle = 0;
 
            // Rotate the component by angle deg
            PointF
rotatePoint = new
PointF(Loc.X + 30, Loc.Y + 30); // Rotate about component
center point
            Matrix myMatrix
= new Matrix();
            gr.Transform =
myMatrix;
 
            // Update the bounding box location
            boundBox = new Rectangle(Loc.X, Loc.Y, Width,
Height);
            // Draw the component path
           
gr.DrawPath(drawPen, gp);
 
            // Draw the bounding box for debug
            //gr.DrawRectangle(redPen, boundBox);
 
            gp.Dispose();
        }
    }
}
Output Port Class (OutPort.cs)
// C# Libraries
using System;
namespace TestDelete.Components.Ideal
    public class OutPort : Comp
        public OutPort()
 
        }
 
        public OutPort(double value,
Point pt)
            Loc.X = pt.X;
            Loc.Y = pt.Y;
            Width = 60;
            Height = 60;
            Value = value;
            boundBox = new Rectangle(Loc.X, Loc.Y, Width,
Height);
            Pin = new Point(Loc.X, Loc.Y + 30);
 
        //Let the InPort draw itself called from the canvas paint event
        public override void
Draw(Graphics gr)
            checkSelect();
           
drawSelectRect(gr, new
Point(Loc.X, Loc.Y + 30));
            GraphicsPath gp = new GraphicsPath();
            Point p1 = new Point(Loc.X, Loc.Y+30);     // Assume p1 is
the end of the lead at the output of Pin
            Point p2 = new Point(p1.X + leadL, p1.Y);
            gp.AddLine(p1,
p2);
            gp.AddLine(p2,
p3);
            gp.AddLine(p3, p4);
            gp.AddLine(p4,
p5);
            gp.AddLine(p5,
p6);
            gp.AddLine(p6,
p2);
 
            // Draw the component text
            compText = "Pout";
 
            // Set rotation angle
            if (isRotated)
            else
                angle = 0;
 
            // Rotate the component by angle deg
            PointF rotatePoint = new PointF(Loc.X + 30, Loc.Y + 30); // Rotate about component center point
            Matrix myMatrix
= new Matrix();
            gr.Transform =
myMatrix;
 
            // Update the bounding box location
            boundBox = new Rectangle(Loc.X, Loc.Y, Width,
Height);
            // Draw the component path
           
gr.DrawPath(drawPen, gp);
 
            // Draw the bounding box for debug
            //gr.DrawRectangle(redPen, boundBox);
 
            gp.Dispose();
        }
    }
}
Wire Class (Wire.cs)
// C# Libraries
using System;
// Microwave Tools Libraries
using TestDelete.Components;
namespace TestDelete.Wires
    public class Wire : Comp
        // Create wire start and end points
        public Point Pt1 = new Point();
        // Add components connected to the input and output of the wire
        public Comp Cin = new Comp();
        public Wire()
 
        }
 
        public Wire(Comp cin, Comp cout)
            Cin = cin;
            Cout = cout;
 
            Cin.wires.Add(this);
            Pt1 = new Point(cin.Pout.X, cin.Pout.Y);
            Loc = Pt1;
 
            Width =
Math.Abs(Pt2.X - Pt1.X);
            Height = Math.Abs(Pt2.Y - Pt1.Y);
            if (Height == 0)
 
            boundBox = new Rectangle(Pt1.X, Pt1.Y, Width,
Height);
 
        public override void
Draw(Graphics gr)
            // Draw the wire end caps
            drawEndCaps(gr);
            checkSelect();
 
            if (Pt1.X != Pt2.X || Pt1.Y != Pt2.Y)
                // Draw L-shaped wire
               
gr.DrawLine(drawPen, Pt1.X, Pt1.Y, Pt1.X, Pt2.Y); // Vertical line
               
gr.DrawLine(drawPen, Pt1.X, Pt2.Y, Pt2.X, Pt2.Y); // Horizontal line
            }
            else
            {
                // Draw straight wire
                gr.DrawLine(drawPen, Pt1, Pt2);
            }
        }
 
        private void drawEndCaps(Graphics gr)
            if (this.endcapsVisible || this.isSelected)
                // Draw custom end cap for Pt1
                Rectangle rect1 = new Rectangle(
                     2 *
endcap_radius, 2 * endcap_radius);
               
gr.DrawRectangle(Pens.Red, rect1);    
// Rectangular end cap
 
                // Draw custom end cap for Pt2
                Rectangle
rect2 = new Rectangle(
                     2 * endcap_radius, 2 * endcap_radius);
               
gr.DrawRectangle(Pens.Red, rect2);   
// Rectangular end cap
            }
        }
    }
}
  
Note that the wire Draw() method draws the wire directly to the graphics window.
Select Component
Add a new button on the MainForm:
- Text: Select Mode
- Name: btnSelect
Add a boolean attribute to MainForm.cs:
       private bool
isSelectMode = false;
Add a click event handler to btnSelect to toggle the logic state of isSelectMode:
       private void btnSelect_Click(object sender, EventArgs e)
            isSelectMode =
!isSelectMode;
        }
When the isSelectMode flag is set, the user can select a component or wire on the schematic.
In MainForm.cs add a MouseDown event handler and hitTest to check if the user has clicked on a component. If a component is hit, set the components isSelected flag to true.
        private void schematicCanvas_MouseDown(object sender, MouseEventArgs e)
            isMouseDown = true;
            // Snap the start point to the Grid
            int x = e.X;
            NewPt1.Y = y;
 
            if (!lineDrawing)
                //
Iterate over the component list and test each to see if it is "hit"
                foreach (Comp comp in ckt.comps)
                    if (hitTest(comp))
                       
tempComp = comp;
                       
offset.X = NewPt1.X - comp.Loc.X;
                       
offset.Y = NewPt1.Y - comp.Loc.Y;
 
                        if (isSelectMode)
                            if (!comp.isSelected)
                               
comp.isSelected = false;
 
                       
schematicCanvas.Invalidate();
                    }
 
                    foreach(Wire wire in comp.wires)
                        if (isSelectMode)
                            if (hitTest(wire))
                                if (!wire.isSelected)
                                   
wire.isSelected = false;
                        }
 
                       
schematicCanvas.Invalidate();
                    }
                }
            }
        }
Mouse down iterates over all components in the component list as well as all wires in each wire list and sets the isSelected flag.
       private bool hitTest(Comp comp)
            Debug.WriteLine("Hit test on comp: " +
comp.ToString() + " Loc: " + comp.Loc.ToString());
            hit = false;
            {
               
Debug.WriteLine("Hit!");
            else
            {
               
Debug.WriteLine("No Hit!");
 
            return hit;
In Comp.cs, add two helper methods that change the drawPen to Red if the component is selected and draw red rectangles (end caps) on the input and/or output leads.
        public void checkSelect()
            if (this.isSelected)
                this.drawPen = new Pen(Color.Black);
 
        public void drawSelectRect(Graphics gr, Point p1)
            if (this.isSelected)
                Rectangle
rect1 = new Rectangle(
                     2 *
endcap_radius, 2 * endcap_radius);
               
gr.DrawRectangle(Pens.Red, rect1);    
// Rectangular end cap
            }
        }
Each of the component subclasses have calls to checkSelect() and drawSelectRect() in the Draw() method. Note that a call to drawSelectRect() is needed for each input/output lead.
In MainForm.cs, add a KeyDown event handler to detect the Delete keypress:
        private void TestDeleteMainForm_KeyDown(object sender, KeyEventArgs e)
            if (e.KeyCode == Keys.Delete)    // 46 - Delete
Key
            {
               
confirmDelete();
            }
        }
Note that we used the KeyDown event rather than the KeyPress event which allows the use of KeyCodes and KeyPress will not detect the Delete key.
Next, add the confirmDelete() method to verify that the currently selected component should be deleted.
        void confirmDelete()
            DialogResult res
= MessageBox.Show("Are you sure you want to
Delete all selected                 components",
               
Debug.WriteLine("Delete confirmed!");
             }
            if (res == DialogResult.Cancel)
               
Debug.WriteLine("Delete cancelled...");
        }
In order to show the MessageBox, schematicCanvas.Invalidate() not be included in the Paint event as it will keep invalidating the window. 
        private void schematicCanvas_Paint(object sender, PaintEventArgs e)
           
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
 
            foreach (Comp comp in ckt.comps)
               
comp.Draw(e.Graphics);
 
                foreach (Wire wire in comp.wires)
                   
wire.Draw(e.Graphics);
                }
            }
        }
Finally, add a deleteSelectedComps() method that will delete all references to the selected component. This will be done in "for" loops rather than "foreach" using array indexes to the component and wire lists.
        private void deleteSelectedComps()
            // Search for selected components
            for (int i = 0; i < ckt.comps.Count; i++)
                if (ckt.comps[i].isSelected)
                    // Iterate over all components again
                    for (int k = 0; k < ckt.comps.Count; k++)
                        // Iterate over the wire list for the current component
                        for (int j = 0; j < ckt.comps[k].wires.Count; j++)
                            // If wire Cout = selected component, delete the wire
                            if (ckt.comps[k].wires[j].Cout ==
ckt.comps[i])
                        }
                    }
 
                    // Delete the selected component
                    ckt.comps.RemoveAt(i);
                }
            }
 
           
schematicCanvas.Invalidate();
        }
We have successfully integrated component rotation and delete capabilities into the TestDelete project. The full source code for the Test Delete project is available on GitHub.



 
 
 
 
 
No comments:
Post a Comment