Create a Shaped 3D Control with GDI+, Page 4
Adding the Control to the Toolbar
After you have compiled the class library containing the control, add it to the Toolbar in VS.NET. You can optionally add a new toolbar tab by selecting the Toolbox (View|Toolbox) and selecting Add Tab from the context menu (right-click over the Toolbox to display the context menu). I added a tab named Controls. To add the custom control, select the new (or an existing) tab, again displaying the toolbox context menu. This time, select Add/Remove Items from the context menu, displaying the Customize Toolbox dialog. On the .NET Framework Components tab, click Browse and navigate to the class library assembly containing the 3D Cube control.
Figure 4 shows the design-time view of the cube with the background color changed to blue.

Click here for a larger image.
Figure 4: The control shown in the toolbox and painted on a form at design time
Testing the Control
The final step is to drag and drop the control from the toolbox onto a form or user control. To test that the bounding region is limited to the cube shape, add a Click event handler. The event should fire only when you click within the boundaries of the control, as opposed to the rectangular boundary shown when the control is selected at design time.
Before you send me some e-mail about errors in the code, albeit minor ones, I'm warning you now: The control code contains errors. The cube bound and control boundary should be synonymous. This implies that setting the bounds of the cube and the control are identical. Additionally, adjusting the center of the cube should reposition the control. Currently, changing properties such as the CubeCenter makes the cube appear offset from the control, and the control does not resize or snap-to-fit automatically at design time. Another flaw is that the depth value is zero by default, so the cube looks like a rectangle when it is first dropped on a form. If you play with the RotateX and RotateY properties of the underlying cube, you will also notice that the cube does not render correctly in some configurations. These tweaks were intentionally left in place for you to play with if you'd like.
Hint: A minimalist approach to improving design flaws would lead one to remove all of the cube properties and accomplish everything through the outer control, specifically the resize event. A maximalist approach would include adding a Z or depth property, surfacing rotation properties, and incorporating the Z or Depth property into a three-dimensional size property. The latter would require a custom property editor for a three-dimensional size and the ScaleCube method would need to be modified to encompass the rotation factors.
The two remaining sections contain the complete code listing for the 3D Cube control and the 3D Cube structure, respectively. While I encourage you to review the previous VB Today column, I know you're busy so I included the complete listing here.
Control Code Listing
Listing 7 is the complete listing for the 3D Cube control.
Listing 7: The complete Listing for the 3D Cube Control
Imports System
Imports System.Collections
Imports System.ComponentModel
Imports System.Drawing
Imports System.Drawing.Drawing2D
Imports System.Data
Imports System.Windows.Forms
<ToolboxBitmap("Cube3d.bmp")> _
Public Class Cube3d
Inherits System.Windows.Forms.Control
#Region " Component Designer generated code "
Public Sub New(ByVal Container _
As System.ComponentModel.IContainer)
MyClass.New()
'Required for Windows.Forms Class Composition Designer
'support
Container.Add(Me)
End Sub
Public Sub New()
MyBase.New()
SetStyle(ControlStyles.SupportsTransparentBackColor Or _
ControlStyles.ResizeRedraw, True)
InitializeComponent()
FCube = New Cube(CubeX, CubeY, CubeHeight, CubeWidth,
CubeDepth)
End Sub
'Component overrides dispose to clean up the component list.
Protected Overloads Overrides Sub Dispose( _
ByVal disposing As Boolean)
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub
'Required by the Component Designer
Private components As System.ComponentModel.IContainer
'NOTE: The following procedure is required by the
'Component Designer
'It can be modified using the Component Designer.
'Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> _
Private Sub InitializeComponent()
components = New System.ComponentModel.Container
End Sub
#End Region
Private FPen As Pen = New Pen(Color.Black)
Private FCube As Cube
Protected Overrides Sub OnPaint( _
ByVal e As System.Windows.Forms.PaintEventArgs)
MyBase.OnPaint(e)
Dim path As GraphicsPath = FCube.GetCube()
e.Graphics.DrawPath(FPen, path)
Me.Region = EnclosingRegion()
End Sub
Public Function EnclosingRegion() As Region
Dim copy As Cube3D = Clone()
copy.ScaleCube(1, 1, 1)
Dim path As GraphicsPath = copy.Cube.GetCube()
path.FillMode = FillMode.Winding
Return New Region(path)
End Function
Public Function Clone() As Cube3d
Dim copy As Cube3D = New Cube3d
copy.SetCubeBounds(CubeX, CubeY, CubeWidth, CubeHeight,
CubeDepth)
Return copy
End Function
Public Sub ScaleCube(ByVal x As Integer, ByVal y As Integer,
ByVal z As Integer)
CubeLocation = New Point(CubeX - x, CubeY - y)
CubeWidth += 2 * x
CubeHeight += 2 * y
CubeDepth += 2 * z
End Sub
Protected Overrides Sub OnResize(ByVal e As System.EventArgs)
MyBase.OnResize(e)
ResizeCubeStructure()
End Sub
Public Sub SetCubeBounds(ByVal x As Integer, _
ByVal y As Integer, _
ByVal width As Integer, ByVal height As Integer, _
ByVal depth As Integer)
FCube.Location = New Point(x, y)
FCube.Width = width
FCube.Height = height
FCube.Depth = depth
Invalidate()
End Sub
Private Sub ResizeCubeStructure()
FCube.FillRectangle(Bounds)
Invalidate()
End Sub
Public Property CubeWidth() As Integer
Get
Return FCube.Width
End Get
Set(ByVal Value As Integer)
FCube.Width = Value
Invalidate()
End Set
End Property
Public Property CubeHeight() As Integer
Get
Return FCube.Height
End Get
Set(ByVal Value As Integer)
FCube.Height = Value
Invalidate()
End Set
End Property
Public Property CubeDepth() As Integer
Get
Return FCube.Depth
End Get
Set(ByVal Value As Integer)
FCube.Depth = Value
Invalidate()
End Set
End Property
Public Property CubeCenter() As Point
Get
Return FCube.Center
End Get
Set(ByVal Value As Point)
FCube.Center = Value
Invalidate()
End Set
End Property
Public Property CubeLocation() As Point
Get
Return FCube.Location
End Get
Set(ByVal Value As Point)
FCube.Location = Value
Invalidate()
End Set
End Property
<Browsable(False)> _
Public Property CubeSize() As CubeSize
Get
Return FCube.Size
End Get
Set(ByVal Value As CubeSize)
FCube.Size = Value
Invalidate()
End Set
End Property
<Browsable(False)> _
Public ReadOnly Property CubeX() As Integer
Get
Return FCube.X
End Get
End Property
<Browsable(False)> _
Public ReadOnly Property CubeY() As Integer
Get
Return FCube.Y
End Get
End Property
<Browsable(False)> _
Public Property CubePen() As Pen
Get
Return FPen
End Get
Set(ByVal Value As Pen)
FPen = Value
End Set
End Property
<Browsable(False)> _
Public ReadOnly Property Cube() As Cube
Get
Return FCube
End Get
End Property
End Class
