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;
using System.Collections.Generic;
// 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 halfCompSize = 30;
protected int leadL = 10; // Length of input and output leads
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>();
public Point Pin;
public Point Pout;
// End caps variables
protected const int
endcap_radius = 3;
protected bool endcapsVisible = false;
// 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.Red);
else
drawPen = new Pen(Color.Black);
}
public void drawSelectRect(Graphics gr, Point p1)
{
if (isSelected)
{
Rectangle
rect1 = new Rectangle(
p1.X -
endcap_radius, p1.Y - endcap_radius,
2 *
endcap_radius, 2 * endcap_radius);
gr.DrawRectangle(Pens.Red, rect1);
// Rectangular end cap
}
}
Input Port Class (InPort.cs)
// C# Libraries
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
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);
Point p3 = new Point(p2.X - 10, p2.Y - 10);
Point p4 = new Point(p3.X - 30, p3.Y);
Point p5 = new Point(p4.X, p4.Y + 20);
Point p6 = new Point(p5.X + 30, p5.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";
pt = new Point(Loc.X+10, Loc.Y+5);
gp.AddString(compText, family, fontStyle, emSize, pt, format);
// Set rotation angle
if (isRotated)
angle = 90;
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();
myMatrix.RotateAt(angle,
rotatePoint, MatrixOrder.Append);
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;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
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);
Pout = new Point(Loc.X + compSize, Loc.Y +
30);
}
public override void
Draw(Graphics gr)
{
// Component selection
checkSelect();
drawSelectRect(gr, new
Point(Loc.X, Loc.Y + 30));
drawSelectRect(gr, new
Point(Loc.X + compL - endcap_radius, 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 + "Ω";
pt = new Point(Loc.X + 5, Loc.Y + 5);
gp.AddString(compText, family, fontStyle, emSize, pt, format);
// Set rotation angle
if (isRotated)
angle = 90;
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();
myMatrix.RotateAt(angle, rotatePoint, MatrixOrder.Append);
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;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
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);
Point p3 = new Point(p2.X + 10, p2.Y - 10);
Point p4 = new Point(p3.X + 30, p3.Y);
Point p5 = new Point(p4.X, p4.Y + 20);
Point p6 = new Point(p5.X - 30, p5.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";
pt = new Point(Loc.X + 20, Loc.Y + 5);
gp.AddString(compText,
family, fontStyle, emSize, pt, format);
// Set rotation angle
if (isRotated)
angle = 90;
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();
myMatrix.RotateAt(angle, rotatePoint, MatrixOrder.Append);
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;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
// Microwave Tools Libraries
using TestDelete.Components;
namespace TestDelete.Wires
{
public class Wire : Comp
{
// Create wire start and end points
public Point Pt1 = new Point();
public Point Pt2 = new Point();
// Add components connected to the input and output of the wire
public Comp Cin = new Comp();
public Comp Cout = 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);
Pt2 = new Point(cout.Pin.X, cin.Pout.Y);
Loc = Pt1;
Width =
Math.Abs(Pt2.X - Pt1.X);
Height = Math.Abs(Pt2.Y - Pt1.Y);
if (Height == 0)
Height = 10;
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(
Pt1.X -
endcap_radius, Pt1.Y - endcap_radius,
2 *
endcap_radius, 2 * endcap_radius);
gr.DrawRectangle(Pens.Red, rect1);
// Rectangular end cap
// Draw custom end cap for Pt2
Rectangle
rect2 = new Rectangle(
Pt2.X -
endcap_radius, Pt2.Y - endcap_radius,
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;
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 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 = true;
else
comp.isSelected = false;
}
schematicCanvas.Invalidate();
}
foreach(Wire wire in comp.wires)
{
if (isSelectMode)
{
if (hitTest(wire))
{
if (!wire.isSelected)
wire.isSelected = true;
else
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());
Debug.WriteLine("Mouse Pos: " +
NewPt1.ToString());
bool hit;
hit = false;
if ((NewPt1.X >= comp.boundBox.X
&& NewPt1.X <= comp.boundBox.X + comp.boundBox.Width) &&
(NewPt1.Y
>= comp.boundBox.Y && NewPt1.Y <= comp.boundBox.Y +
comp.boundBox.Height))
{
Debug.WriteLine("Hit!");
hit = true;
}
else
{
Debug.WriteLine("No Hit!");
hit = false;
}
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.Red);
else
this.drawPen = new Pen(Color.Black);
}
public void drawSelectRect(Graphics gr, Point p1)
{
if (this.isSelected)
{
Rectangle
rect1 = new Rectangle(
p1.X -
endcap_radius, p1.Y - endcap_radius,
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",
"Confirmation",
MessageBoxButtons.OKCancel, MessageBoxIcon.Information);
if (res == DialogResult.OK)
{
Debug.WriteLine("Delete confirmed!");
deleteSelectedComps();
}
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])
ckt.comps[k].wires.RemoveAt(j);
}
}
// 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