Introduction
Hello and welcome to the third and final installment of this article series. So far, we have done a lot of work by building the shapes, the logic to resize them, the properties of the various shapes, and the lines measuring the distance between the shapes whilst moving them around. This installment will bring everything together by actually (finally!) creating the canvas to draw upon.
So, let’s continue from where we last stopped.
Create a new class and name it shpCanvas.
Override the OnPaint event, and the following:
C#
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
if (this.Mode == SnapModes.SnapToGrid)
{
this.PaintGrid(e.Graphics);
}
else if (this.Mode == SnapModes.SnapLines)
{
if (snapLines != null && snapLines.Count > 0)
{
foreach (var snapLine in snapLines)
snapLine.Draw(e.Graphics);
}
}
foreach (var s in this.Shapes)
{
s.Draw(e.Graphics);
}
e.Graphics.SmoothingMode =
System.Drawing.Drawing2D.SmoothingMode.Default;
foreach (var s in this.SelectedShapes)
{
s.DrawGrabHandles(e.Graphics,
this.SelectedShapes.IndexOf(s) == 0);
}
}
private void PaintGrid(Graphics g)
{
const int GridSize = 10;
for (var x = 0; x < this.Width; x += GridSize)
{
for (var y = 0; y < this.Height; y += GridSize)
{
g.DrawLine(Pens.LightGray, 0, y, this.Width, y);
g.DrawLine(Pens.LightGray, x, 0, x, this.Height);
}
}
}
VB.NET
Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
MyBase.OnPaint(e)
If Me.Mode = SnapModes.SnapToGrid Then
Me.PaintGrid(e.Graphics)
ElseIf Me.Mode = SnapModes.SnapLines Then
If snapLines IsNot Nothing AndAlso snapLines.Count _
> 0 Then
For Each snapLine In snapLines
snapLine.Draw(e.Graphics)
Next
End If
End If
For Each s In Me.shapes
s.Draw(e.Graphics)
Next
e.Graphics.SmoothingMode = System.Drawing.Drawing2D. _
SmoothingMode.[Default]
For Each s In Me.selectedshapes
s.DrawGrabHandles(e.Graphics, _
Me.selectedshapes.IndexOf(s) = 0)
Next
End Sub
Private Sub PaintGrid(ByVal g As Graphics)
Const GridSize As Integer = 10
Dim x = 0
While x < Me.Width
Dim y = 0
While y < Me.Height
g.DrawLine(Pens.LightGray, 0, y, Me.Width, y)
g.DrawLine(Pens.LightGray, x, 0, x, Me.Height)
y += GridSize
End While
x += GridSize
End While
End Sub
For every change to the canvas, the program calculates its size of the grid and draws the necessary snaplines.
Add the shape selection logic to show the user that the particular shape has been selected.
C#
private shpColl shapes;
public shpColl Shapes
{
get
{
return shapes;
}
}
[Browsable(false)]
public shpShape SelectedShape
{
get
{
if (this.SelectedShapes != null &&
this.SelectedShapes.Count > 0) return
this.SelectedShapes[0];
return null;
}
}
private List<shpShape> selectedshapes;
public List<shpShape> SelectedShapes
{
get
{
return selectedshapes;
}
}
public event EventHandler SelectedShapeChanged;
protected virtual void OnSelectedShapeChanged(EventArgs e)
{
this.SelectedShapeChanged?.Invoke(this, e);
}
public void Selection(shpShape shape)
{
if (shape == null)
{
selectedshapes = new List<shpShape>();
this.OnSelectedShapeChanged(EventArgs.Empty);
return;
}
if (this.SelectedShapes == null) selectedshapes = new
List<shpShape>();
if (this.SelectedShapes.Contains(shape))
{
this.SelectedShapes.Remove(shape);
this.SelectedShapes.Insert(0, shape);
}
else
{
selectedshapes = new List<shpShape>();
this.SelectedShapes.Add(shape);
}
this.OnSelectedShapeChanged(EventArgs.Empty);
this.Invalidate();
}
public void InvalidateRect(Rectangle rect)
{
rect.Inflate(1, 1);
this.Invalidate(rect);
}
public void InvalidateShape(shpShape shape)
{
this.InvalidateRect(shape.Bounds);
}
VB.NET
Private shpes As shpColl
Public ReadOnly Property Shapes As shpColl
Get
Return shpes
End Get
End Property
<Browsable(False)>
Public ReadOnly Property SelectedShape As shpShape
Get
If Me.selectedshapes IsNot Nothing AndAlso _
Me.selectedshapes.Count > 0 Then Return _
Me.selectedshapes(0)
Return Nothing
End Get
End Property
Private selshapes As List(Of shpShape)
Public ReadOnly Property SelectedShapes As List(Of shpShape)
Get
Return selshapes
End Get
End Property
Public Event SelectedShapeChanged As EventHandler
Protected Overridable Sub OnSelectedShapeChanged(ByVal e _
As EventArgs)
RaiseEvent SelectedShapeChanged(Me, e)
End Sub
Public Sub Selection(ByVal shape As shpShape)
If shape Is Nothing Then
selshapes = New List(Of shpShape)()
Me.OnSelectedShapeChanged(EventArgs.Empty)
Return
End If
If Me.SelectedShapes Is Nothing Then selshapes = New _
List(Of shpShape)()
If Me.selectedshapes.Contains(shape) Then
Me.selectedshapes.Remove(shape)
Me.selectedshapes.Insert(0, shape)
Else
selshapes = New List(Of shpShape)()
Me.selectedshapes.Add(shape)
End If
Me.OnSelectedShapeChanged(EventArgs.Empty)
Me.Invalidate()
End Sub
Public Sub InvalidateRect(ByVal rect As Rectangle)
rect.Inflate(1, 1)
Me.Invalidate(rect)
End Sub
Public Sub InvalidateShape(ByVal shape As shpShape)
Me.InvalidateRect(shape.Bounds)
End Sub
Add the code to align the shapes.
C#
private void AlignShapes(shpShape moving, shpShape shpfixed)
{
Snapping = true;
if (this.AlignShape(moving.Bounds.Top, shpfixed.Bounds.Top))
{
this.AlignTop(shpfixed, moving);
}
if (this.AlignShape(moving.Bounds.Bottom,
shpfixed.Bounds.Bottom))
{
this.AlignBottom(shpfixed, moving);
}
if (this.AlignShape(moving.Bounds.Left,
shpfixed.Bounds.Left))
{
this.AlignLeft(shpfixed, moving);
}
if (this.AlignShape(moving.Bounds.Right,
shpfixed.Bounds.Right))
{
this.AlignRight(shpfixed, moving);
}
Snapping = false;
}
private bool AlignShape(int shapeLoc, int otherLoc)
{
return shapeLoc.Between(otherLoc + this.ShapeAlignDist,
otherLoc - this.ShapeAlignDist);
}
private void AlignLeft(shpShape shpfixed, shpShape moving)
{
moving.Move(new Point(shpfixed.Bounds.Left,
moving.Bounds.Y));
lnSnap snap;
if (moving.Bounds.Y > shpfixed.Bounds.Y)
{
snap = new lnSnap(moving.Bounds.Left, shpfixed.Bounds.Top,
moving.Bounds.Left, moving.Bounds.Bottom, Color.Blue);
}
else
{
snap = new lnSnap(moving.Bounds.Left, moving.Bounds.Top,
moving.Bounds.Left, shpfixed.Bounds.Bottom,
Color.Blue);
}
snapLines.Add(snap);
}
private void AlignRight(shpShape shpfixed, shpShape moving)
{
moving.Move(new Point(shpfixed.Bounds.Right -
moving.Bounds.Width, moving.Bounds.Y));
lnSnap snap;
if (moving.Bounds.Y > shpfixed.Bounds.Y)
{
snap = new lnSnap(moving.Bounds.Right,
shpfixed.Bounds.Top, moving.Bounds.Right,
moving.Bounds.Bottom, Color.Blue);
}
else
{
snap = new lnSnap(moving.Bounds.Right, moving.Bounds.Top,
moving.Bounds.Right, shpfixed.Bounds.Bottom,
Color.Blue);
}
snapLines.Add(snap);
}
private void AlignTop(shpShape shpfixed, shpShape moving)
{
moving.Move(new Point(moving.Bounds.X, shpfixed.Bounds.Top));
lnSnap snap;
if (moving.Bounds.X > shpfixed.Bounds.X)
{
snap = new lnSnap(shpfixed.Bounds.Left, moving.Bounds.Top,
moving.Bounds.Right, moving.Bounds.Top, Color.Blue);
}
else
{
snap = new lnSnap(moving.Bounds.Left, moving.Bounds.Top,
shpfixed.Bounds.Right, moving.Bounds.Top, Color.Blue);
}
snapLines.Add(snap);
}
private void AlignBottom(shpShape shpfixed, shpShape moving)
{
moving.Move(new Point(moving.Location.X,
shpfixed.Bounds.Bottom - moving.Bounds.Height));
lnSnap snap;
if (moving.Bounds.X > shpfixed.Bounds.X)
{
snap = new lnSnap(shpfixed.Bounds.Left,
moving.Bounds.Bottom, moving.Bounds.Right,
moving.Bounds.Bottom, Color.Blue);
}
else
{
snap = new lnSnap(moving.Bounds.Left,
moving.Bounds.Bottom, shpfixed.Bounds.Right,
moving.Bounds.Bottom, Color.Blue);
}
snapLines.Add(snap);
}
VB.NET
Private Sub AlignShapes(ByVal moving As shpShape, _
ByVal shpfixed As shpShape)
Snapping = True
If Me.AlignShape(moving.Bounds.Top, shpfixed.Bounds.Top) Then
Me.AlignTop(shpfixed, moving)
End If
If Me.AlignShape(moving.Bounds.Bottom, _
shpfixed.Bounds.Bottom) Then
Me.AlignBottom(shpfixed, moving)
End If
If Me.AlignShape(moving.Bounds.Left, _
shpfixed.Bounds.Left) Then
Me.AlignLeft(shpfixed, moving)
End If
If Me.AlignShape(moving.Bounds.Right, _
shpfixed.Bounds.Right) Then
Me.AlignRight(shpfixed, moving)
End If
Snapping = False
End Sub
Private Function AlignShape(ByVal shapeLoc As Integer, _
ByVal otherLoc As Integer) As Boolean
Return shapeLoc.Between(otherLoc + Me.ShapeAlignDist, _
otherLoc - Me.ShapeAlignDist)
End Function
Private Sub AlignLeft(ByVal shpfixed As shpShape, _
ByVal moving As shpShape)
moving.Move(New Point(shpfixed.Bounds.Left, moving.Bounds.Y))
Dim snap As lnSnap
If moving.Bounds.Y > shpfixed.Bounds.Y Then
snap = New lnSnap(moving.Bounds.Left, _
shpfixed.Bounds.Top, moving.Bounds.Left, _
moving.Bounds.Bottom, Color.Blue)
Else
snap = New lnSnap(moving.Bounds.Left, moving.Bounds.Top, _
moving.Bounds.Left, shpfixed.Bounds.Bottom, Color.Blue)
End If
snapLines.Add(snap)
End Sub
Private Sub AlignRight(ByVal shpfixed As shpShape, _
ByVal moving As shpShape)
moving.Move(New Point(shpfixed.Bounds.Right - _
moving.Bounds.Width, moving.Bounds.Y))
Dim snap As lnSnap
If moving.Bounds.Y > shpfixed.Bounds.Y Then
snap = New lnSnap(moving.Bounds.Right, _
shpfixed.Bounds.Top, moving.Bounds.Right, _
moving.Bounds.Bottom, Color.Blue)
Else
snap = New lnSnap(moving.Bounds.Right, _
moving.Bounds.Top, moving.Bounds.Right, _
shpfixed.Bounds.Bottom, Color.Blue)
End If
snapLines.Add(snap)
End Sub
Private Sub AlignTop(ByVal shpfixed As shpShape, ByVal moving _
As shpShape)
moving.Move(New Point(moving.Bounds.X, shpfixed.Bounds.Top))
Dim snap As lnSnap
If moving.Bounds.X > shpfixed.Bounds.X Then
snap = New lnSnap(shpfixed.Bounds.Left, _
moving.Bounds.Top, moving.Bounds.Right, _
moving.Bounds.Top, Color.Blue)
Else
snap = New lnSnap(moving.Bounds.Left, moving.Bounds.Top, _
shpfixed.Bounds.Right, moving.Bounds.Top, Color.Blue)
End If
snapLines.Add(snap)
End Sub
Private Sub AlignBottom(ByVal shpfixed As shpShape, _
ByVal moving As shpShape)
moving.Move(New Point(moving.Location.X, _
shpfixed.Bounds.Bottom - moving.Bounds.Height))
Dim snap As lnSnap
If moving.Bounds.X > shpfixed.Bounds.X Then
snap = New lnSnap(shpfixed.Bounds.Left, _
moving.Bounds.Bottom, moving.Bounds.Right, _
moving.Bounds.Bottom, Color.Blue)
Else
snap = New lnSnap(moving.Bounds.Left, _
moving.Bounds.Bottom, shpfixed.Bounds.Right, _
moving.Bounds.Bottom, Color.Blue)
End If
snapLines.Add(snap)
End Sub
This aligns the various shapes according to the selection. The snapping logic works similar to the preceding alignment functions.
The code can be found on GitHub:
Conclusion
This concludes this article series. Remember, not all the code is listed here, but is indeed available on GitHub. I hope you have enjoyed this little project as much as I did.