Introduction
Drawing applications have always had a soft place in my heart. In the past, I have written a lot about drawing and creating funky stuff with shapes and the .NET Framework’s System.Drawing namespace. Today is not much different. Today, you will learn how to create an editable shape. This shape will be able to be moved around, resized, have some Properties set, as well as have a snap line between shapes.
There is a lot of work, so let’s get started.
Start Visual Studio and create a new C# or VB.NET Windows Forms application. You do not need to design anything because the design will take place mostly through code. Add a Class Library project to your Windows Forms project by selecting File, Add, New Project.
You may name your projects and classes anything you like, but keep in mind that I may have named mine differently.
Add a new Class to your Class Library project. This class will be used to handle the Shape’s events and settable properties. These properties will be set through a Properties Window built into the control. The Properties include:
- BackColor
- Name
- Size
The events include Resizing events as well as moving events. Add the complete Class now:
C#
public abstract class shpShape { protected shpShape() : this(Point.Empty) { } protected shpShape(Point loc) { this.MinimumSize = new Size(50, 50); this.Bounds = new Rectangle(loc, this.DefaultSize); this.BackColor = Color.White; this.Locked = false; } public enum HitStatus { None, Drag, ResizeTopLeft, ResizeTopRight, ResizeBottomLeft, ResizeBottomRight, ResizeLeft, ResizeTop, ResizeRight, ResizeBottom } public event EventHandler LocationChanged; public event EventHandler SizeChanged; public event EventHandler AppearanceChanged; protected virtual void OnLocationChanged(EventArgs e) { if (LocationChanged != null) this.LocationChanged(this, e); } protected virtual void OnSizeChanged(EventArgs e) { if (this.SizeChanged != null) this.SizeChanged(this, e); } protected virtual void OnAppearanceChanged(EventArgs e) { if (this.AppearanceChanged != null) this.AppearanceChanged(this, e); } private string name = String.Empty; public string Name { get { return name; } set { name = value; } } private Rectangle bounds; [Browsable(false)] public Rectangle Bounds { get { return bounds; } set { bounds = value; this.GrabHandles.SetBounds(value); } } [XmlIgnore] public Point Location { get { return this.Bounds.Location; } set { if (this.Bounds.Location == value) return; Rectangle rect = this.Bounds; rect.Location = value; this.Bounds = rect; this.OnLocationChanged(EventArgs.Empty); } } [XmlIgnore] public Size Size { get { return this.Bounds.Size; } set { if (this.Bounds.Size == value) return; Rectangle rect = this.Bounds; rect.Size = value; this.Bounds = rect; this.OnSizeChanged(EventArgs.Empty); } } private bool locked; public virtual bool Locked { get { return locked; } set { locked = value; this.GrabHandles.Locked = value; } } private shpHandles grabhandles; [XmlIgnore] internal shpHandles GrabHandles { get { if (grabhandles == null) grabhandles = new shpHandles(this); return grabhandles; } } private Size minsize; [Browsable(false)] public Size MinimumSize { get { return minsize; } set { if (value.Width < 0 || value.Height < 0) minsize = value; } } internal Point MoveStart { get; set; } [XmlIgnore] protected virtual Size DefaultSize { get { return new Size(150, 150); } } [XmlIgnore] public Color BackColor { get; set; } [Browsable(false)] public int XmlBackColor { get { return this.BackColor.ToArgb(); } set { this.BackColor = Color.FromArgb(value); } } [Browsable(false)] [XmlIgnore] public ContextMenuStrip ContextMenuStrip { get; set; } public abstract void Draw(Graphics g); internal virtual void DrawGrabHandles(Graphics g, bool first) { this.GrabHandles.Draw(g, first); } public void Move(Point newLocation) { if (this.Locked) return; this.Location = newLocation; } public void Resize(HitStatus hitStatus, int x, int y) { if (this.Locked) return; switch (hitStatus) { case HitStatus.ResizeBottomLeft: this.ResizeBottomLeft(x, y); break; case HitStatus.ResizeBottomRight: this.ResizeBottomRight(x, y); break; case HitStatus.ResizeTopLeft: this.ResizeTopLeft(x, y); break; case HitStatus.ResizeTopRight: this.ResizeTopRight(x, y); break; case HitStatus.ResizeLeft: this.ResizeLeft(x, y); break; case HitStatus.ResizeRight: this.ResizeRight(x, y); break; case HitStatus.ResizeTop: this.ResizeTop(x, y); break; case HitStatus.ResizeBottom: this.ResizeBottom(x, y); break; } } private void ResizeBottomLeft(int x, int y) { Rectangle oldBounds = this.Bounds; int newTop = oldBounds.Top; int newLeft = x; int newWidth = oldBounds.Right - x; int newHeight = y - oldBounds.Top; if (newWidth < this.MinimumSize.Width) { newWidth = this.MinimumSize.Width; newLeft = oldBounds.Right - newWidth; } if (newHeight < this.MinimumSize.Height) { newHeight = this.MinimumSize.Height; } this.Bounds = new Rectangle(newLeft, newTop, newWidth, newHeight); } private void ResizeBottomRight(int x, int y) { Rectangle oldBounds = this.Bounds; int newTop = oldBounds.Top; int newLeft = oldBounds.Left; int newWidth = x - newLeft; int newHeight = y - oldBounds.Top; if (newWidth < this.MinimumSize.Width) { newWidth = this.MinimumSize.Width; } if (newHeight < this.MinimumSize.Height) { newHeight = this.MinimumSize.Height; } this.Bounds = new Rectangle(newLeft, newTop, newWidth, newHeight); } private void ResizeTopLeft(int x, int y) { Rectangle oldBounds = this.Bounds; int newTop = y; int newLeft = x; int newWidth = oldBounds.Right - x; int newHeight = oldBounds.Bottom - y; if (newWidth < this.MinimumSize.Width) { newWidth = this.MinimumSize.Width; newLeft = oldBounds.Right - newWidth; } if (newHeight < this.MinimumSize.Height) { newHeight = this.MinimumSize.Height; newTop = oldBounds.Bottom - newHeight; } this.Bounds = new Rectangle(newLeft, newTop, newWidth, newHeight); } private void ResizeTopRight(int x, int y) { Rectangle oldBounds = this.Bounds; int newTop = y; int newLeft = oldBounds.Left; int newWidth = x - newLeft; int newHeight = oldBounds.Bottom - y; if (newWidth < this.MinimumSize.Width) { newWidth = this.MinimumSize.Width; } if (newHeight < this.MinimumSize.Height) { newHeight = this.MinimumSize.Height; newTop = oldBounds.Bottom - newHeight; } this.Bounds = new Rectangle(newLeft, newTop, newWidth, newHeight); } private void ResizeTop(int x, int y) { Rectangle oldBounds = this.Bounds; int newTop = y; int newLeft = oldBounds.Left; int newWidth = oldBounds.Width; int newHeight = oldBounds.Bottom - y; if (newHeight < this.MinimumSize.Height) { newHeight = this.MinimumSize.Height; newTop = oldBounds.Bottom - newHeight; } this.Bounds = new Rectangle(newLeft, newTop, newWidth, newHeight); } private void ResizeLeft(int x, int y) { Rectangle oldBounds = this.Bounds; int newTop = oldBounds.Top; int newLeft = x; int newWidth = oldBounds.Right - x; int newHeight = oldBounds.Height; if (newWidth < this.MinimumSize.Width) { newWidth = this.MinimumSize.Width; newLeft = oldBounds.Right - newWidth; } this.Bounds = new Rectangle(newLeft, newTop, newWidth, newHeight); } private void ResizeRight(int x, int y) { Rectangle oldBounds = this.Bounds; int newTop = oldBounds.Top; int newLeft = oldBounds.Left; int newWidth = x - newLeft; int newHeight = oldBounds.Height; if (newWidth < this.MinimumSize.Width) { newWidth = this.MinimumSize.Width; } this.Bounds = new Rectangle(newLeft, newTop, newWidth, newHeight); } private void ResizeBottom(int x, int y) { Rectangle oldBounds = this.Bounds; int newTop = oldBounds.Top; int newLeft = oldBounds.Left; int newWidth = oldBounds.Width; int newHeight = y - oldBounds.Top; if (newHeight < this.MinimumSize.Height) { newHeight = this.MinimumSize.Height; } this.Bounds = new Rectangle(newLeft, newTop, newWidth, newHeight); } public HitStatus HitTest(Point loc) { if (this.GrabHandles.TotalBounds.Contains(loc)) { if (this.GrabHandles.TopLeft.Contains(loc)) return HitStatus.ResizeTopLeft; else if (this.GrabHandles.TopRight.Contains(loc)) return HitStatus.ResizeTopRight; else if (this.GrabHandles.BottomLeft.Contains(loc)) return HitStatus.ResizeBottomLeft; else if (this.GrabHandles.BottomRight.Contains(loc)) return HitStatus.ResizeBottomRight; if (Rectangle.Union(this.GrabHandles.TopLeft, this.GrabHandles.TopRight).Contains(loc)) return HitStatus.ResizeTop; else if (Rectangle.Union(this.GrabHandles.TopRight, this.GrabHandles.BottomRight).Contains(loc)) return HitStatus.ResizeRight; else if (Rectangle.Union(this.GrabHandles.BottomRight, this.GrabHandles.BottomLeft).Contains(loc)) return HitStatus.ResizeBottom; else if (Rectangle.Union(this.GrabHandles.BottomLeft, this.GrabHandles.TopLeft).Contains(loc)) return HitStatus.ResizeLeft; return HitStatus.Drag; } else { return HitStatus.None; } } public virtual string ShapeName() { return this.GetType().Name; } }
VB.NET
Imports System Imports System.ComponentModel Imports System.Drawing Imports System.Windows.Forms Imports System.Xml.Serialization Public MustInherit Class shpShape Protected Sub New() Me.New(Point.Empty) End Sub Protected Sub New(ByVal loc As Point) Me.MinimumSize = New Size(50, 50) Me.bounds = New Rectangle(loc, Me.DefaultSize) Me.BackColor = Color.White Me.locked = False End Sub Public Enum HitStatus None Drag ResizeTopLeft ResizeTopRight ResizeBottomLeft ResizeBottomRight ResizeLeft ResizeTop ResizeRight ResizeBottom End Enum Public Event LocationChanged As EventHandler Public Event SizeChanged As EventHandler Public Event AppearanceChanged As EventHandler Protected Overridable Sub OnLocationChanged(ByVal e As _ EventArgs) RaiseEvent LocationChanged(Me, e) End Sub Protected Overridable Sub OnSizeChanged(ByVal e As EventArgs) RaiseEvent SizeChanged(Me, e) End Sub Protected Overridable Sub OnAppearanceChanged(ByVal e As _ EventArgs) RaiseEvent AppearanceChanged(Me, e) End Sub Private nme As String = String.Empty Public Property Name As String Get Return nme End Get Set(ByVal value As String) nme = value End Set End Property Private bonds As Rectangle <Browsable(False)> Public Property Bounds As Rectangle Get Return bonds End Get Set(ByVal value As Rectangle) bonds = value Me.grabhandles.SetBounds(value) End Set End Property <XmlIgnore> Public Property Location As Point Get Return Me.bounds.Location End Get Set(ByVal value As Point) If Me.bounds.Location = value Then Return Dim rect As Rectangle = Me.bounds rect.Location = value Me.bounds = rect Me.OnLocationChanged(EventArgs.Empty) End Set End Property <XmlIgnore> Public Property Size As Size Get Return Me.bounds.Size End Get Set(ByVal value As Size) If Me.bounds.Size = value Then Return Dim rect As Rectangle = Me.bounds rect.Size = value Me.bounds = rect Me.OnSizeChanged(EventArgs.Empty) End Set End Property Private lcked As Boolean Public Overridable Property Locked As Boolean Get Return lcked End Get Set(ByVal value As Boolean) lcked = value Me.grabhandles.Locked = value End Set End Property Private grhandles As shpHandles <XmlIgnore> Friend ReadOnly Property GrabHandles As shpHandles Get If grhandles Is Nothing Then grhandles = New shpHandles(Me) Return grhandles End Get End Property Private minsize As Size <Browsable(False)> Public Property MinimumSize As Size Get Return minsize End Get Set(ByVal value As Size) If value.Width < 0 OrElse value.Height < 0 Then _ minsize = value End Set End Property Friend Property MoveStart As Point <XmlIgnore> Protected Overridable ReadOnly Property DefaultSize As Size Get Return New Size(150, 150) End Get End Property <XmlIgnore> Public Property BackColor As Color <Browsable(False)> Public Property XmlBackColor As Integer Get Return Me.BackColor.ToArgb() End Get Set(ByVal value As Integer) Me.BackColor = Color.FromArgb(value) End Set End Property <Browsable(False)> <XmlIgnore> Public Property ContextMenuStrip As ContextMenuStrip Public MustOverride Sub Draw(ByVal g As Graphics) Friend Overridable Sub DrawGrabHandles(ByVal g As Graphics, _ ByVal first As Boolean) Me.grabhandles.Draw(g, first) End Sub Public Sub Move(ByVal newLocation As Point) If Me.locked Then Return Me.Location = newLocation End Sub Public Sub Resize(ByVal hitStatus As HitStatus, ByVal x _ As Integer, ByVal y As Integer) If Me.locked Then Return Select Case hitStatus Case HitStatus.ResizeBottomLeft Me.ResizeBottomLeft(x, y) Case HitStatus.ResizeBottomRight Me.ResizeBottomRight(x, y) Case HitStatus.ResizeTopLeft Me.ResizeTopLeft(x, y) Case HitStatus.ResizeTopRight Me.ResizeTopRight(x, y) Case HitStatus.ResizeLeft Me.ResizeLeft(x, y) Case HitStatus.ResizeRight Me.ResizeRight(x, y) Case HitStatus.ResizeTop Me.ResizeTop(x, y) Case HitStatus.ResizeBottom Me.ResizeBottom(x, y) End Select End Sub Private Sub ResizeBottomLeft(ByVal x As Integer, _ ByVal y As Integer) Dim oldBounds As Rectangle = Me.bounds Dim newTop As Integer = oldBounds.Top Dim newLeft As Integer = x Dim newWidth As Integer = oldBounds.Right - x Dim newHeight As Integer = y - oldBounds.Top If newWidth < Me.MinimumSize.Width Then newWidth = Me.MinimumSize.Width newLeft = oldBounds.Right - newWidth End If If newHeight < Me.MinimumSize.Height Then newHeight = Me.MinimumSize.Height End If Me.bounds = New Rectangle(newLeft, newTop, newWidth, _ newHeight) End Sub Private Sub ResizeBottomRight(ByVal x As Integer, _ ByVal y As Integer) Dim oldBounds As Rectangle = Me.bounds Dim newTop As Integer = oldBounds.Top Dim newLeft As Integer = oldBounds.Left Dim newWidth As Integer = x - newLeft Dim newHeight As Integer = y - oldBounds.Top If newWidth < Me.MinimumSize.Width Then newWidth = Me.MinimumSize.Width End If If newHeight < Me.MinimumSize.Height Then newHeight = Me.MinimumSize.Height End If Me.bounds = New Rectangle(newLeft, newTop, newWidth, _ newHeight) End Sub Private Sub ResizeTopLeft(ByVal x As Integer, _ ByVal y As Integer) Dim oldBounds As Rectangle = Me.bounds Dim newTop As Integer = y Dim newLeft As Integer = x Dim newWidth As Integer = oldBounds.Right - x Dim newHeight As Integer = oldBounds.Bottom - y If newWidth < Me.MinimumSize.Width Then newWidth = Me.MinimumSize.Width newLeft = oldBounds.Right - End If If newHeight < Me.MinimumSize.Height Then newHeight = Me.MinimumSize.Height newTop = oldBounds.Bottom - newHeight End If Me.bounds = New Rectangle(newLeft, newTop, newWidth, _ newHeight) End Sub Private Sub ResizeTopRight(ByVal x As Integer, _ ByVal y As Integer) Dim oldBounds As Rectangle = Me.bounds Dim newTop As Integer = y Dim newLeft As Integer = oldBounds.Left Dim newWidth As Integer = x - newLeft Dim newHeight As Integer = oldBounds.Bottom - y If newWidth < Me.MinimumSize.Width Then newWidth = Me.MinimumSize.Width End If If newHeight < Me.MinimumSize.Height Then newHeight = Me.MinimumSize.Height newTop = oldBounds.Bottom - newHeight End If Me.bounds = New Rectangle(newLeft, newTop, newWidth, _ newHeight) End Sub Private Sub ResizeTop(ByVal x As Integer, ByVal y As Integer) Dim oldBounds As Rectangle = Me.bounds Dim newTop As Integer = y Dim newLeft As Integer = oldBounds.Left Dim newWidth As Integer = oldBounds.Width Dim newHeight As Integer = oldBounds.Bottom - y If newHeight < Me.MinimumSize.Height Then newHeight = Me.MinimumSize.Height newTop = oldBounds.Bottom - newHeight End If Me.bounds = New Rectangle(newLeft, newTop, newWidth, _ newHeight) End Sub Private Sub ResizeLeft(ByVal x As Integer, ByVal y As Integer) Dim oldBounds As Rectangle = Me.bounds Dim newTop As Integer = oldBounds.Top Dim newLeft As Integer = x Dim newWidth As Integer = oldBounds.Right - x Dim newHeight As Integer = oldBounds.Height If newWidth < Me.MinimumSize.Width Then newWidth = Me.MinimumSize.Width newLeft = oldBounds.Right - newWidth End If Me.bounds = New Rectangle(newLeft, newTop, newWidth, _ newHeight) End Sub Private Sub ResizeRight(ByVal x As Integer, ByVal y As Integer) Dim oldBounds As Rectangle = Me.bounds Dim newTop As Integer = oldBounds.Top Dim newLeft As Integer = oldBounds.Left Dim newWidth As Integer = x - newLeft Dim newHeight As Integer = oldBounds.Height If newWidth < Me.MinimumSize.Width Then newWidth = Me.MinimumSize.Width End If Me.bounds = New Rectangle(newLeft, newTop, newWidth, _ newHeight) End Sub Private Sub ResizeBottom(ByVal x As Integer, ByVal y As Integer) Dim oldBounds As Rectangle = Me.bounds Dim newTop As Integer = oldBounds.Top Dim newLeft As Integer = oldBounds.Left Dim newWidth As Integer = oldBounds.Width Dim newHeight As Integer = y - oldBounds.Top If newHeight < Me.MinimumSize.Height Then newHeight = Me.MinimumSize.Height End If Me.bounds = New Rectangle(newLeft, newTop, newWidth, newHeight) End Sub Public Function HitTest(ByVal loc As Point) As HitStatus If Me.grabhandles.TotalBounds.Contains(loc) Then If Me.grabhandles.TopLeft.Contains(loc) Then Return HitStatus.ResizeTopLeft ElseIf Me.grabhandles.TopRight.Contains(loc) Then Return HitStatus.ResizeTopRight ElseIf Me.grabhandles.BottomLeft.Contains(loc) Then Return HitStatus.ResizeBottomLeft ElseIf Me.grabhandles.BottomRight.Contains(loc) Then Return HitStatus.ResizeBottomRight End If If Rectangle.Union(Me.grabhandles.TopLeft, _ Me.grabhandles.TopRight).Contains(loc) Then Return HitStatus.ResizeTop ElseIf Rectangle.Union(Me.grabhandles.TopRight, _ Me.grabhandles.BottomRight).Contains(loc) Then Return HitStatus.ResizeRight ElseIf Rectangle.Union(Me.grabhandles.BottomRight, _ Me.grabhandles.BottomLeft).Contains(loc) Then Return HitStatus.ResizeBottom ElseIf Rectangle.Union(Me.grabhandles.BottomLeft, _ Me.grabhandles.TopLeft).Contains(loc) Then Return HitStatus.ResizeLeft End If Return HitStatus.Drag Else Return HitStatus.None End If End Function Public Overridable Function ShapeName() As String Return Me.[GetType]().Name End Function End Class
The above class looks scary because of the size, but it is actually pretty straightforward, as was explained earlier. Luckily, the rest of our work today is a bit less than what we have already done.
Add another class. I have named mine shpHandles. This class will be used to draw grab handles on the shapes. These grab handles then can be clicked and used in the Shape resizing or moving process. Add the code to it:
C#
public class shpHandles { public const int Size = 3; public shpHandles(shpShape shape) { this.BorderWidth = 4; this.SetBounds(shape.Bounds); } public Rectangle BorderBounds { get; private set; } public int BorderWidth { get; set; } public bool Locked { get; set; } public Rectangle TotalBounds { get { return Rectangle.Union(this.TopLeft, this.BottomRight); } } internal Rectangle TopLeft { get { return new Rectangle(this.BorderBounds.X - Size, this.BorderBounds.Y - Size, 2 * Size + 1, 2 * Size + 1); } } internal Rectangle TopRight { get { return new Rectangle(this.BorderBounds.Right - Size, this.BorderBounds.Y - Size, 2 * Size + 1, 2 * Size + 1); } } internal Rectangle TopMiddle { get { return new Rectangle(this.BorderBounds.X + this.BorderBounds.Width / 2 - Size, this.BorderBounds.Y - Size, 2 * Size + 1, 2 * Size + 1); } } internal Rectangle MiddleLeft { get { return new Rectangle(this.BorderBounds.X - Size, this.BorderBounds.Y + this.BorderBounds.Height / 2 - Size, 2 * Size + 1, 2 * Size + 1); } } internal Rectangle MiddleRight { get { return new Rectangle(this.BorderBounds.Right - Size, this.BorderBounds.Y + this.BorderBounds.Height / 2 - Size, 2 * Size + 1, 2 * Size + 1); } } internal Rectangle MiddleMiddle { get { return new Rectangle(this.BorderBounds.X + this.BorderBounds.Width / 2 - Size, this.BorderBounds.Y + this.BorderBounds.Height / 2 - Size, 2 * Size + 1, 2 * Size + 1); } } internal Rectangle BottomLeft { get { return new Rectangle(this.BorderBounds.X - Size, this.BorderBounds.Bottom - Size, 2 * Size + 1, 2 * Size + 1); } } internal Rectangle BottomRight { get { return new Rectangle(this.BorderBounds.Right - Size, this.BorderBounds.Bottom - Size, 2 * Size + 1, 2 * Size + 1); } } internal Rectangle BottomMiddle { get { return new Rectangle(this.BorderBounds.X + this.BorderBounds.Width / 2 - Size, this.BorderBounds.Bottom - Size, 2 * Size + 1, 2 * Size + 1); } } internal void SetBounds(Rectangle shape) { this.BorderBounds = new Rectangle(shape.X - this.BorderWidth, shape.Y - this.BorderWidth, shape.Width + 2 * this.BorderWidth, shape.Height + 2 * this.BorderWidth); } internal void Draw(Graphics g, bool firstSelection) { ControlPaint.DrawBorder(g, this.BorderBounds, ControlPaint.ContrastControlDark, ButtonBorderStyle.Dotted); if (this.Locked) { this.DrawLock(g); } else { this.DrawGrabHandle(g, this.TopLeft, firstSelection); this.DrawGrabHandle(g, this.TopMiddle, firstSelection); this.DrawGrabHandle(g, this.TopRight, firstSelection); this.DrawGrabHandle(g, this.MiddleLeft, firstSelection); this.DrawGrabHandle(g, this.MiddleRight, firstSelection); this.DrawGrabHandle(g, this.BottomLeft, firstSelection); this.DrawGrabHandle(g, this.BottomMiddle, firstSelection); this.DrawGrabHandle(g, this.BottomRight, firstSelection); } } private void DrawGrabHandle(Graphics g, Rectangle rect, bool firstSel) { if (firstSel) { var rect1 = rect; var rect2 = rect; var innerRect = rect; innerRect.Inflate(-1, -1); rect1.X += 1; rect1.Width -= 2; rect2.Y += 1; rect2.Height -= 2; g.FillRectangle(Brushes.Black, rect1); g.FillRectangle(Brushes.Black, rect2); g.FillRectangle(Brushes.White, innerRect); } else { g.FillRectangle(Brushes.Black, rect); } } private void DrawLock(Graphics g) { var rect = this.TopLeft; rect.X -= 1; rect.Width -= 1; rect.Height -= 2; var innerRect = rect; innerRect.Inflate(-1, -1); g.FillRectangle(Brushes.White, innerRect); g.DrawRectangle(Pens.Black, rect); var outerHandleRect1 = rect; outerHandleRect1.Y -= 2; outerHandleRect1.Height = 2; outerHandleRect1.Width = 5; outerHandleRect1.X += 1; var outerHandleRect2 = outerHandleRect1; outerHandleRect2.Y -= 1; outerHandleRect2.X += 1; outerHandleRect2.Width = 3; outerHandleRect2.Height = 1; var innerHandleRect = outerHandleRect1; innerHandleRect.X += 1; innerHandleRect.Width = 3; g.FillRectangle(Brushes.Black, outerHandleRect1); g.FillRectangle(Brushes.Black, outerHandleRect2); g.FillRectangle(Brushes.White, innerHandleRect); } }
VB.NET
Imports System.Drawing Imports System.Windows.Forms Public Class shpHandles Public Const Size As Integer = 3 Public Sub New(ByVal shape As shpShape) Me.BorderWidth = 4 Me.SetBounds(shape.Bounds) End Sub Public Property BorderBounds As Rectangle Public Property BorderWidth As Integer Public Property Locked As Boolean Public ReadOnly Property TotalBounds As Rectangle Get Return Rectangle.Union(Me.TopLeft, Me.BottomRight) End Get End Property Friend ReadOnly Property TopLeft As Rectangle Get Return New Rectangle(Me.BorderBounds.X - Size, _ Me.BorderBounds.Y - Size, 2 * Size + 1, 2 * Size + 1) End Get End Property Friend ReadOnly Property TopRight As Rectangle Get Return New Rectangle(Me.BorderBounds.Right - Size, _ Me.BorderBounds.Y - Size, 2 * Size + 1, 2 * Size + 1) End Get End Property Friend ReadOnly Property TopMiddle As Rectangle Get Return New Rectangle(Me.BorderBounds.X + _ Me.BorderBounds.Width / 2 - Size, Me.BorderBounds.Y - _ Size, 2 * Size + 1, 2 * Size + 1) End Get End Property Friend ReadOnly Property MiddleLeft As Rectangle Get Return New Rectangle(Me.BorderBounds.X - Size, _ Me.BorderBounds.Y + Me.BorderBounds.Height / 2 - Size,_ 2 * Size + 1, 2 * Size + 1) End Get End Property Friend ReadOnly Property MiddleRight As Rectangle Get Return New Rectangle(Me.BorderBounds.Right - Size, _ Me.BorderBounds.Y + Me.BorderBounds.Height / 2 - Size,_ 2 * Size + 1, 2 * Size + 1) End Get End Property Friend ReadOnly Property MiddleMiddle As Rectangle Get Return New Rectangle(Me.BorderBounds.X + _ Me.BorderBounds.Width / 2 - Size, Me.BorderBounds.Y + _ Me.BorderBounds.Height / 2 - Size, 2 * Size + 1, _ 2 * Size + 1) End Get End Property Friend ReadOnly Property BottomLeft As Rectangle Get Return New Rectangle(Me.BorderBounds.X - Size, _ Me.BorderBounds.Bottom - Size, 2 * Size + 1, _ 2 * Size + 1) End Get End Property Friend ReadOnly Property BottomRight As Rectangle Get Return New Rectangle(Me.BorderBounds.Right - Size, _ Me.BorderBounds.Bottom - Size, 2 * Size + 1, _ 2 * Size + 1) End Get End Property Friend ReadOnly Property BottomMiddle As Rectangle Get Return New Rectangle(Me.BorderBounds.X + _ Me.BorderBounds.Width / 2 - Size, _ Me.BorderBounds.Bottom - Size, 2 * Size + 1, _ 2 * Size + 1) End Get End Propert Friend Sub SetBounds(ByVal shape As Rectangle) Me.BorderBounds = New Rectangle(shape.X - Me.BorderWidth, _ shape.Y - Me.BorderWidth, shape.Width + 2 *_ Me.BorderWidth, shape.Height + 2 * Me.BorderWidth) End Sub Friend Sub Draw(ByVal g As Graphics, _ ByVal firstSelection As Boolean) ControlPaint.DrawBorder(g, Me.BorderBounds, _ ControlPaint.ContrastControlDark, _ ButtonBorderStyle.Dotted) If Me.Locked Then Me.DrawLock(g) Else Me.DrawGrabHandle(g, Me.TopLeft, firstSelection) Me.DrawGrabHandle(g, Me.TopMiddle, firstSelection) Me.DrawGrabHandle(g, Me.TopRight, firstSelection) Me.DrawGrabHandle(g, Me.MiddleLeft, firstSelection) Me.DrawGrabHandle(g, Me.MiddleRight, firstSelection) Me.DrawGrabHandle(g, Me.BottomLeft, firstSelection) Me.DrawGrabHandle(g, Me.BottomMiddle, firstSelection) Me.DrawGrabHandle(g, Me.BottomRight, firstSelection) End If End Sub Private Sub DrawGrabHandle(ByVal g As Graphics, ByVal rect _ As Rectangle, ByVal firstSel As Boolean) If firstSel Then Dim rect1 = rect Dim rect2 = rect Dim innerRect = rect innerRect.Inflate(-1, -1) rect1.X += 1 rect1.Width -= 2 rect2.Y += 1 rect2.Height -= 2 g.FillRectangle(Brushes.Black, rect1) g.FillRectangle(Brushes.Black, rect2) g.FillRectangle(Brushes.White, innerRect) Else g.FillRectangle(Brushes.Black, rect) End If End Sub Private Sub DrawLock(ByVal g As Graphics) Dim rect = Me.TopLeft rect.X -= 1 rect.Width -= 1 rect.Height -= 2 Dim innerRect = rect innerRect.Inflate(-1, -1) g.FillRectangle(Brushes.White, innerRect) g.DrawRectangle(Pens.Black, rect) Dim outerHandleRect1 = rect outerHandleRect1.Y -= 2 outerHandleRect1.Height = 2 outerHandleRect1.Width = 5 outerHandleRect1.X += 1 Dim outerHandleRect2 = outerHandleRect1 outerHandleRect2.Y -= 1 outerHandleRect2.X += 1 outerHandleRect2.Width = 3 outerHandleRect2.Height = 1 Dim innerHandleRect = outerHandleRect1 innerHandleRect.X += 1 innerHandleRect.Width = 3 g.FillRectangle(Brushes.Black, outerHandleRect1) g.FillRectangle(Brushes.Black, outerHandleRect2) g.FillRectangle(Brushes.White, innerHandleRect) End Sub End Class
Add the final two classes. One class is for the Snap Lines, and the other is for our Extension methods.
C#
internal static class Ext { public static Point Add(this Point Point1, Point Point2) { return new Point(Point1.X + Point2.X, Point1.Y + Point2.Y); } public static Point Subtract(this Point Point1, Point Point2) { return Point1.Add(Point2.Negate()); } public static Point Negate(this Point Point1) { return new Point(-Point1.X, -Point1.Y); } public static Point Floor(this Point Point1, int Grid) { var X = Point1.X / Grid; var Y = Point1.Y / Grid; return new Point(X * Grid, Y * Grid); } public static bool Between(this int Location1, int Location2, int Location3) { if (Location2 > Location3) return (Location3 <= Location1 && Location2 > Location1); if (Location2 < Location3) return (Location2 <= Location1 && Location3 > Location1); return false; } } public class lnSnap { public lnSnap(int X1, int Y1, int X2, int Y2, Color LineColor) { this.X1 = X1; this.Y1 = Y1; this.X2 = X2; this.Y2 = Y2; this.Color = LineColor; } public int X1 { get; set; } public int Y1 { get; set; } public int X2 { get; set; } public int Y2 { get; set; } public Color Color { get; set; } public void Draw(Graphics g) { using (var p = new Pen(this.Color)) { g.DrawLine(p, this.X1, this.Y1, this.X2, this.Y2); } } }
VB.NET
Imports System.Drawing Imports System.Runtime.CompilerServices Friend Module Ext <Extension()> Function Add(ByVal Point1 As Point, ByVal Point2 As Point) _ As Point Return New Point(Point1.X + Point2.X, Point1.Y + Point2.Y) End Function <Extension()> Function Subtract(ByVal Point1 As Point, ByVal Point2 As Point) _ As Point Return Point1.Add(Point2.Negate()) End Function <Extension()> Function Negate(ByVal Point1 As Point) As Point Return New Point(-Point1.X, -Point1.Y) End Function <Extension()> Function Floor(ByVal Point1 As Point, ByVal Grid As Integer) _ As Point Dim X = Point1.X / Grid Dim Y = Point1.Y / Grid Return New Point(X * Grid, Y * Grid) End Function <Extension()> Function Between(ByVal Location1 As Integer, ByVal Location2 _ As Integer, ByVal Location3 As Integer) As Boolean If Location2 > Location3 Then Return (Location3 <= _ Location1 AndAlso Location2 > Location1) If Location2 < Location3 Then Return (Location2 <= _ Location1 AndAlso Location3 > Location1) Return False End Function End Module Imports System.Drawing Public Class lnSnap Public Sub New(ByVal X1 As Integer, ByVal Y1 As Integer, _ ByVal X2 As Integer, ByVal Y2 As Integer, _ ByVal LineColor As Color) Me.X1 = X1 Me.Y1 = Y1 Me.X2 = X2 Me.Y2 = Y2 Me.Color = LineColor End Sub Public Property X1 As Integer Public Property Y1 As Integer Public Property X2 As Integer Public Property Y2 As Integer Public Property Color As Color Public Sub Draw(ByVal g As Graphics) Using p = New Pen(Me.Color) g.DrawLine(p, Me.X1, Me.Y1, Me.X2, Me.Y2) End Using End Sub End Class
All that is needed now is to create the various shapes; then, everything can gel together. I am stopping here today because the creation of the shapes and the canvas is a topic on its own.
Conclusion
For now, the stage is basically set. All we need is a platform to draw on, and the various shapes. This is what we will do next time. Until then, happy coding!