Lesson 2: Creating ActiveX Controls with ATL

In this lesson, you will re-implement the OneArmedBandit ActiveX control using ATL. By doing this, you will be able to compare the differences between MFC and ATL control development techniques and assess which technology is most suited to your needs.

After this lesson, you will be able to:

  • Describe how to develop ActiveX controls using ATL.
  • Describe how to add properties and methods to your control.
  • Describe how to implement a control property page and make properties persistent.
  • Describe how to use ATL's implementation of the COM connection point architecture to add events to your control.
Estimated lesson time: 50 minutes

Adding Controls to an ATL COM Project

As with any ATL COM development, you start by creating an ATL project and then add individual COM objects using the ATL Object Wizard. In the following exercises, you will create a DLL project to host your control, and then you'll add your ATL ActiveX control.

  • To create the OneArmedBanditATL project
    1. On the File menu, click New. On the Projects tab, click ATL COM AppWizard.
    2. In the Project name box, type OneArmedBanditATL and click OK.
    3. In the ATL COM AppWizard dialog box, ensure that Dynamic Link Library (DLL) is selected and click Finish.
    4. Review the New Project Information dialog box and click OK.

  • To add the ATLBandit ActiveX control
    1. On the Insert menu, click New ATL Object.
    2. In the Category list, click Controls. The ATL Object Wizard should appear as shown in Figure 11.5.
    3. click to view at full size.

      Figure 11.5 ATL Object Wizard control options

      The ATL Object Wizard displays a number of different types of controls that you can create, including:

      • Full controls Controls that can be embedded in any container that complies with ActiveX guidelines.
      • Lite controls Controls that can be embedded in Internet Explorer, but that do not support interfaces required by many other containers.
      • Composite controls Controls such as dialog boxes that are capable of containing other ActiveX controls.
      • HTML controls Controls that use an embedded Web-browser control to display an HTML page. You'll learn more about these in Chapter 12.

    4. In the Objects list, click Full Control. Click Next.
    5. Click the Names tab. In the Short Name box, type ATLBandit.
    6. On the Attributes tab, select the Support Connection Points option.
    7. On the Stock Properties tab, use the > button to move the Background Color and the Foreground Color stock properties from the Not Supported list to the Supported list, as shown in Figure 11.6.
    8. click to view at full size.

      Figure 11.6 Selecting stock properties in the ATL Object Wizard

    9. Click OK to add the ATLBandit ActiveX control to the project.

    In ClassView, you see that the wizard has added the IATLBandit interface and the CATLBandit implementation class. The CATLBandit class is derived from the ATL CComControl class (as well as many others). Open the ATLBandit.h file and locate the COM map structure. You see that the CATLBandit class supports the many interfaces that are required by ActiveX control containers.

    Directly beneath the COM map, you will see this property map structure:

    BEGIN_PROP_MAP(CATLBandit)      PROP_DATA_ENTRY("_cx", m_sizeExtent.cx, VT_UI4)      PROP_DATA_ENTRY("_cy", m_sizeExtent.cy, VT_UI4)      PROP_ENTRY("BackColor", DISPID_BACKCOLOR, CLSID_StockColorPage)      PROP_ENTRY("ForeColor", DISPID_FORECOLOR, CLSID_StockColorPage)      // Example entries      // PROP_ENTRY("Property Description", dispid, clsid)      // PROP_PAGE(CLSID_StockColorPage) END_PROP_MAP()

    ATL creates the property map structure to make implementing property persistence easy. You can see in the code just shown that entries for the stock properties, including the dimensions of the control, have already been added to the property map. Note that the PROP_ENTRY macros allow you to associate a property page with a property, and that the color stock property page has already been specified for your ForeColor and BackColor properties.

    Adding Properties

    You will now add the NumberOfSymbols custom property to the control class.

  • To add the NumberOfSymbols property
    1. In ClassView, right-click the IATLBandit interface item. Click Add Property.
    2. In the Add Property To Interface dialog box, select short for the property type. In the Property Name box, enter NumberOfSymbols.
    3. Click OK to create the functions that implement the property.

    Remember that when you are working with ATL, you must define a member variable to hold the data and provide implementations of the Get and Put functions so that you can pass data to and from this member variable.

  • To implement the NumberOfSymbols property
    1. Add a protected member variable named m_numberOfSymbols of type short to the CATLBandit class.
    2. In ClassView, expand the IATLBandit interface under the CATLBandit class item to locate the get_NumberOfSymbols() and put_NumberOfSymbols() functions. Implement these functions as shown in the following code:
    3. STDMETHODIMP CATLBandit::get_NumberOfSymbols(short *pVal) {      *pVal = m_numberOfSymbols;      return S_OK; } STDMETHODIMP CATLBandit::put_NumberOfSymbols(short newVal) {      newVal = newVal < 3 ? 3 : newVal;      newVal = newVal > 7 ? 7 : newVal;      m_numberOfSymbols = newVal;      SetDirty(TRUE);      return S_OK; }

    You should note that the put_ NumberOfSymbols() function ensures that the NumberOfSymbols property is set to a valid value. Note also the call to the SetDirty() function. This function should be called when the value of a persistent property is changed, so that the container can ask the user whether to save or abandon changes to the control's state. You will implement persistence for the NumberOfSymbols property after you have added a property page that allows access to the property value.

    Adding Events

    You will now implement the Click and Jackpot events for your control. To help you understand how COM events are implemented, open the project IDL file, OneArmedBanditATL.idl, and look at the type library definition, which is shown below.

    [      uuid(39623002-6FAA-11D3-935D-0080C7FA0C3E),      version(1.0),      helpstring("OneArmedBanditATL 1.0 Type Library") ] library ONEARMEDBANDITATLLib {      importlib("stdole32.tlb");      importlib("stdole2.tlb");      [           uuid(39623010-6FAA-11D3-935D-0080C7FA0C3E),           helpstring("_IATLBanditEvents Interface")      ]      dispinterface _IATLBanditEvents      {           properties:           methods:      };      [           uuid(3962300F-6FAA-11D3-935D-0080C7FA0C3E),           helpstring("ATLBandit Class")      ]      coclass ATLBandit      {           [default] interface IATLBandit;           [default, source] dispinterface _IATLBanditEvents;      }; };

    The definition of the ONEARMEDBANDITATLLib type library contains the definition of a dispatch interface named _IATLBanditEvents. This interface is declared inside the ATLBandit coclass block—indicating that the ATLBandit control exposes the _IATLBanditEvents interface. Notice, however, that the in-terface is declared with the [source] IDL attribute, which indicates that the _IATLBanditEvents interface is a source of events. This kind of interface is known as a connection point. COM objects that expose connection points implement the IConnectionPointContainer interface, which manages the connection of source interfaces to a corresponding client object known as a sink. A sink object implements methods defined by the source object. Through the connection point mechanism, a pointer to the sink object's interface is passed to the source object. This pointer provides the source with access to the sink's implementation of its methods. To fire an event, the source object calls the corresponding method on the sink interface.

    The first stage of defining an event is to add a method to the source interface to represent the event.

  • To add the Click() and Jackpot() event methods
    1. In ClassView, right-click the _IATLBanditEvents interface item. Click Add Method on the shortcut menu.
    2. Select void as the return type. Enter Click as the method name.
    3. Repeat the process to create a void method with the name Jackpot.

    After you have added the event methods to the source interface, you use an ATL Wizard to implement a connection point. ATL uses information from the type library to achieve this implementation, which means that the type library must first be compiled.

  • To compile the type library
    1. Switch to FileView and locate the OneArmedBanditATL.idl file.
    2. Right-click OneArmedBanditATL.idl and then click Compile OneArmedBanditATL.idl.
    3. When the compile is completed, switch back to ClassView.

    Now you can use the ATL Wizard to implement the connection point.

  • To implement a connection point for the ATLBandit control
    1. Right-click the CATLBandit class item and then click Implement Connection Point. The Implement Connection Point Wizard displays as shown in Figure 11.7.
    2. click to view at full size.

      Figure 11.7 The Implement Connection Point Wizard

    3. In the Implement Connection Point Wizard, select the _IATLBanditEvents checkbox and click OK.

    The wizard creates a proxy class (nothing to do with the proxy objects used in marshaling), which contains proxy functions that implement the event methods you added to your event interface. If you look in ClassView, you will see that the CProxy_IATLBanditEvents class has been added to your project, and that it provides the functions Fire_Click() and Fire_Jackpot() that you use to fire control events. ATL alters the definition of the CATLBandit class so that it derives from the CProxy_IATLBanditEvents class, making the Fire_Click() and Fire_Jackpot() methods accessible as members of your control class.

    The Implement Connection Point Wizard also creates the following connection point map within your class.

    BEGIN_CONNECTION_POINT_MAP(CATLBandit)      CONNECTION_POINT_ENTRY(IID_IPropertyNotifySink)      CONNECTION_POINT_ENTRY(DIID__IATLBanditEvents) END_CONNECTION_POINT_MAP()

    Make sure that the connection point map appears exactly as just shown—ATL sometimes fails to implement this map correctly, causing compilation errors.

    You are now in a position to use the event proxy functions to fire events from your control. To demonstrate this, you will implement a handler function that fires a Click() event when you click inside the control.

  • To implement the OnLButtonDown() handler function
    1. In ClassView, right-click the CATLBandit class item. On the shortcut menu, click Add Windows Message Handler.
    2. In the New Windows messages/events list, click WM_LBUTTONDOWN.
    3. Click Add and Edit to add the OnLButtonDown() function and jump to the implementation code, which is declared inline in the CATLBandit class definition. Note that ATL maintains its own kind of message map, which is located just below the connection point map.
    4. Replace the //TODO comment inside the OnLButtonDown() function implementation with the following single line of code:
    5. Fire_Click();

    Adding Methods

    You will now add the Play() method to the control interface and provide the code to implement the method.

  • To add the Play() method
    1. In ClassView, right-click the IATLBandit interface. Click Add Method on the shortcut menu.
    2. In the Add Method to Interface dialog box, type Play in the Method Name box.
    3. Click OK to create the method.

  • To implement the Play() method
    1. As you did with the MFC OneArmedBandit control in Lesson 1, add the following protected member variable to the CATLBandit class:
    2. TCHAR m_symbols[3];

      To the CATLBandit constructor, add the following line to initialize the string:

      _tcscpy(m_symbols, _T("JJJ"));

    3. In ClassView, expand the IATLBandit interface beneath the CATLBandit class item.
    4. Double-click the Play() method underneath the IATLBandit interface item to edit the CATLBandit::Play() implementation function.
    5. Add code to the body of the CATLBandit::Play() function so that it appears as follows:
    6. (This code can be found in CH11_03.cpp, installed from the companion CD.)

      STDMETHODIMP CATLBandit::Play() {      srand((unsigned)time(NULL));      _tcscpy(m_symbols, _T("JJJ"));      for(int i = 0; i < 3; i++)           m_symbols[i] += UINT(rand() % m_numberOfSymbols);      // repaint control      m_spInPlaceSite->InvalidateRect(NULL, TRUE);      if(m_symbols[0] == m_symbols[1] &&           m_symbols[1] == m_symbols[2])           Fire_Jackpot();      return S_OK; }

    7. To the top of the ATLBandit.cpp file, add the following line:
    8. #include <time.h>

    Creating Property Pages

    You will now implement the Symbols property page, which will enable users to set the value of the NumberOfSymbols custom property. ATL sets up each page of a control's property sheet as a separate object, implemented by a class derived from IPropertyPage. In the following exercise, you will insert a new property page class into your project.

  • To add the Symbols property page
    1. On the Insert menu, click New ATL Object. In the Category list, click Controls.
    2. In the Objects list, click Property Page. Click Next.
    3. Click the Names tab. In the Short Name box, type BanditPP.
    4. On the Strings tab, type Symbols in the Title box. Delete the text in the other two boxes.
    5. Click OK to add the property page. A dialog template for the page opens in the dialog editor.
    6. Create the dialog template just as you did for the MFC control (refer back to Figure 11.4). Add a static text control and a numeric edit control with the ID IDC_NUMSYMBOLS.

    If you switch back to ClassView, you will see that the CBanditPP class has been added to the project. The ATL Object Wizard has provided an implementation of the IPropertyPageImpl::Apply() function for this class. Apply() is executed when the user of the property page clicks the OK or Apply button. You will add code to provide an implementation of this function.

  • To implement the CBanditPP::Apply() function
  • Locate the CBanditPP::Apply() function in the BanditPP.h file. Replace the example code with the following implementation:

    STDMETHOD(Apply)(void) {      CComQIPtr<IATLBandit> pIBandit(m_ppUnk[0]);      pIBandit->put_NumberOfSymbols(GetDlgItemInt(IDC_NUMSYMBOLS));      m_bDirty = FALSE;      return S_OK; }

    This function sets the control's NumberOfProperties property with the integer value retrieved from the edit control. Notice the use of the ATL smart pointer class CComQIPtr to retrieve an IATLBandit pointer. The member variable m_ppUnk is an array of IUnknown pointers to the objects associated with the property page.

    The Apply() function stores the value in the edit control to the persistent IATLBandit::NumberOfSymbols property. You will also need to create a function to initialize the edit control with the property value. This function is essentially the Apply() function in reverse.

  • To implement the CBanditPP::OnInitDialog() function
    1. In ClassWizard, right-click the CBanditPP class item. Click Add Windows Message Handler on the shortcut menu.
    2. In the New Windows messages/events list, click the WM_INITDIALOG message.
    3. Click Add and Edit to add the OnInitDialog() function and jump to the implementation code.
    4. Implement the function as shown in the following code:
    5. LRESULT OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam,       BOOL& bHandled) {      CComQIPtr<IATLBandit> pIBandit(m_ppUnk[0]);      short i = 0;      pIBandit->get_NumberOfSymbols(&i);      SetDlgItemInt(IDC_NUMSYMBOLS, i);      return 0; }

      At this point, you should also add a notification function to indicate that the property page has been modified. This function should handle the EN_CHANGE event that fires when the user changes the value in the edit control.

  • To implement the CBanditPP::OnChangeNumsymbols() function
    1. In ClassWizard, right-click the CBanditPP class item. Click Add Windows Message Handler on the shortcut menu.
    2. In the Class or object to handle list, click the IDC_NUMSYMBOLS object.
    3. In the New Windows messages/events list, click the EN_CHANGE event.
    4. Click Add and Edit and then click OK to add the OnChangeNumsymbols() function and jump to the implementation code.
    5. Replace the //TODO comment with the following line of code:
    6. SetDirty(TRUE);

    Now that you have fully implemented the property page, you can add an entry to the control class property map to make the NumberOfSymbols property persistent.

  • To make the NumberOfSymbols property persistent
    1. Locate the property map in the ATLBandit.h file.
    2. Add a new entry for the NumberOfSymbols property so that the property map appears as follows:
    3. BEGIN_PROP_MAP(CATLBandit)      PROP_DATA_ENTRY("_cx", m_sizeExtent.cx, VT_UI4)      PROP_DATA_ENTRY("_cy", m_sizeExtent.cy, VT_UI4)      PROP_ENTRY("NumberOfSymbols", 1, CLSID_BanditPP)      PROP_ENTRY("BackColor", DISPID_BACKCOLOR, CLSID_StockColorPage)      PROP_ENTRY("ForeColor", DISPID_FORECOLOR, CLSID_StockColorPage) END_PROP_MAP()

    The second parameter to the PROP_ENTRY macro is the DISPID, which can be found in the IDL file. The CLSID_BanditPP constant is defined in the OneArmedBanditATL_i.c file.

    Drawing the Control

    All that remains is to implement the drawing code. Like the MFC COleControl class, the CComControl class provides an OnDraw() function to contain all the drawing code. The CComControl::OnDraw() function receives a reference to an ATL_DRAWINFO structure that contains, among other things, a handle to a device context and a pointer to a RECT structure that denotes the bounding rectangle of the control. The device context is an HDC, the raw Windows data type that is wrapped by the MFC class CDC. This means that you have to use the GDI API functions to render your control. The GDI functions are similar to their CDC counterparts, the main difference being that they receive the HDC as the first argument. If you compare the implementation of CATLBandit::OnDraw() with the COneArmedBanditCtrl::OnDraw() function in the previous lesson, you will get a pretty good idea of how it all works.

  • To implement the CATLBandit::OnDraw() function
  • Replace the function in ATLBandit.h with the following version:

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

    HRESULT OnDraw(ATL_DRAWINFO& di) {      const RECT& rc = *reinterpret_cast<const RECT*>(di.prcBounds);      HDC dc = di.hdcDraw;      COLORREF colorBack, colorFore;      OleTranslateColor(m_clrForeColor, NULL, &colorFore);      OleTranslateColor(m_clrBackColor, NULL, &colorBack);      // Get dimensions of control      float ctrlWidth = float(rc.right - rc.left);      float ctrlHeight = float(rc.bottom - rc.top);      // Set up DC      HBRUSH brush = CreateSolidBrush(colorBack);      HBRUSH oldBrush = static_cast<HBRUSH>(SelectObject(dc, brush));      HPEN pen = CreatePen(PS_SOLID, 3, colorFore);      HPEN oldPen = static_cast<HPEN>(SelectObject(dc, pen));      HFONT SymbolFont = CreateFont(long(ctrlHeight / 1.1),           long(ctrlWidth/6), 0, 0, 0, 0, 0, 0, SYMBOL_CHARSET, 0, 0, 0,           0,"WingDings");      HFONT oldFont = static_cast<HFONT>(SelectObject(dc, SymbolFont));      // Draw bounding rectangle      Rectangle(dc, rc.left, rc.top, rc.right, rc.bottom);      SetBkMode(dc, TRANSPARENT);      // Draw text      SetTextColor(dc, colorFore);      RECT rect;      CopyRect(&rect, &rc);      TCHAR strDisplay[5];      _stprintf(strDisplay, "%c %c %c",            m_symbols[0], m_symbols[1], m_symbols[2]);      DrawText(dc, strDisplay, 5, &rect, DT_SINGLELINE | DT_CENTER |            DT_VCENTER );      // Draw vertical bars      long onethird = long(ctrlWidth / 3);      POINT ptTop = { rc.left, rc.top };      POINT ptBtm = { rc.left, rc.bottom };      ptTop.x += onethird; ptBtm.x += onethird;      MoveToEx(dc, ptTop.x, ptTop.y, NULL);      LineTo(dc, ptBtm.x, ptBtm.y);      ptTop.x += onethird; ptBtm.x += onethird;      MoveToEx(dc, ptTop.x, ptTop.y, NULL);      LineTo(dc, ptBtm.x, ptBtm.y);      // Restore device context      SelectObject(dc, oldFont);      SelectObject(dc, oldPen);      SelectObject(dc, oldBrush);      DeleteObject(brush);      DeleteObject(pen);      DeleteObject(SymbolFont);      return S_OK; }

    You can now build the ATLBandit control. Load the control into the ActiveX Control Test Container to test the control display, the property pages, the property persistence, the Play() method, and the Click and Jackpot events.

    MFC or ATL?

    By now you should have a good idea of the differences between MFC and ATL ActiveX control development. MFC makes it very easy to develop controls, shielding you from most of the complexities of COM programming. Although developing controls with ATL involves more work, as your COM expertise develops you might come to appreciate the greater degree of control that ATL affords.

    ActiveX controls written in ATL are smaller than their MFC counterparts, and if written properly will perform better. A self-contained (MinDependency) build of the ATLBandit project results in a control that is 100 KB in size. The OneArmedBandit.ocx, when statically linked to the MFC libraries, is about 264 KB. When the component is dynamically linked to the MFC libraries, its size drops to about 36 KB, making MFC an attractive proposition when developing for target platforms where you can expect the MFC DLLs to be installed.

    Lesson Summary

    ATL can be used to create small, high-performance ActiveX controls that are suitable for use in any ActiveX control container. In ATL, ActiveX controls are implemented by a class that inherits from the CComControl class. ActiveX controls also implement the many COM interfaces that are required by ActiveX control containers. ATL control classes implement a property map structure to implement property persistence, a message map to handle Windows messages, and COM connection points to implement ActiveX events. You can insert a property page COM object into your project to implement a property page for your control. You draw your control using GDI API functions inside the CComControl::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