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.
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