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.