The Four-Not Three-Models for Control Creation Using VB5

Daniel Appleman

If you’re thinking about creating your own controls-and who isn’t these days-this month’s column will help to explain the strengths, weaknesses, and tradeoffs associated with each of the four options. This article is adapted from material in Dan’s new book, Developing ActiveX Components with Visual Basic 5.0: A Guide to the Perplexed (Ziff-Davis Press, ISBN 1-56276-510-8).

VISUAL Basic supports four models for creating ActiveX controls. Yes, I know that Microsoft mentions only three, but the fourth is really quite important. It’s also quite complex and requires a somewhat advanced level of knowledge, so I can’t really fault them for not discussing it. Choosing a model is one of the most important decisions you’ll make when designing a control. In this section I’ll review these approaches and the advantages and disadvantages of each one.

Enhance an existing control

Let’s assume you’ve been using Command buttons in your applications for years, but one day you realize that, well, something has been missing, that there’s just one feature that you really wish command buttons had. The one thing you want is a click event that includes parameters telling you where in the button the mouse has been clicked.

As a veteran VB programmer you know that you can accomplish this using the MouseUp event, but you also know that anyone for whom a modified click event is the ultimate wish is probably not thinking too clearly anyway. But that’s irrelevant-the point is that almost every VB programmer has probably looked at a control and thought: “If only it had this one additional feature . . .”

The ActiveX control designer lets you drop other controls (constituent controls) onto your ActiveX control. It also lets you create public properties and events that can access the properties and events of the constituent control. Try this: Create a blank control and add a command button to the control. Then add the following code:

Event Click(ByVal X As Single, ByVal Y As Single)
Private mousex As Single, mousey As Single
Private Sub Command1_MouseDown(Button As _
  Integer, Shift As Integer, X As Single, Y As Single)
   mousex = X: mousey = Y
   RaiseEvent MouseDown(Button, Shift, X, Y)
End Sub
Private Sub Command1_MouseMove(Button As _
  Integer, Shift As Integer, X As Single, Y As Single)
   mousex = X: mousey = Y
   RaiseEvent MouseMove(Button, Shift, X, Y)
End Sub
Private Sub Command1_Click()
   RaiseEvent Click(mousex, mousey)
End Sub

During the Command1_MouseMove and Command1_MouseDown events, the mouse location is stored in two private variables that are used to provide the parameters for the Click event. It’s also important to make sure that the command button fills the area of the control. This is accomplished by detecting the UserControl’s Resize event and resizing the command button to fill the control area:

Private Sub UserControl_Resize()
   ' Let control fill the window
   Command1.Move 0, 0, Width, Height
End Sub

Keep a number of important facts in mind in this example. First, you shouldn’t redefine standard events such as the click event. Doing so is likely to confuse those using your controls. Instead, create a new event name. The same thing applies to standard properties.

Also remember that when you click on a constituent button control, the click event of the button, not that of the UserControl, is triggered. The UserControl click event is triggered only when you click on the control area itself. In this example, you don’t need to use the UserControl’s click event, because you expand the button to fill the entire control. The UserControl’s click event is never triggered.

Existing control tradeoffs

The biggest advantage of enhancing an existing control is that it’s extremely easy. The ActiveX control wizard can do much of the work of mapping public properties, methods, and events to those of constituent controls or the UserControl object. There are, however, a number of disadvantages to this approach:

Because each constituent control defines its own behavior and drawing characteristics, there are limits to what you can do to modify the control. There are occasions where you can use advanced subclassing techniques to perform some modifications to the behavior of the constituent controls, but this is a rather advanced technique that requires a good understanding of the Win32 API and, depending on the control, some trial and error as you attempt to figure out how the control responds to different Windows messages.

Constituent controls are always in run mode when your control is active, making it impossible to set the control’s design-time properties even though the control’s container is in design mode.

As soon as you go beyond the built-in controls or redistributable controls provided by Microsoft, you run into licensing and copyright issues. In order for your control to work in VB’s design environment, each constituent control must be properly licensed.

Build a control from constituent controls

This model is described by Microsoft as one of the three possible control models. In fact, it’s just a superset of the Enhanced control model. The major differences are as follows: Instead of mapping your public properties and events to a single control, you map them to any or all of the constituent controls. It’s also possible to map the same property to more than one control.

You’re also much more likely to access properties of the UserControl object. Consider a control that contains two button controls. You can map multiple events to one event. The click event from both constituent controls can be mapped to a single parameterized click event as follows:

Event Click(ByVal ButtonNum%)
Private Sub Command1_Click()
   RaiseEvent Click(1)
End Sub
Private Sub Command2_Click()
   RaiseEvent Click(2)
End Sub
Private Sub UserControl_Click()
   RaiseEvent Click(0)
End Sub

Try running the project and clicking on both command buttons and the space between them. A message describing which object has been clicked is displayed in the immediate window. You can also map a single class property to multiple controls as shown here with the Font property:

Public Property Get Font() As Font
   Set Font = UserControl.Font
End Property
Public Property Set Font(ByVal New_Font As Font)
   Set Command1.Font = New_Font
   Set UserControl.Font = New_Font
   Set Command2.Font = New_Font
   PropertyChanged "Font"
End Property

In this particular case, mapping the Font property to the UserControl’s Font property is overkill, because this example doesn’t currently draw text directly onto the UserControl. Why is the one from the UserControl object returned in the Property Get statement? In truth, it doesn’t matter. All of the objects reference the same Font object, so you can retrieve a reference from any of them and it will work.

