Lesson 1: Creating ActiveX Controls with MFC

MFC simplifies the process of creating an ActiveX control. Using the MFC ActiveX ControlWizard, you can easily create a fully featured ActiveX control. In this lesson, you will use MFC to create an ActiveX control that implements a dispatch interface, fires events, and provides a property page that can be used to get and set persistent properties.

After this lesson, you will be able to:

  • Describe how to use the MFC ActiveX ControlWizard to create an ActiveX control.
  • Describe how to use ClassWizard to add properties, methods, and events to your control.
  • Describe how to implement a control property page.
  • Describe how to make properties persistent.
  • Describe how to use the ActiveX Control Test Container to test your control.
Estimated lesson time: 50 minutes

Creating an MFC ActiveX Control Project

In this lesson, you will develop the OneArmedBandit ActiveX control, a software version of a Las Vegas-style slot machine. The control provides the single method Play(), which causes a random combination of symbols to appear in the control's three "reels." When the control displays three identical symbols, the Jackpot event is fired. When the user clicks inside the control, the Click event is fired. Figure 11.1 shows the OneArmedBandit control inside the ActiveX Control Test Container.

The OneArmedBandit control provides a property page that allows a user to set the values of the ForeColor and BackColor properties, and to set the custom control-specific NumberOfSymbols property. This property is used to set the number of possible symbols that the control can display, and thus increase or decrease the odds of winning the jackpot. All three of the control properties are persistent.

click to view at full size.

Figure 11.1 The OneArmedBandit ActiveX control

