Create a Shaped 3D Control with GDI+
Implementing Control Methods
Listing 3: Custom control methods.
Imports System Imports System.Collections Imports System.ComponentModel Imports System.Drawing Imports System.Drawing.Drawing2D Imports System.Data Imports System.Windows.Forms Public Class Cube3d Inherits System.Windows.Forms.Control #Region " Component Designer generated code " Public Sub New() MyBase.New() SetStyle(ControlStyles.SupportsTransparentBackColor Or _ ControlStyles.ResizeRedraw, True) InitializeComponent() FCube = New Cube(CubeX, CubeY, CubeHeight, CubeWidth, _ CubeDepth) 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 ' elided intentionally!
The Listing 3 code modifies the constructor (Sub New) and OnPaint, and adds EnclosingRegion, Clone, ScaleCube, OnResize, SetCubeBounds, and ResizeCubeStructure. In the constructor, it adds a call to SetStyle. Passing an Or'd list of enumerated values permits you to customize the control style. For this demonstration, I elected to permit background transparency and enable automatic redrawing when the control is resized. The former permits see-through controls, and the latter ensures that the underlying cube representation adjusts when the control is resized.
The EnclosingRegion method plays a little game. Essentially, you obtain a copy of the cube and expand it three-dimensionally by one pixel, providing enough room for the cube outline. You use a copy because you don't want to change the cube itself; you just want a little more room for the clip region—visible area—of the cube. The Clone method creates a new instance, a copy, of the cube itself.
ScaleCube accepts three points and adjusts the location, width, height, and depth. (Multiplying width and height by 2 accounts for the offsetting change to x and y.) As well as permitting the cube to shrink and swell, you use this method to permit enough room to ensure that the cube control's outline is visible along the edges. (Scaling the cube to make room for the visual image is a bit dodgy, as my friend Tony Cowan would say, and could stand some improvement.)
The OnResize method is overridden to ensure that when the control is resized the underlying cube is resized relatively. Without this method, the actual dimensions of the cube could be greater or less than the size of the control, breaking the illusion of the control conforming to the primitive's dimensions.
Finally, SetCubeBounds accounts for the depth dimension and invalidates the control, so that it is redrawn, and ResizeCubeStructure shrinks or grows the cube to fit the control's bounds and again causes the control to be redrawn.
Surfacing Constituent Properties
The 3D cube control is defined by a location and three Cartesian points representing height, width, and depth. While it makes sense to permit the consumer to modify the location and dimensions, permitting design-time modification of other properties probably makes no sense and may be error prone. Listing 4 contains only the properties that you will permit consumers to modify at design time. Simply add the code in Listing 4 to the Cube3D's class code. (The complete Listing is provided in Control Code Listing.)
Listing 4: The public, design-time modifiable properties of the 3D Cube control.
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
If you want to change all of the cube's dimensions, call SetCubeBounds. Each of the cube's location and dimensional properties individually causes the cube to be redrawn. These properties, modifiable at design time, are CubeWidth, CubeHeight, CubeDepth, CubeCenter, and CubeLocation. CubeLocation moves the upper-left corner of the cube, and CubeCenter relocates the cube relative to its three-dimensional center. Each of these properties is implemented in terms of the underlying 3D cube structure.
Page 2 of 6