Free VC++ Tutorial

Web based School

Previous Page Main Page Next Page


23 — Using OLE Controls

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


Figure 23.1. The OCTL control in action.

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.

Adding OLE Custom Controls to Your Application

Follow these steps to add custom control support to an application:

  1. Ensure that your application supports custom controls by calling AfxEnableControlContainer.

  2. Add the custom control to a dialog template.

  3. Set the control's initial properties.

  4. Add member variables to represent the control or its properties as appropriate.

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

Creating a Control Container

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.

Adding a Custom Control to a Dialog Template

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


Figure 23.2. Inserting an OLE custom control.

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.


Figure 23.3. Installed OLE controls.

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.

Setting Control Properties

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


Figure 23.4. OLE custom control properties.

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


Figure 23.5. OCTL property page: Control.

The last property page (Figure 23.6) in this property sheet offers an interface to all control properties. This includes read-only properties as well, whose values cannot be modified.


Figure 23.6. All OCTL control properties.

Adding Member Variables

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.


Figure 23.7. Creation of class for OLE custom control.

If you click OK, Developer Studio responds with yet another dialog (Figure 23.8) where you can modify any default class and file names that the ClassWizard generated for you.


Figure 23.8. Confirm OLE custom control classes.

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.

    Listing 23.1. COCTL class declaration.
class COCTL : public CWnd

{

protected:

    DECLARE_DYNCREATE(COCTL)

public:

    CLSID const& GetClsid()

    {

        static CLSID const clsid

            = { 0x677600e7, 0xf6c5, 0x11ce,

              { 0x87, 0xc3, 0x0, 0x40, 0x33, 0x21, 0xbf, 0xac } };

        return clsid;

    }

    virtual BOOL Create(LPCTSTR lpszClassName,

        LPCTSTR lpszWindowName, DWORD dwStyle,

        const RECT& rect,

        CWnd* pParentWnd, UINT nID,

        CCreateContext* pContext = NULL)

    { return CreateControl(GetClsid(), lpszWindowName, dwStyle,

        rect, pParentWnd, nID); }

    BOOL Create(LPCTSTR lpszWindowName, DWORD dwStyle,

        const RECT& rect, CWnd* pParentWnd, UINT nID,

        CFile* pPersist = NULL, BOOL bStorage = FALSE,

        BSTR bstrLicKey = NULL)

    { return CreateControl(GetClsid(), lpszWindowName, dwStyle,

        rect, pParentWnd, nID,

        pPersist, bStorage, bstrLicKey); }

// Attributes

public:

    short GetShape();

    void SetShape(short);

    BOOL GetSelected();

    void SetSelected(BOOL);

// Operations

public:

    void AboutBox();

};

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

    Listing 23.2. COCTL class implementation.
///////////////////////////////////////////////////////////////////

// COCTL

IMPLEMENT_DYNCREATE(COCTL, CWnd)

///////////////////////////////////////////////////////////////////

// COCTL properties

short COCTL::GetShape()

{

    short result;

    GetProperty(0x1, VT_I2, (void*)&result);

    return result;

}

void COCTL::SetShape(short propVal)

{

    SetProperty(0x1, VT_I2, propVal);

}

BOOL COCTL::GetSelected()

{

    BOOL result;

    GetProperty(0x2, VT_BOOL, (void*)&result);

    return result;

}

void COCTL::SetSelected(BOOL propVal)

{

    SetProperty(0x2, VT_BOOL, propVal);

}

///////////////////////////////////////////////////////////////////

// COCTL operations

void COCTL::AboutBox()

{

   InvokeHelper(0xfffffdd8, DISPATCH_METHOD, VT_EMPTY, NULL, NULL);

}

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.
void CODLGDlg::OnButton()

{

    // TODO: Add your control notification handler code here

    CString shape;

    switch (m_cOCTL.GetShape())

    {

        case 0: shape = "Ellipse"; break;

        case 1: shape = "Rectangle"; break;

        case 2: shape = "Triangle"; break;

    }

    AfxMessageBox("The control's shape is " + shape + ".");

}

Handling Messages

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.


Figure 23.9. Adding a message map entry for an OLE custom control 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

    if (IsSelected)

        AfxMessageBox("The control has been selected.");

    else

        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.

Custom Controls Supplied with Visual C++

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.


Figure 23.10. Some custom controls supplied with Visual C++.

Summary

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.

Previous Page Main Page Next Page