Create a Shaped 3D Control with GDI+
My previous VB Today column demonstrated how to define a custom three-dimensional shape primitive, a 3D cube structure, with .NET and GDI+. This article continues the discussion of GDI+ and demonstrates how you can convert such primitives into shaped controls, as you might desire to create non-rectilinear controls for custom Windows software development. (A couple of interesting general books on this subject are About Face and The Inmates are Running the Asylum, both by Alan Cooper of Visual Basic fame. While these books won't tell you how to create shaped controls in VB.NET, they are well written and enlightening on the subject of GUI design.)
This demonstration builds on the 3D cube structure from the first part of the previous VB Today column and layers a 3D Windows control on top of that structure. Along the way, you will learn about custom control design, surfacing constituent properties, using control attributes, and shaping controls. Both the new code and the original structure code are provided for convenience.
The goal is to convert a shaped structure into a shaped control. This means that the bounding region of the control matches the actual control. In contrast, most Windows controls are two-dimensional and rectangular. The example here is cubic, but the shape could be any polygon. The process is pretty much the same for any shaped control.
Defining the Control
The first step is to create a project that contains or references the assembly or source containing the 3D structure. Next, you need to surface constituent properties that you'd like consumers to be able to modify at design time and then use attributes to conceal public properties that are not suitable for modifying in the VS.NET Properties window.
Implementing Control Fields
Start by implementing control fields that you need and methods you'd like to have. The basic control already contains a significant number of events and fields that every custom control inherits. The ability to respond to click events or set the background color is inherent in every control, so you need to add only elements that are unique to your control. For your purposes, you need a field to capture the underlying cubic structure and an additional pen to indicate the drawing color of the shape's outline.
To begin your custom control and a test application, create a new Windows application solution. You'll use the default form for testing the control. Add a class library project to the solution with File|New|Project and select the Class Library project from the New Project dialog. You can delete the default class1.vb file generated from the template for class libraries. In the Solution Explorer (View|Solution Explorer), select the new class library project. Next, select Project|Add Inherited Control. This step opens the Add New Item dialog (see Figure 1). Change the default selection Inherited User Control (in the list of templates) to Custom Control, and change the Name input field to Cube3d. Click Open.
Figure 1: Add a custom control to the class library that will contain the 3D cube control.
You certainly could use an empty class and code all of the elements by hand, but using project templates is a lot easier. (Refer to my book Advanced Visual Basic .NET Power Coding [Addison-Wesley, 2003] for more on project templates.)
At this point, the source code for the 3D cube control should look like the code in Listing 1.
Listing 1: An empty custom control created from VS.NET's project item templates.
Public Class Cube3d Inherits System.Windows.Forms.Control #Region " Component Designer generated code " Public Sub New() MyBase.New() ' This call is required by the Component Designer. InitializeComponent() 'Add any initialization after the InitializeComponent() call End Sub 'Control 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 Control 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 Protected Overrides Sub OnPaint(ByVal pe As System.Windows._ Forms.PaintEventArgs) MyBase.OnPaint(pe) 'Add your custom paint code here End Sub End Class
For now, you can ignore everything between the #Region directive. (Unless I am referring to a modification to generated code between the #Region directive, this code will be elided in future listings to save space.) However, note that this is a sizeable chunk that you didn't have to write because you used the template.
The next step is to add Import statements for namespaces you will be using, and incorporate the fields discussed at the beginning of this section. Listing 2 shows the modified code with the collapsed #Region code.
Listing 2: Add the imports statement and fields.
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 [ Component Designer generated code ] Private FPen As pen = New pen(Color.Black) Private FCube As Cube Protected Overrides Sub OnPaint(ByVal pe As System.Windows. _ Forms.PaintEventArgs) MyBase.OnPaint(pe) 'Add your custom paint code here End Sub End Class
All of the Imports statements and the two private fields, FPen and FCube, were added (shown in bold). F is a prefix convention that I use. F indicates a field, and the F-prefix is dropped to contrive the property name. VB.NET is strongly typed, so you don't benefit from using type prefix notations like int, str, or the like. In fact, Microsoft no longer encourages the use of prefixes. (Some people like to use m_ to indicate that a field is a member of a class, but membership is implied. All you have to do is distinguish fields from properties, and consistency is most important.)
The Pen will be the color of the cube's outline, and the FCube field refers to the 3D cube defined and referred to previously. (The complete Listing for the Cube structure is provided at the end of this article in the section Primitive 3D Cube Code Reference.) Next, you need to define some behaviors.