This model of control creation suffers from exactly the same advantages and disadvantages as the Enhanced control approach. It also has the characteristic that the UserControl itself can’t receive the focus-only the constituent controls receive focus.

User-drawn controls

User-drawn controls represent one of the most exciting approaches for control creation, though they also represent a jump in complexity. With this type of control, you work primarily with properties and events of the UserControl object. You can include invisible constituent controls if you wish, but as soon as you add one, the behavior of the control changes in that the UserControl object can no longer receive the focus.

Listing 1 demonstrates a simple user-drawn control. (You’ll usually use the UserControl_Paint event to draw to the control.) The control appears as a green rectangle when created, then switches to blue and red as it receives and loses the input focus. When you run the project, you’ll see the control on the clkForm test form. Try tabbing to and from the control, and try clicking on the control to confirm that the click event works.

Listing 1. Trivial user-drawn control.

Option Explicit
Event Click()
Private Sub UserControl_Click()
   RaiseEvent Click
End Sub
Private Sub UserControl_GotFocus()
   UserControl.BackColor = vbBlue
End Sub
Private Sub UserControl_Initialize()
   UserControl.BackColor = vbGreen
End Sub
Private Sub UserControl_LostFocus()
   UserControl.BackColor = vbRed
End Sub

User-drawn control tradeoffs

The biggest advantage of user-drawn controls is their flexibility. Because you determine the behavior and appearance of the control at all points in its life, there are few limits on what your control can do.

The biggest disadvantage of user-drawn controls derives from this fact. Not only can you determine the behavior and appearance of the control-but also you must do so. This can rapidly become very complex. There are so many possible techniques for using user-drawn controls that it would be presumptuous to suggest that what you read here is more than a solid introduction.

Keep in mind that when you’re implementing a user-drawn control, you aren’t limited to the functionality inherent in Windows-you have access to all of the Win32 API functions as well-and that the Win32 API includes some extremely powerful (and fast) graphic and text output functions that go far beyond what’s implemented by VB. Refer to my Visual Basic Programmer’s Guide to the Win32 API for an in-depth description of these functions.

Also watch for third-party products that include commercial quality controls with source code. They not only offer controls that you can use in your application, but they’re also a great way to learn advanced control creation techniques.

Create your own window

The Windows operating system defines standard classes of windows such as list boxes, text boxes, outline windows, common dialog boxes, and so on.

Many ActiveX controls and VB intrinsic controls are actually superclasses of these standard windows. For example: The VB list box control actually creates a Windows list box window. It intercepts the underlying messages for the window and maps them into control properties and events. This allows a relatively simple control to take full advantage of controls that are built into the operating system.

When you use constituent controls, you’re in effect building another level onto this structure. The problem with this approach is that your control can suffer from limitations that are introduced at either level of the hierarchy. Not every ActiveX control implements all of the functionality of the underlying Windows class. For example, even the VB ListBox control doesn’t let you set tab locations in a list box-a feature that’s built into the operating system’s implementation.

With VB5-authored ActiveX controls you’re also limited because design-time-only properties can’t be changed at runtime. Why is this? Because with a LISTBOX class window, certain characteristics, such as whether the control can handle multiple selections, must be determined as the window is created. In order to change this characteristic, you have to destroy and re-create the window. VB5-created controls can’t arbitrarily destroy and re-create a constituent control-something that is necessary to change characteristics such as support for multiple selections.

But there is a solution.

Instead of using the VB list box, you can go directly to the windows class itself and create your own LISTBOX class window on top of the UserControl. Because you created the window yourself, you can destroy it and re-create it at will-allowing you to change these “design-time” characteristics any time you wish-even at the container’s runtime if you so choose. The advantage of this approach is that it allows you to take full advantage of window classes that are defined by the operating system.

But there are a number of disadvantages:

This approach requires a good understanding of Windows API techniques and the operation of the windows class.

This approach can involve quite a bit of work-especially for complex controls.

There’s another factor to consider. If you’re creating a control for your own use, you may not need to expose all of the functionality offered by the operating system for a particular window class. A commercial developer will usually expose as many of the features of the underlying control as possible to make the control useful to as many programmers as possible. But when you take this approach for your own applications, you can concentrate on the features that you need and not waste time implementing and testing others. So this model may be more practical than it seems at first glance.

The really bad news about this approach is that it’s beyond the scope of this particular article. So consider this an introduction to some of the possible approaches when you’re creating ActiveX controls with VB-a subject that undoubtedly will provide fodder for many columns to come.

Daniel Appleman, author of the Visual Basic Programmer’s Guide to the Windows API, the Visual Basic Programmer’s Guide to the Win32 API, How Computer Programming Works, and Developing ActiveX Components with Visual Basic 5.0: A Guide to the Perplexed, has been developing applications for Microsoft Windows since its release in 1985. In 1991 he founded Desaware Inc., a Campbell, CaliforniaÐbased software company focusing on component-based software and advanced tools for developers. 70303.2252@compuserve.com or dan@desaware.com.

To find out more about Visual Basic Developer and Pinnacle Publishing, visit their website at: http://www.pinppub.com/vbd/

Note: This is not a Microsoft Corporation website.
Microsoft is not responsible for its content.

This article is reproduced from the April 1997 issue of Visual Basic Developer. Copyright 1997, by Pinnacle Publishing, Inc., unless otherwise noted. All rights are reserved. Visual Basic Developer is an independently produced publication of Pinnacle Publishing, Inc. No part of this article may be used or reproduced in any fashion (except in brief quotations used in critical articles and reviews) without prior consent of Pinnacle Publishing, Inc. To contact Pinnacle Publishing, Inc., please call (800) 788-1900 or (206) 251-1900.