As with all MFC development, the first stage in creating an ActiveX control is to create a development project. In the following exercise, you will create the OneArmedBandit project.

  • To create the OneArmedBandit project
    1. On the File menu, click New, and then click the Projects tab.
    2. Click MFC ActiveX ControlWizard. In the Project name box, type OneArmedBandit and click OK.
    3. Step 1 of the ControlWizard appears. Review the options and click Next to accept the default settings.
    4. In Step 2 of the ControlWizard, clear the Has an "About" box option. Click Finish.
    5. The New Project Information dialog box appears as shown in Figure 11.2.
    6. click to view at full size.

      Figure 11.2 Creating the OneArmedBandit project

      There are a couple of interesting things to note about the information displayed in this dialog box. First, the DLL is created with an .ocx extension. The use of the .ocx extension is not required for ActiveX controls; it is simply a convention left over from the days when ActiveX controls were called OLE controls. OLE controls replaced the older Visual Basic Extension (VBX) controls. Second, the type library source code is in a file named OneArmedBandit.odl. This file contains Object Description Language (ODL) code. ODL was the predecessor to Interface Definition Language (IDL), and it is very similar to IDL in syntax. The Microsoft IDL (MIDL) compiler is capable of compiling ODL code.

    7. Click OK to create the OneArmedBandit project. Expand the ClassView items to view the classes created for your project, as shown in Figure 11.3.

    The ActiveX ControlWizard creates classes to implement the control's DLL server, the control itself, and the control's property page. The COneArmedBanditCtrl control class is derived from the MFC class COleControl. This powerful class inherits all the functionality of the CWnd and CCmdTarget classes and provides a large number of member functions related to the operation of an ActiveX control. Using these functions, you can get and set a control's stock properties, retrieve ambient properties from the container, fire stock events, implement property persistence, and perform a number of operations related to siting and displaying a control.

    click to view at full size.

    Figure 11.3 OneArmedBandit project classes

    Defining the Control Interface

    The ControlWizard also defines two dispatch interfaces for your control: _DOneArmedBandit and _DOneArmedBanditEvents. You add methods and properties to the first of these and events to the second. Although you can make these additions by right-clicking the interface items and choosing the appropriate command from the shortcut menu, the preferred method is to use ClassWizard.

    Adding Properties

    In the following exercises, you will be adding the stock ForeColor and BackColor properties and the custom NumberOfSymbols property to the COneArmedBanditCtrl class.

  • To add properties to an ActiveX control
    1. Press CTRL+W to open ClassWizard. Select the Automation tab.
    2. Click Add Property to display the Add Property dialog box.
    3. Expand the External name drop-down list to display a list of stock properties supported by the COleControl class. From this list, select BackColor.
    4. Note that the Stock radio button in the Implementation group is selected automatically. The dialog box also indicates that the GetBackColor() and SetBackColor() functions will be created. Click OK to implement the BackColor stock property and create the Get and Set functions.
    5. Repeat the process to create the ForeColor stock property.
    6. To create the custom property, open the Add Property dialog box and type the name NumberOfSymbols into the External name list. Note that ClassWizard will create the m_numberOfSymbols member variable to store the property value, and that it will also create a notification function OnNumberOf SymbolsChanged() that is called when the property is changed.
    7. From the Type drop-down list, select short, and then click OK to create the custom property.
    8. Click OK to finish creating the properties.

    In ClassView, you will be able see the new properties listed underneath the _DOneArmedBandit interface item. Notice also that the member variable and the notification function have been added to the COneArmedBanditCtrl class. Double-click the OnNumberOfSymbolsChanged() function to view the source code in the OneArmedBanditCtl.cpp file. The default version of the function simply calls the COleControl::SetModifiedFlag() function.

    Toward the top of the OneArmedBanditCtl.cpp file, you will find this code:

    BEGIN_DISPATCH_MAP(COneArmedBanditCtrl, COleControl)      //{{AFX_DISPATCH_MAP(COneArmedBanditCtrl)      DISP_PROPERTY_NOTIFY(COneArmedBanditCtrl, "NumberOfSymbols",            m_numberOfSymbols, OnNumberOfSymbolsChanged, VT_I2)      DISP_STOCKPROP_BACKCOLOR()      DISP_STOCKPROP_FORECOLOR()      //}}AFX_DISPATCH_MAP END_DISPATCH_MAP()

    This code, along with the corresponding DECLARE_DISPATCH_MAP macro in the header file, implements an MFC dispatch map for the class. The dispatch map is very similar to the message map you learned about in Chapter 3, but instead of mapping Windows messages to class handler functions, the dispatch map maps Automation client requests to your control class's implementation of the properties and methods. For example, when a Visual Basic client requests the NumberOfSymbols property with the following code, the control uses the dispatch map (specifically the DISP_PROPERTY_NOTIFY macro) to retrieve the value contained in the m_numberOfSymbols variable and pass it back to the client:

    Dim myobj As OneArmedBandit Set myobj = New OneArmedBandit MsgBox myobj.NumberOfSymbols

    As you can see from the dispatch map code, stock properties are supported with their own specific macros.

    Property Persistence

    MFC makes it easy to make control properties persistent. The COleControl:: DoPropExchange() function serializes the control properties to and from a storage medium—usually the control's container. The ActiveX ControlWizard overloads the DoPropExchange() function for your control class. The framework passes this function a pointer to a CPropExchange object, which encapsulates the context of the property exchange, including its direction. The over- loaded version of DoPropExchange() calls the base class version to serialize the stock properties implemented by the control. It is your responsibility to add code to serialize any custom properties you want to make persistent.

    MFC provides a number of functions that allow you to serialize properties of different types. The names of these functions begin with the prefix PX_. To serialize your NumberOfSymbols property, replace the //TODO comment in your control class's DoPropExchange() function with a call to the PX_Short() function, as follows:

    void COneArmedBanditCtrl::DoPropExchange(CPropExchange* pPX) {      ExchangeVersion(pPX, MAKELONG(_wVerMinor, _wVerMajor));      COleControl::DoPropExchange(pPX);      PX_Short(pPX, "NumberOfSymbols", m_numberOfSymbols, 3); }

    The fourth argument to the PX_Short() function specifies a default value for the property that is used if the function is unable to retrieve a value from storage medium. The example code above ensures that the NumberOfSymbols property of a new control instance will be initialized with a default value of 3.

    Adding Methods

    As well as providing access to control properties, the dispatch map also maps client method calls to class member function implementations. You use ClassWizard to add a method.

  • To add a method to an ActiveX control
    1. In ClassWizard, click the Automation tab and click Add Method to display the Add Method dialog box.
    2. In the External name list, type Play.
    3. In the Return type drop-down list, click void.
    4. Click OK, and then click OK in ClassWizard to create the method.

    Notice that the Play() function has been added to the COneArmedBanditCtrl class. If you inspect the dispatch map, you will see that the following line has been added:

    DISP_FUNCTION(COneArmedBanditCtrl, "Play", Play, VT_EMPTY, VTS_NONE)

    Adding Events

    ClassWizard also automates the process of defining events fired by the control. In the following exercise, you will implement the Click stock event and the Jackpot custom event.

  • To define an ActiveX control event
    1. In ClassWizard, click the ActiveX Events tab. Click Add Event to display the Add Event dialog box.
    2. From the External name drop-down list, choose the Click stock event. Click OK to add the event.
    3. Click Add Event once again. In the Add Event dialog box, type Jackpot into the External name list. Click OK.
    4. In ClassWizard, click OK to finish creating events.

    In ClassView, notice that the FireJackpot() function has been added to the COneArmedBanditCtrl class. You can use this function from within your code to fire the Jackpot event. ClassWizard has also added entries to the class's event map. The event map is a structure very similar to the dispatch map that is used to implement ActiveX control events. The following is the event map implementation for the COneArmedBanditCtrl class:

    BEGIN_EVENT_MAP(COneArmedBanditCtrl, COleControl)      //{{AFX_EVENT_MAP(COneArmedBanditCtrl)      EVENT_CUSTOM("Jackpot", FireJackpot, VTS_NONE)      EVENT_STOCK_CLICK()      //}}AFX_EVENT_MAP END_EVENT_MAP()

    Creating Property Pages

    Now that you have implemented your ActiveX control's interface, you can add property pages to allow users of the control to get and set its properties. The framework uses the PROPPAGEID series of macros to specify an array of property page IDs for your control's property sheet. The default code generated by the ActiveX ControlWizard for the OneArmedBandit project can be found in the OneArmedBandit.cpp file, and is as follows:

    BEGIN_PROPPAGEIDS(COneArmedBanditCtrl, 1)      PROPPAGEID(COneArmedBanditPropPage::guid) END_PROPPAGEIDS(COneArmedBanditCtrl)

    Note that this structure, unlike the dispatch and event maps, is not maintained by ClassWizard. You have to maintain the code yourself.

    Custom Property Pages

    In the code that is shown above, the single PROPPAGEID entry in the table refers to the default property page that is created for your control. The ActiveX ControlWizard creates a dialog template resource and a dialog class (based on the COlePropertyPage class) that you can edit and compile to create a property page that allows access to your control's custom properties.

    In the following exercises, you will create a property page that allows a user to set the NumberOfSymbols custom property.

  • To implement the custom property page
    1. In ResourceView, expand the Dialog folder. Double-click IDD_PROPPAGE_ONEARMEDBANDIT to edit the dialog template resource.
    2. Delete the //TODO static text. Add a static text control and an edit control so that the dialog template appears as shown in Figure 11.4.
    3. Figure 11.4 Implementing the custom property page

      Give the edit control the ID IDC_NUMSYMBOLS. On the Styles tab of the Edit Properties dialog box, select the Number check box.

    4. Press CTRL+W to open ClassWizard. Select the Member Variables tab.
    5. Click Add Variable to add a Value category member variable named m_numsymbols. The variable type should be short. In the Optional property name box, type NumberOfSymbols and then click OK.
    6. Specify the range validation by entering a minimum value of 3 and a maximum value of 7. Click OK.

    Look at the DoDataExchange() function of the COneArmedBanditPropPage class. Notice that in addition to the DDX and DDV functions, ClassWizard has added the following line:

    DDP_Text(pDX, IDC_NUMSYMBOLS, m_numsymbols, _T("NumberOfSymbols"));

    This is one of a number of functions with the DDP_ prefix that is provided by MFC to transfer data between an ActiveX Control property page and the control properties. Note that the dispatch name of the control property is used, so you do not have to provide an additional code link between the COneArmedBanditPropPage class and the COneArmedBanditCtrl class.

    Stock Property Pages

    Because you have added the ForeColor and BackColor properties to your control, you will need to implement a property page to allow the user to set them. MFC provides three stock property pages for use with ActiveX controls: CLSID_ CColorPropPage, CLSID_CFontPropPage, and CLSID_CPicturePropPage. These pages display a user interface for stock color, font, and picture properties, respectively. To add a stock property page, add another PROPPAGEID macro to the code in the OneArmedBandit.cpp file, as shown here:

    BEGIN_PROPPAGEIDS(COneArmedBanditCtrl, 2)      PROPPAGEID(COneArmedBanditPropPage::guid)      PROPPAGEID(CLSID_CColorPropPage) END_PROPPAGEIDS(COneArmedBanditCtrl)

    Note that the second argument to the BEGIN_PROPPAGEIDS macro must be altered to match the number of property pages specified for the ActiveX control.

    OnDraw() Function

    Controls created with the MFC ActiveX ControlWizard are user-interface controls. This means that much of the control-specific implementation that you provide is drawing code. As with an MFC document/view application, all the drawing code for a control is placed within a single function named OnDraw(). The following is the default implementation generated by the ControlWizard:

    void COneArmedBanditCtrl::OnDraw(      CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid) {      // TODO: Replace the following code with your own drawing code.      pdc->FillRect(rcBounds,      CBrush::FromHandle((HBRUSH)GetStockObject(WHITE_BRUSH)));      pdc->Ellipse(rcBounds); }

    The COleControl::OnDraw() function receives a pointer to an MFC device context object, and a rectangle that represents the control's on-screen area, specified in logical units. You should avoid using fixed values in your drawing code, and you should scale all drawing output to the dimensions of the bounding rectangle. In this way, you ensure that the entire control is always visible and that its internal proportions are always maintained. The OnDraw() function also receives a rectangle that represents the invalidated area of the control and can be used to optimize your drawing code.

  • To implement the COneArmedBanditCtrl::OnDraw() function
  • Replace the code in your project with the following code:

    (This code can be found in CH11_01.cpp, installed from the companion CD.)

    void COneArmedBanditCtrl::OnDraw(      CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid) {      // Get colors of stock properties      COLORREF colorBack = TranslateColor(GetBackColor());      COLORREF colorFore = TranslateColor(GetForeColor());      // Get dimensions of control      float ctrlWidth = float(rcBounds.Width());      float ctrlHeight = float(rcBounds.Height());      // Setup DC      CBrush brush(colorBack);      CBrush * pOldBrush = pdc->SelectObject(&brush);      CPen pen(PS_SOLID, 3, colorFore);      CPen *pOldPen = pdc->SelectObject(&pen);      CFont SymbolFont;      CFont *pOldFont;      if(SymbolFont.           CreateFont(long(ctrlHeight / 1.1), long(ctrlWidth/6),           0, 0, 0, 0, 0, 0, SYMBOL_CHARSET, 0, 0, 0,           0,"WingDings"));           pOldFont = pdc->SelectObject(&SymbolFont);      else           pOldFont = SelectStockFont(pdc);      // Draw bounding rectangle      pdc->Rectangle(rcBounds);      pdc->SetBkMode(TRANSPARENT);      // Draw text      pdc->SetTextColor(colorFore);      RECT rect;      ::CopyRect(&rect, rcBounds);      CString strDisplay;      strDisplay.Format("%c %c %c",           m_symbols[0], m_symbols[1], m_symbols[2]);      pdc->DrawText(strDisplay, &rect, DT_SINGLELINE | DT_CENTER           | DT_VCENTER );      // Draw vertical bars      long onethird = long(ctrlWidth / 3);      CPoint ptTop(rcBounds.TopLeft());      CPoint ptBtm(rcBounds.left, rcBounds.bottom);      ptTop.x += onethird; ptBtm.x += onethird;      pdc->MoveTo(ptTop);      pdc->LineTo(ptBtm);      ptTop.x += onethird; ptBtm.x += onethird;      pdc->MoveTo(ptTop);      pdc->LineTo(ptBtm);      // Restore device context      pdc->SelectObject(pOldFont);      pdc->SelectObject(pOldPen);      pdc->SelectObject(pOldBrush); }

    This function simulates the slot machine window by displaying three characters from the Wingdings symbol font, as shown in Figure 11.1. The characters themselves are contained in the fixed-length string m_symbols, which is a member variable of the COneArmedBanditCtrl class. You will need to add this member before your code will compile.

  • To implement the COneArmedBanditCtrl::m_symbols string
    1. In ClassWizard, right-click the COneArmedBanditCtrl class. Click Add Member Variable. Create the following protected member variable:
    2. TCHAR m_symbols[3];

    3. Locate the COneArmedBanditCtrl constructor and add the following line to initalize the string:
    4. _tcscpy(m_symbols, _T("JJJ"));

    Implementing the Control Method

    All that remains is to implement the Play() method that simulates the spinning of the slot machine reels, displaying a random symbol in each window. You created a stub for the Play() implementation function when you added the method to the interface. Now you need to add code to randomly alter each of the characters in the COneArmedBanditCtrl::m_symbols string.

  • To implement the COneArmedBanditCtrl::Play() function
    1. Locate the Play() function stub and then replace the stub with the code that follows:
    2. (This code can be found in CH11_02.cpp, installed from the companion CD.)

      void COneArmedBanditCtrl::Play() {      srand((unsigned)time(NULL));      _tcscpy(m_symbols, _T("JJJ"));      for(int i = 0; i < 3; i++)           m_symbols[i] += UINT(rand() % m_numberOfSymbols);      InvalidateControl(); // repaints control      if(m_symbols[0] == m_symbols[1] &&           m_symbols[1] == m_symbols[2])           FireJackpot(); }

      The use of the modulus operator (%) ensures that each character in the m_symbols string is incremented by a random number between zero and m_numberOfSymbols. When all three characters are the same, the Jackpot custom event is fired.

    3. Press F7 to build the OneArmedBandit ActiveX control. The compiler and linker create a DLL with an .ocx extension in your output directory. If the build is successful, the control is registered on your computer.

    Testing the Control

    The ActiveX Control Test Container is a useful tool provided by Visual Studio that allows you to run and test any ActiveX control registered on your computer. In the following exercises, you will learn how to use the ActiveX Control Test Container to test the property pages, the events, and the Play() method of the OneArmedBandit ActiveX control.

  • To test the OneArmedBandit ActiveX control
    1. On the Tools menu in Visual C++, click ActiveX Control Test Container. On the Test Container's Edit menu, click Insert New Control.
    2. In the Insert Control dialog box, click OneArmedBandit Control and then click OK.
    3. Using the resize handles around the edge of the control, make the control a suitable size.
    4. On the Edit menu, simply click Properties. On the General tab of the OneArmedBandit Control Properties dialog box, change the Number of symbols property to 5.
    5. Click the Colors tab and choose BackColor and ForeColor values for the control.
    6. Click OK, and then check that the control displays the correct colors.
    7. Click inside the control area and verify that the Click event is notified in the lower half of the Test Container window.
    8. On the Control menu, click Invoke Methods. If necessary, move the Invoke Methods dialog box so that you can see the entire control and the output in the lower half of the Test Container window.
    9. With Play (Method) selected in the Method Name drop-down list, click Invoke to play the one-armed bandit game. Keep playing until you get three symbols that are the same. When this happens, you should see the Jackpot event notified in the lower half of the Test Container window.

  • To test property persistence
    1. Close the ActiveX Control Test Container. When prompted, save the session as oab.tcs.
    2. Re-open the ActiveX Control Test Container. On the File menu, simply click oab.tcs from the recently used file list. The OneArmedBandit control should display with the size and colors saved from the previous session. (The COleControl class implements the serialization of the control dimensions, without requiring any coding on your part).
    3. Click the control border to select the control. View the control properties to check that the NumberOfSymbols property is still set to 5.
    4. Close the ActiveX Control Test Container.

    Lesson Summary

    In this lesson, you have seen just how easy it is to develop ActiveX controls using MFC. The ActiveX ControlWizard creates a set of classes that implement a DLL, a control and a property page. The ControlWizard defines dispatch interfaces for your control's properties, methods, and events. You use ClassWizard to design these interfaces. An MFC ActiveX control is based on the COleControl class, which provides many member functions that allow you to implement stock properties and events, retrieve ambient properties from the container, implement property persistence, and connect a control's properties to a property page. Apart from the implementations of the custom methods, most of the code that you need to write for your control is drawing code, placed within the control's OnDraw() function.



    Microsoft Press - Desktop Applications with Microsoft Visual C++ 6. 0. MCSD Training Kit
    Desktop Applications with Microsoft Visual C++ 6.0 MCSD Training Kit
    ISBN: 0735607958
    EAN: 2147483647
    Year: 1999
    Pages: 95

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net