Microsoft's Visual Basic programming environment introduced a new style of software development. Visual Basic Custom Controls, or VBXs, provided the basis for component-based programming.
While VBXs were not exactly revolutionary (they represented just another form of a dynamic link library), they fulfilled the promise of reusability to a degree not previously thought possible. Novice programmers who may not even have heard the acronym DLL manipulated VBXs with ease and created near-professional quality Visual Basic applications in days.
Although VBX technology was developed as a technology specific to Visual Basic, its unprecedented success prompted Microsoft to add VBX support to the Visual C++ development system. Interfacing C programs and a VBX library is not as hard as it sounds; after all, the VBX is also written in the C language. The difficulty lies in the fact that the data types used were tailored towards the Basic language, not C.
Unfortunately, the good days of VBX support in C programs were too good to last. Shortly after VBX support was introduced, ongoing development of the 16-bit version of Visual C++ ceased. The 32-bit version, which represented the development environment of choice for Windows NT and now Windows 95, was fundamentally incompatible with 16-bit VBX libraries.
Microsoft decided to address this issue and design a new custom control mechanism that also promised to resolve other VBX limitations. No longer a technology specific to a single programming environment, the new OLE Custom Controls use OLE technology for communicating with a control container application and promise the same ease of use as VBX technology.
In the rest of this chapter, we examine OLE controls from the perspective of the control user; that is, the application programmer who takes an OLE control as some kind of a black box and incorporates it as a component into his or her application. This perspective markedly differs from that of the control developer, who creates the OLE control in the form of an OCX library; and that of the end user, who is the recipient of the application created by the control user.
For the purposes of demonstration, I developed a very simple OLE control with the uninspired name OCTL. OCTL is a button that can have a rectangular, elliptical, or triangular shape; when the button is clicked with the mouse, it changes color from green to red or back to indicate its selected or deselected state (Figure 23.1).
What exactly is an OLE custom control? It can be viewed as an object that presents itself to the application programmer in the form of a series of properties, methods, and events. Control properties can be set or retrieved and are represented by values of various types, such as integers or character strings. Control methods are procedures that you can call in order for the control to perform a function. Events are messages that the control sends to its parent window indicating an occurrence of some kind, such as a mouse click.
Although many controls present a visual interface, a control does not necessarily have to be visible in order to be functional. It is possible to create controls that offer properties, respond to methods, and send event notifications without ever presenting a visual interface to the end user.
Follow these steps to add custom control support to an application:
Ensure that your application supports custom controls by calling AfxEnableControlContainer.
Add the custom control to a dialog template.
Set the control's initial properties.
Add member variables to represent the control or its properties as appropriate.
Add message handlers to handle messages from the control as appropriate.
To demonstrate custom control usage, I built an application named ODLG, which uses a dialog with my OCTL control in it. ODLG is an AppWizard-generated, dialog-based application; the only nondefault option when creating this application with AppWizard was OLE custom control support.
Although I describe OLE custom control use in the context of dialogs, custom control use is no more restricted to dialog boxes than is the use of standard Windows controls.
In order to be capable of using OLE custom controls, an application must be a control container. This is accomplished by calling AfxEnableControlContainer during application initialization. Applications created by AppWizard and marked to support OLE custom controls have this call placed into the InitInstance member function of their application objects. If you created an application with AppWizard and did not include OLE custom control support, you can add this call manually any time.
Custom controls can be added to a dialog template just like other controls, using the dialog editor in Developer Studio. However, custom controls, at least initially, may not appear in the palette of controls. Therefore, it is necessary to use the right mouse button to invoke a popup menu with the option of inserting a custom control (Figure 23.2).
When you select the Insert OLE Control option, the Developer Studio responds with a dialog listing all OLE controls that are installed on your system (Figure 23.3). Select a control and click on the OK button to have it placed in your dialog at a default location in the upper-left corner.
Once the control has been placed in the dialog, you can move it around and resize it just as you would move and resize any other control.
In the ODLG application, I added a button in addition to the OCTL control. This button will be used by the end user to retrieve and display the control's Shape property. This button is identified as IDC_BUTTON; the OCTL control itself is identified as IDC_OCTLCTRL.
Although it may not be obvious at first sight, as soon as you insert a control in your dialog template, the code comprising the control becomes active. The Developer Studio actually loads the library code in the OCX file and activates the control in design mode. The behavior of many controls varies according to whether the control has been activated in this mode or in user mode when an application using the control is running.
The significance of this is that most OLE custom controls offer a property page interface where the control's properties can be set. Many control properties are persistent; settings configured at design time define the run-time appearance and behavior of the control.
The OCTL control has exactly two properties. IsSelected, a Boolean property, tells whether the control is in a selected or deselected state. This property is read-only (and so obviously cannot be set at design time). The other property, Shape, determines whether the control acquires an elliptical, rectangular, or triangular shape, and is meant to be used as a design-time property. (In fact, attempting to set this property at runtime results in an error.)
A control's properties can be set by right-clicking on the control in the dialog editor to invoke the control's property sheet interface (Figure 23.4).
Of the three or more property pages that form part of this property sheet, the first and the last are supplied by Developer Studio; the ones in the middle are supplied by the control itself. For example, the OCTL control supplies a single property page where the value of its Shape property can be set (Figure 23.5).
The procedure for adding member variables for an OLE custom control is like the one for adding member variables for other types of controls. Select the control in the dialog editor and invoke the ClassWizard, select the Member Variables tab in ClassWizard, and double-click on the line containing the control's identifier to add a member variable.
However, at this point, something strange takes place. The ClassWizard responds with the dialog shown in Figure 23.7. In order to create variables corresponding to the OLE control, it is necessary to first create a CWnd-derived class representing the control. This is done automatically by ClassWizard when you first attempt to add a member variable for the control.
After the new classes, you can proceed adding a member variable (m_cOCTL) for the OLE custom control. This procedure occurs only once; subsequently, you can add member variables for the control normally.
Now we take a brief look at the code generated by ClassWizard. When I added a member variable for the OCTL control to my ODLG application, ClassWizard created the COCTL class; the header file was octl.h, the implementation file octl.cpp.
The declaration of class COCTL, shown in Listing 23.1, includes all member functions necessary to create the control, adjust its properties, and invoke its methods.
There is nothing surprising in the implementation file (Listing 23.2) either. However, consider yourself strongly advised to heed the warnings at the top of these files. These pieces of code are machine-generated for the sole purpose of providing an interface to a control written by somebody else. It is not your job as the application programmer to attempt to modify the control's behavior (nor are you able to do that without having access to the control's source code).
Back to my ODLG sample application. I added a single member variable of type COCTL, which I named m_cOCTL. To utilize this variable, I added a message handler function for the Describe button. In this function, shown in Listing 23.3, I retrieve the control's Shape property by calling the COCTL::GetShape member function and display the result in a message box.
Listing 23.3. Accessing a control's properties.
// TODO: Add your control notification handler code here
case 0: shape = "Ellipse"; break;
case 1: shape = "Rectangle"; break;
case 2: shape = "Triangle"; break;
AfxMessageBox("The control's shape is " + shape + ".");
Handling messages from an OLE custom control is perhaps even simpler than handling properties. You can add handlers for any event the control supports via ClassWizard. For example, Figure 23.9 demonstrates adding a message handler for the single event that the OCTL control can generate, a Select event.
Depending on the type of the event, the event handler may receive parameters. In the case of OCTL, a handler for the Select event received a Boolean parameter specifying whether the control has just been selected or deselected. The sample handler function in ODLG simply displays a message box to this effect, as shown in Listing 23.4.
Listing 23.4. Handling an OLE custom control event.
void CODLGDlg::OnSelectOctlctrl(BOOL IsSelected)
// TODO: Add your control notification handler code here
AfxMessageBox("The control has been selected.");
AfxMessageBox("The control has been deselected.");
That's all there is to it! All that remains is recompiling and running your application.
As you can see, the procedure for inserting an OLE custom control is no more complex than the procedure for inserting other types of controls. Moreover, in order to efficiently use an OLE custom control, you need not know the details of its implementation (or even how OLE custom controls are implemented in the first place). All you need is documentation on the control's purpose and behavior, its properties, methods, and events.
I believe Microsoft really delivered on its promise of providing a 32-bit custom control technology that is superior to VBX technology. All we need now is a variety of wonderful third-party custom controls.
Microsoft supplies several OLE custom controls with Visual C++. These controls are similar in function and appearance to VBX controls found in earlier versions (for example, Version 3.0) of Visual Basic.
Among these controls is the Animated Button Control; this flexible control can be used to implement multistate or animated buttons.
The Grid Control presents a two-dimensional array of cells organized into rows and columns in a spreadsheet-like interface.
The Key State Control can be used to display the state of the Num Lock, Caps Lock, or Scroll Lock keys or the Insert/Overwrite status.
The Microsoft Comm control is an example of a control that is not visible at runtime. This control provides a communication port interface.
The Microsoft Masked Edit Control provides an edit control with customized, formatted editing capabilities.
The Microsoft Multimedia Control provides a programmatic multimedia interface.
The PicClip control is yet another control that is not visible at runtime; this control provides an efficient mechanism for organizing a large number of small icons or bitmaps.
Some of these controls (the Animated Button Control, the Grid Control, the Key State Control, and the Microsoft Multimedia Control) are demonstrated in a dialog box shown in Figure 23.10.
OLE custom controls represent a technology that is a 32-bit successor to the Visual Basic custom control (VBX) technology.
In order for an application to act as an OLE control container, it is necessary to call the AfxEnableControlContainer function when the application is being initialized.
OLE custom controls can be used in applications just like ordinary controls. They can be inserted into a dialog template using the Developer Studio dialog editor. The control can be configured through a set of property page interfaces that appear in the control's property sheet in the dialog editor.
The control's properties can be accessed from within the application by using the ClassWizard to add member variables that correspond to the control. When adding a member variable for a certain type of control for the first time, ClassWizard creates a wrapper class for the control and adds it to the project.
The ClassWizard can also be used to add handler functions for control events.
Microsoft supplies several custom controls as part of the Visual C++ development environment.