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!