visual c plus mfc 29

Upload: svinothkumar

Post on 03-Jun-2018

227 views

Category:

Documents


0 download

TRANSCRIPT

  • 8/11/2019 Visual c Plus Mfc 29

    1/70

    ATL and ActiveX Controls

    Program examples compiled using Visual C++ 6.0 compiler on Windows XP Pro machine with Service Pack 2.Topics and sub topics for this tutorial are listed below. Dont forget to read Tenouks small disclaimer. Thesupplementary note for this tutorial is control class.

    Index:

    ActiveX ControlsUsing ATL to Write a ControlThe MyatldicesvrProgram From Scratch

    The StoryATL's Control ArchitectureCComControlCComControlBase

    CWindowImpland CWindowImplBase

    ATL Windowing

    ATL Message MapsDeveloping the ControlDeciding What to Draw

    Responding to Window MessagesAdding Properties and Property PagesProperty PagesProperty PersistenceBidirectional Communication (Events)Using the ControlConclusion

    If you've finished reading about COM and ATL and still wonder how COM fits into your day-to-day programmingactivities, you're not alone. Figuring out how to use COM in real life isn't always obvious at first glance. After all, awhole lot of extra code must be typed in just to get a COM object up and running. However, there's a very realapplicationof COM right under your nose, ActiveX Controls. ActiveX controls are small gadgets (usually UI-oriented) written around the Component Object Model.In Module 28, you examined COM classes created by using ATL. In this module, you'll learn how to write a certainkind of COM class, an ActiveX control. You had a chance to work with ActiveX Controls from the client side inModule 18. Now it's time to write your own. There are several steps involved in creating an ActiveX control usingATL, including:

    Deciding what to draw.

    Developing incoming interfaces for the control.

    Developing outgoing interfaces (events) for the control.

    Implementing a persistence mechanism for the control. Providing a user interface for manipulating the control's properties.

    This module covers all these steps. Soon, you'll be able to use ATL to create ActiveX controls that you (or otherdevelopers) can use within other programs. The next two modules will present ActiveX control program examplescompiled using Visual C++ .Net.

    ActiveX Controls

    Even today, there's some confusion as to what really constitutes an ActiveX control. In 1994, Microsoft tacked somenew interfaces onto its Object Linking and Embedding protocol, packaged them within DLLs, and called them OLEControls. Originally, OLE Controlsimplemented nearly the entire OLE Document embedding protocol. Inaddition, OLE Controls supported the following:

    Dynamic invocation (Automation).

    Property pages (so the user could modify the control's properties).

    http://www.tenouk.com/disclaimer.htmlhttp://www.tenouk.com/visualcplusmfc/mfcsupp/ccomcontrol.htmlhttp://www.tenouk.com/visualcplusmfc/visualcplusmfc28.htmlhttp://www.tenouk.com/visualcplusmfc/visualcplusmfc18.htmlhttp://www.tenouk.com/visualcplusmfc/visualcplusmfc18.htmlhttp://www.tenouk.com/visualcplusmfc/visualcplusmfc28.htmlhttp://www.tenouk.com/visualcplusmfc/mfcsupp/ccomcontrol.htmlhttp://www.tenouk.com/disclaimer.html
  • 8/11/2019 Visual c Plus Mfc 29

    2/70

    Outbound callback interfaces (event sets).

    Connections (a standard way to for clients and controls to hook up the event callbacks).

    When the Internet became a predominant factor in Microsoft's marketing plans, Microsoft announced its intention toplant ActiveX Controls on Web pages. At that point, the size of these components became an issue. Microsoft took

    its OLE Control specification, changed the name from OLE Controlsto ActiveX Controls, and stated that all the

    features listed above were optional. This means that under the new ActiveX Control definition, a control's onlyrequirement is that it be based on COM and that it implements I Unknown. Of course, for a control to be useful itreally needs to implement most of the features listed above. So in the end, ActiveX Controls and OLE Controls refer

    to more or less the same animal.Developers have been able to use MFC to create ActiveX controls since mid-1994. However, one of the downsidesof using MFC to create ActiveX controls is that the controls become bound to MFC. Sometimes you want yourcontrols to be smaller or to work even if the end user doesn't have the MFC DLLs on his or her system. In addition,using MFC to create ActiveX controls forces you into making certain design decisions. For example, if you decideto use MFC to write an ActiveX control, you more or less lock yourself out of using dual interfaces (unless you feellike writing a lot of extra code). Using MFC to create ActiveX controls also means the control and its property pages

    need to use IDispatch to communicate among themselves.To avoid the problems described so far, developers can now use ATL to create ActiveX controls. ATL now includesthe facilities to create full-fledged ActiveX controls, complete with every feature an ActiveX control should have.

    These features include incoming interfaces, persistent properties, property pages, and connection points. If you'veever written an ActiveX control using MFC, you'll see how much more flexible using ATL can be.

    Using ATL to Write a Control

    Although creating an ActiveX control using ATL is actually a pretty straightforward process, using ATL ends upbeing a bit more burdensome than using MFC. That's because ATL doesn't include all of MFC's amenities. Forexample, ATL doesn't include device context wrappers. When you draw on a device context, you need to use theraw device context handle. In addition, ClassWizard doesn't understand ATL-based source code, so when you want

    your control to handle messages, you end up using the "TypingWizard". That is, you end up typing the messagemaps in by hand.

    Despite these issues, creating an ActiveX control using ATL is a whole lot easier than creating one from scratch.Also, using ATL gives you a certain amount of flexibility you don't get when you use MFC. For example, while

    adding dual interfaces to your control is a tedious process with MFC, you get them for free when you use ATL. TheATL COM Object Wizardalso makes adding more COM classes (even non-control classes) to your project very

    easy, while adding new controls to an MFC-based DLL is a bit more difficult.For this module's example, we'll represent a small pair of dice as an ATL-based ActiveX control. The dice controlwill illustrate the most important facets of ActiveX Controls, including control rendering, incoming interfaces,properties, property pages, and events.

    The Myatldicesvr Program From Scratch

    Before we dig into the story, let build an ATL program from scratch. As usual, launch AppWizard by clicking FileNewmenu of the Visual C++. Then, just follow the shown steps.

  • 8/11/2019 Visual c Plus Mfc 29

    3/70

    Figure 1: Myatldicesvr- Visual C++ new ATL COM AppWizard project dialog.

    Figure 2: ATL COM AppWizard step 1 of 1.

  • 8/11/2019 Visual c Plus Mfc 29

    4/70

    Figure 3: Myatldicesvrproject summary.

    Add new ATL object.

    Figure 4: Adding new ATL object.

    Select Controlsin Categorylist and Full Controlin Objectslist. Then click the Nextbutton.

  • 8/11/2019 Visual c Plus Mfc 29

    5/70

  • 8/11/2019 Visual c Plus Mfc 29

    6/70

    Figure 7: Adding the Support Connection Pointattribute to ATL object.

    Just accept the default for Miscellaneous.

    Figure 8: Accepting default miscellaneous options.

    We select the Background Colorfor the Stock Properties.

  • 8/11/2019 Visual c Plus Mfc 29

    7/70

    Figure 9: Adding background of the Stock Properties.

    Start creating bitmaps for white, blue and red colors. Use the Copyand Pastemenu under the Editto speed up yourwork :-).

    Figure 10: Adding white dice bitmaps.

  • 8/11/2019 Visual c Plus Mfc 29

    8/70

    Figure 11: Adding blue dice bitmaps.

    Figure 12: Adding red dice bitmaps.

    Now we can start the coding part. Put codes in myatldiceob.h. Firstly add the MAX_DI EFACESconstant.

    #def i ne MAX_DI EFACES 6

    Listing 1.

    Using ClassView, add LoadBi t maps( ) function to Cmyat l di ceobclass as shown below.

  • 8/11/2019 Visual c Plus Mfc 29

    9/70

    BOOL LoadBi t maps( ) ;

    Figure 13: Adding functions and variables to Cmyat l di ceobclass.

    Figure 14: Adding LoadBi t map( ) function.

    Add the HBI TMAP m_di eBi t maps[ MAX_DI EFACES] array variable.

  • 8/11/2019 Visual c Plus Mfc 29

    10/70

    Figure 15: Adding an array variable.

    Using ClassView, add the following member functions.

    voi d ShowFi r st Di eFace(ATL_DRAWI NFO& di ) ; voi d ShowSecondDi eFace( ATL_ DRAWI NFO& di ) ;

    Figure 16: Adding ShowFi r st Di eFace( ) member function.

    Figure 17: Adding ShowSecondDi eFace( ) function to Cmyat l di ceobclass.

    Using ClassView, add the following member variables.

    shor t m_nDi ceCol or ; shor t m_nTi mesToRol l ; shor t m_nTi mesRol l ed;

  • 8/11/2019 Visual c Plus Mfc 29

    11/70

    Figure 18: Adding member variables to Cmyat l di ceobclass.

    Add the following member variables.

    unsi gned shor t m_nFi r st Di eVal ue; unsi gned shor t m_nSecondDi eVal ue;

    Figure 19: Adding member variable, m_nFi r st Di eVal ueto Cmyat l di ceobclass.

    Figure 20: Adding member variable, m_nSecondDi eVal ueto Cmyat l di ceobclass.

    The following are the previously added member functions and variables. Take note that, the codes have been

    relocated just after the END_MSG_MAP( ) .

  • 8/11/2019 Visual c Plus Mfc 29

    12/70

    Listing 2.

    Add the Cmyat l di ceob( ) codes. These codes are variables initialization.

    Cmyat l di ceob( ) {

    m_bWi ndowOnl y = TRUE;

    LoadBi t maps( ) ; sr and( ( unsi gned) t i me( NULL) ) ; m_nFi r st Di eVal ue = ( r and( ) % ( MAX_DI EFACES) ) + 1; m_nSecondDi eVal ue = ( r and( ) % ( MAX_DI EFACES) ) + 1;

    m_nTi mesToRol l = 15; m_nTi mesRol l ed = 0; m_nDi ceCol or = 0;

    }

    Listing 3.

    Add the #i ncl udedirective for time related function.

    #i ncl ude " t i me. h"

    Listing 4.

  • 8/11/2019 Visual c Plus Mfc 29

    13/70

    Edit the OnDr aw( ) . Take note that, here, you will find that the location of the OnDr aw( ) and a few otherfunctions declared and defined in myatldiceob.h(default if using ClassView) while in the story part of this Tutorial,the functions declared in myatldiceob.hand defined in myatldiceob.cpp.

    HRESULT OnDr aw( ATL_DRAWI NFO& di ) {

    RECT& r c = *( RECT*) di . prcBounds; ShowFi r st Di eFace( di ) ; ShowSecondDi eFace( di ) ; return S_OK;

    }

    Listing 5.

    Add WM_TI MER(OnTi mer ( ) ) Windows message handler using ClassView to Cmyat l di ceobclass.

    Figure 21: Adding WM_TI MERWindows message handler to Cmyat l di ceobclass.

  • 8/11/2019 Visual c Plus Mfc 29

    14/70

    Figure 22: Adding WM_TI MECHANGEWindows message handler.

    Add the OnTi mer ( ) code.

    LRESULT OnTi mer ( UI NT uMsg, WPARAM wPar am, LPARAM l Par am, BOOL& bHandl ed) {

    / / TODO : Add Code f or message handl er ./ / Cal l Def Wi ndowPr oc i f necessary. i f( m_nTi mesRol l ed > 15) {

    m_nTi mesRol l ed = 0; Ki l l Ti mer ( 1) ;

    } el se {m_nFi r st Di eVal ue = ( r and( ) % ( MAX_DI EFACES) ) + 1; m_nSecondDi eVal ue = ( r and( ) % ( MAX_DI EFACES) ) + 1; Fi r eVi ewChange( ) ;

    m_nTi mesRol l ed++; }bHandl ed = TRUE; return 0;

    }

  • 8/11/2019 Visual c Plus Mfc 29

    15/70

    Listing 6.

    Add other implementation codes in myatldiceob.cppfor LoadBi t maps( ) , ShowFi r st Di eFace( ) andShowSecondDi eFace( ) .

    BOOL Cmyat l di ceob: : LoadBi t maps( ) {

    i nt i ; BOOL bSuccess = TRUE; i nt nI D = I DB_DI CE1;

    f or ( i =0; i

  • 8/11/2019 Visual c Plus Mfc 29

    16/70

    Listing 7.

    voi d Cmyat l di ceob: : ShowFi r st Di eFace( ATL_DRAWI NFO &di ) {

    BI TMAP bmI nf o; GetObj ect ( m_di eBi t maps[ m_nFi r st Di eVal ue- 1] ,

    si zeof( bmI nf o) , &bmI nf o) ;

    SI ZE si ze;

    si ze. cx = bmI nf o. bmWi dth; si ze. cy = bmI nf o. bmHei ght ;

    HDC hMemDC; hMemDC = Cr eat eCompat i bl eDC( di . hdcDr aw) ;

    HBI TMAP hOl dBi t map; HBI TMAP hbm = m_di eBi t maps[ m_nFi r st Di eVal ue- 1] ; hOl dBi t map = ( HBI TMAP) Sel ect Obj ect ( hMemDC, hbm) ;

    i f ( hOl dBi t map == NULL) return; / / destr uctor s wi l l c l ean up

    Bi t Bl t ( di . hdcDr aw, di . pr cBounds- >l ef t +1,

    di . prcBounds- >t op+1, si ze. cx, si ze. cy, hMemDC, 0, 0, SRCCOPY) ;

    Sel ect Obj ect ( di . hdcDr aw, hOl dBi t map) ; Del et eDC(hMemDC) ;

    }

  • 8/11/2019 Visual c Plus Mfc 29

    17/70

    Listing 8.

    voi d Cmyat l di ceob: : ShowSecondDi eFace( ATL_DRAWI NFO &di )

    {BI TMAP bmI nf o; GetObj ect ( m_di eBi t maps[ m_nFi r st Di eVal ue- 1] ,

    si zeof( bmI nf o) , &bmI nf o) ;

    SI ZE si ze;

    si ze. cx = bmI nf o. bmWi dth; si ze. cy = bmI nf o. bmHei ght ;

    HDC hMemDC; hMemDC = Cr eat eCompat i bl eDC( di . hdcDr aw) ;

    HBI TMAP hOl dBi t map; HBI TMAP hbm= m_di eBi t maps[ m_nSecondDi eVal ue- 1] ; hOl dBi t map = ( HBI TMAP) Sel ect Obj ect ( hMemDC, hbm) ;

    i f ( hOl dBi t map == NULL) return; / / destr uctor s wi l l c l ean up

    Bi t Bl t ( di . hdcDr aw, di . pr cBounds- >l ef t +si ze. cx + 2, di . prcBounds- >t op+1, si ze. cx, si ze. cy, hMemDC, 0,

    0, SRCCOPY) ;

  • 8/11/2019 Visual c Plus Mfc 29

    18/70

    Sel ect Obj ect ( di . hdcDr aw, hOl dBi t map) ; Del et eDC(hMemDC) ;

    }

    Listing 9.

    Add the Rol l Di ce( ) method using ClassView to I myat l di ceobinterface.

    Figure 23: Adding method to I myat l di ceobinterface.

  • 8/11/2019 Visual c Plus Mfc 29

    19/70

    Figure 24: Adding Rol l Di ce( ) method to I myat l di ceobinterface.

    Listing 10.

    Add the Rol l Di ce( ) code.

    STDMETHODI MP Cmyat l di ceob: : Rol l Di ce( ) {

    / / TODO: Add your i mpl ement at i on code hereSetTi mer ( 1, 250) ; return S_OK;

    }

    Listing 11.

    Add WM_LBUTTONDBLCLKWindows message to Cmyat l di ceobclass using ClassView.

  • 8/11/2019 Visual c Plus Mfc 29

    20/70

    Figure 25: Add WM_LBUTTONDBLCLKWindows message to Cmyat l di ceobclass.

    Then, add the code.

    LRESULT OnLBut t onDbl Cl k( UI NT uMsg, WPARAM wPar am, LPARAM l Par am, BOOL&bHandl ed) {

    / / TODO : Add Code f or message handl er . Cal l Def Wi ndowProc i fnecessar y.

    Rol l Di ce( ) ; bHandl ed = TRUE; return 0;

    }

    Listing 12.

    Build and run using container or use the ActiveX Control Test Containeras shown below. Complete steps to builda dialog based program to test this dice control can be found HERE.

    http://www.tenouk.com/visualcplusmfc/mfcatltest29.htmlhttp://www.tenouk.com/visualcplusmfc/mfcatltest29.html
  • 8/11/2019 Visual c Plus Mfc 29

    21/70

    Figure 26: Using ActiveX Control Test Containerto test our control.

    Figure 27: Inserting ActiveX control for testing.

  • 8/11/2019 Visual c Plus Mfc 29

    22/70

    Figure 28: Selecting myatldiceobcontrol for testing.

    Figure 29: myatldiceobin ActiveX Control Test Container.

    Add other functionalities. Add the following properties for get and put functions to I myat l di ceobinterface.

    Di ceCol or ( ) Ti mesToRol l ( )

  • 8/11/2019 Visual c Plus Mfc 29

    23/70

    Figure 30: Adding properties for get and put functions to I myat l di ceobinterface.

    Figure 31: Adding Di ceCol or ( ) property for get and put functions to I myat l di ceobinterface.

  • 8/11/2019 Visual c Plus Mfc 29

    24/70

    Figure 32: AddingTi mesToRol l ( ) property for get and put functions to I myat l di ceobinterface.

    You can verify the previous step in myatldiceob.hfile as shown below.

    Listing 13.

    And also in myatldicesvr.idlfile.

    Listing 14.

  • 8/11/2019 Visual c Plus Mfc 29

    25/70

  • 8/11/2019 Visual c Plus Mfc 29

    26/70

    Add methods to_I myat l di ceobEvent s interface. These methods are visible in myatldicesvr.idlfile only.Then, they will be visible to the myat l di ceobclass through the Implement Connection Point.

    voi d Doubl es( shor t n) voi d SnakeEyes( ) voi d Di ceRol l ed( shor t x, shor t y)

    Figure 33: Adding methods to_ I myat l di ceobEvent s interface.

    Figure 34: Adding Doubl es( ) methods to_ I myat l di ceobEvent sinterface.

  • 8/11/2019 Visual c Plus Mfc 29

    27/70

    Figure 35: Adding SnakeEyes( ) methods to_ I myat l di ceobEvent s interface.

    Figure 36: Adding Di ceRol l ed( ) methods to_ I myat l di ceobEvent s interface.

    Add the Implement Connection Pointfunctionality to Cmyat l di ceobclass for the previously created methods.

  • 8/11/2019 Visual c Plus Mfc 29

    28/70

    Figure 37: Adding Implement Connection Pointfunctionality to Cmyat l di ceobclass.

    Figure 38: Selecting_I myat l di ceobEvent s event interface.

    Edit the LoadBi t map( ) function by adding the swi t chstatement to select different dice color.

    BOOL Cmyat l di ceob: : LoadBi t maps( ) {

    i nt i ; BOOL bSuccess = TRUE;

  • 8/11/2019 Visual c Plus Mfc 29

    29/70

    i nt nI D = I DB_DI CE1;

    swi t ch( m_nDi ceCol or ) {

    case 0: nI D = I DB_DI CE1;

    break;

    case 1: nI D = I DB_BLUEDI CE1; break;

    case 2: nI D = I DB_REDDI CE1; break;

    }

    f or ( i =0; i

  • 8/11/2019 Visual c Plus Mfc 29

    30/70

    {/ / TODO: Add your i mpl ement at i on code herei f( : : I sWi ndow( m_hWnd) ) {

    SetTi mer ( 1, 250) ; }

    return S_OK; }

    Listing 17.

    Edit OnTi mer ( ) function to reflect our new functionality.

    LRESULT OnTi mer ( UI NT uMsg, WPARAM wPar am, LPARAM l Par am, BOOL& bHandl ed) {

    / / TODO : Add Code f or message handl er . / / Cal l Def Wi ndowPr oc i f necessar y. i f( m_nTi mesRol l ed > m_nTi mesToRol l ) {

    m_nTi mesRol l ed = 0; Ki l l Ti mer ( 1) ;

    Fi r e_Di ceRol l ed( m_nFi r st Di eVal ue, m_nSecondDi eVal ue) ;

    i f( m_nFi r st Di eVal ue == m_nSecondDi eVal ue) {

    Fi r e_Doubl es( m_nFi r st Di eVal ue) ; }

    i f( m_nFi r st Di eVal ue == 1 && m_nSecondDi eVal ue == 1) {

    Fi r e_SnakeEyes( ) ; }}el se{

    m_nFi r st Di eVal ue = ( r and( ) % ( MAX_DI EFACES) ) + 1; m_nSecondDi eVal ue = ( r and( ) % ( MAX_DI EFACES) ) + 1;Fi r eVi ewChange( ) ; m_nTi mesRol l ed++;

    }

    bHandl ed = TRUE; return 0;

    }

    Adding a property page so that user can easily use/change our control properties. Add new ATL object by selectingthe Insert New ATL Object.

  • 8/11/2019 Visual c Plus Mfc 29

    31/70

    Figure 39: Inserting new ATL object.

    Select Controlsin Categorylist and Property Pagein Objectslist. Then click the Nextbutton

    Figure 40: Selecting Property Objectfor controls property page.

    Type in a meaningful name such as Di ceMai nPropPageas shown below in the Short Namefield.

    Figure 41: Entering new ATL objects name.

  • 8/11/2019 Visual c Plus Mfc 29

    32/70

    Just accept the default setting for Attributespage.

    Figure 42: Accepting the defaults option of the Attributes.

    Type in meaningful strings for Stringspage and click the OKbutton.

    Figure 43: Entering some strings for our controls property page.

    Add Static text, Combo boxand Edit controlto the blank I DD_DI CEMAI NPROPPAGEtemplate. UseI DC_COLORand I DC_TI MESTOROLL as their I Ds respectively. Leave others as default.

  • 8/11/2019 Visual c Plus Mfc 29

    33/70

    Figure 44: Adding Static text, Combo box and Edit control to the blank property page template.

    Figure 45: Combo box, I DC_COLORproperty.

  • 8/11/2019 Visual c Plus Mfc 29

    34/70

    Figure 46: Edit controls property.

    Add Windows message handlers as shown in the following Table using ClassView to CDi ceMai nPropPageclass.

    ID/Class Windows message Functions

    I DC_COLOR CBN_SELENDOK OnCol or Change( ) I DC_TI MESTOROLL EN_CHANGE OnTi mesToRol l Change( )

    CDi ceMai nPropPage WM_I NI TDI ALOG -

    Table 1.

  • 8/11/2019 Visual c Plus Mfc 29

    35/70

    Figure 47: Adding Windows message handlers to CDi ceMai nPropPageclass.

    Figure 48: Adding WM_I NI TDI ALOGhandler to CDi ceMai nPropPageclass.

  • 8/11/2019 Visual c Plus Mfc 29

    36/70

    Figure 49: Adding CBN_SELENDOKhandler to CDi ceMai nPropPageclass.

  • 8/11/2019 Visual c Plus Mfc 29

    37/70

    Figure 50: Adding EN_CHANGEhandler to CDi ceMai nPropPageclass.

    Before we forget, add the following property entries to myatldiceob.hfile manually.

    PROP_ENTRY( "Di ceCol or " , 2, CLSI D_Di ceMai nPr opPage) PROP_ENTRY( "Ti mesToRol l " , 3, CLSI D_Di ceMai nPr opPage)

    Listing 18.

    Edit the Appl y( ) method in DiceMainPropPage.hfile.

    STDMETHOD( Appl y) ( voi d) {

    ATLTRACE(_T( "CDi ceMai nPropPage: : Appl y\ n" ) ) ; f or ( UI NT i = 0; i < m_nObj ect s; i ++) {

    USES_ CONVERSI ON; ATLTRACE( _T( "CDi ceMai nPropPage: : Appl y\ n") ) ; f or ( UI NT i = 0; i < m_nObj ect s; i ++) {CComQI Pt r pATLDi ceObj ( m_ppUnk[ i ] ) ; HWND hWndComboBox = Get Dl gI t em( I DC_COLOR) ;

    shor t nCol or = ( shor t ) : : SendMessage( hWndComboBox, CB_GETCURSEL, 0, 0) ;

    i f( nCol or >= 0 && nCol or put _Di ceCol or ( nCol or ) ) {

    CComPt r pEr r or ; CComBSTR st r Er r or ; Get Er r or I nf o( 0, &pEr r or ) ; pEr r or - >Get Descri pt i on( &st r Er r or ) ; MessageBox( OLE2T(st r Er r or ) ,

    _T( "Er r or " ) ,MB_I CONEXCLAMATI ON) ;

    return E_FAI L; }}shor t nTi mesToRol l = ( shor t ) GetDl gI t emI nt ( I DC_TI MESTOROLL) ; i f FAI LED( pATLDi ceObj - >put_Ti mesToRol l ( nTi mesToRol l ) ) {

    CComPt r pEr r or ; CComBSTR st r Er r or ; Get Er r or I nf o( 0, &pEr r or ) ; pEr r or - >Get Descri pt i on( &st r Er r or ) ; MessageBox( OLE2T( st r Er r or ) , _T(" Er r or ") ,MB_I CONEXCLAMATI ON) ; return E_FAI L;

    }}m_bDi r t y = FALSE;

  • 8/11/2019 Visual c Plus Mfc 29

    38/70

    return S_OK;

    }m_bDi r t y = FALSE; return S_OK;

    }

    Add the handlers codes.

    LRESULT OnI ni t Di al og( UI NT uMsg, WPARAM wParam, LPARAM l Param, BOOL& bHandl ed) {

    / / TODO : Add Code f or message handl er . Cal l Def Wi ndowProc i fnecessar y.

    HWND hWndComboBox = Get Dl gI t em( I DC_COLOR) ; : : SendMessage( hWndComboBox,

    CB_ADDSTRI NG, 0, ( LPARAM) "Whi t e") ;

    : : SendMessage( hWndComboBox, CB_ADDSTRI NG,

    0, ( LPARAM) "Bl ue") ; : : SendMessage( hWndComboBox, CB_ADDSTRI NG, 0, ( LPARAM) "Red" ) ;

    bHandl ed = TRUE; return 0;

    }

    LRESULT OnCol or Change( WORD wNot i f yCode, WORD wI D, HWND hWndCt l , BOOL&bHandl ed) {

    / / TODO : Add Code f or cont r ol not i f i cat i on handl er . Set Di r t y(TRUE) ; return 0;

    }

    LRESULT OnTi mesToRol l Change( WORD wNot i f yCode, WORD wI D, HWND hWndCt l , BOOL&bHandl ed) {

    / / TODO : Add Code f or cont r ol not i f i cat i on handl er . Set Di r t y(TRUE) ; return 0;

    }

    Manually add the Show( ) and Set Obj ect s( ) methods. Take note that if you use different interface name forthe ATL object name for example, you have to change the related codes accordingly.

    STDMETHOD( Show) ( UI NT nCmdShow) {

    HRESULT hr ; USES_ CONVERSI ON;

    i f( nCmdShow == SW_SHOW | | nCmdShow == SW_SHOWNORMAL) {f or ( UI NT i = 0; i < m_nObj ect s; i ++) {

    CComQI Pt r pmyat l di ceob(m_ppUnk[ i ] ) ; shor t nCol or = 0;

    i f FAI LED( pmyat l di ceob- >get _Di ceCol or ( &nCol or ) ) {

  • 8/11/2019 Visual c Plus Mfc 29

    39/70

    CComPt r pEr r or ; CComBSTR st r Er r or ; Get Er r or I nf o( 0, &pEr r or ) ; pEr r or - >Get Descri pt i on( &st r Er r or ) ; MessageBox( OLE2T( st r Er r or ) , _T(" Er r or ") ,MB_I CONEXCLAMATI ON) ;

    return E_FAI L; }HWND hWndComboBox = Get Dl gI t em( I DC_COLOR) ; : : SendMessage( hWndComboBox,

    CB_SETCURSEL, nCol or , 0) ;

    shor t nTi mesToRol l = 0;i f FAI LED( pmyat l di ceob- >get _Ti mesToRol l ( &nTi mesToRol l ) ) {

    CComPt r pEr r or ; CComBSTR st r Er r or ; Get Er r or I nf o( 0, &pEr r or ) ; pEr r or - >Get Descri pt i on( &st r Er r or ) ; MessageBox( OLE2T( st r Er r or ) , _T(" Er r or ") ,MB_I CONEXCLAMATI ON) ; return E_FAI L;

    }Set Dl gI t emI nt ( I DC_TI MESTOROLL, nTi mesToRol l , FALSE) ;

    }}m_bDi r t y = FALSE;

    hr = I Pr opert yPageI mpl : : Show( nCmdShow) ; return hr ;

    }

    STDMETHOD( SetObj ect s) ( ULONG nObj ect s, I Unknown** ppUnk) {

    HRESULT hr = I Proper t yPageI mpl : : Set Obj ect s( nObj ect s,ppUnk) ;

    return hr ; }

    Build and make sure there is no error. Try using the dice control in Visual C++ dialog based programor VisualBasic form. The following shows the dice control using the ActiveX Control Test Container. Double click the diceor in the square area. Here, we cannot see/use the property page.

    http://www.tenouk.com/visualcplusmfc/mfcatltest29.htmlhttp://www.tenouk.com/visualcplusmfc/mfcatltest29.html
  • 8/11/2019 Visual c Plus Mfc 29

    40/70

  • 8/11/2019 Visual c Plus Mfc 29

    41/70

  • 8/11/2019 Visual c Plus Mfc 29

    42/70

    Figure 54: The Miscellaneous control properties tab on the ATL Object Wizard Properties dialog box.

    Finally, select the Stock Propertiestab if you want to give your control some stock properties. Stock properties arethose properties that you might expect any control to have, including background colors, border colors, foregroundcolors, and a caption. Figure 55 shows the Stock Properties tab.

    Figure 55: The Stock Properties tab on the ATL Object Wizard Properties dialog box.

    When you've finished selecting the attributes for the control, click OK.

    The ATL Object Wizard adds a header file and a source file defining the new control. In addition, the ATL Object

    Wizard sets aside space in the IDL file to hold the control's main interface and assigns a GUI Dto the interface.Here's the C++ definition of the control produced by the ATL Object Wizard (myatldiceob.h):

    / / myat l di ceob. h : Decl ar at i on of t he Cmyat l di ceob

    #i f ndef _ _MYATLDI CEOB_H_#def i ne _ _MYATLDI CEOB_H_

    #i ncl ude "r esour ce. h" / / mai n symbol s#i ncl ude

  • 8/11/2019 Visual c Plus Mfc 29

    43/70

    / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / Cmyat l di ceobcl ass ATL_NO_VTABLE Cmyat l di ceob :

    publ i c CComObj ect Root Ex, publ i c I Di spatchI mpl ,

    publ i c CComCont r ol , publ i c I Per si st St r eamI ni t I mpl , publ i c I Ol eCont r ol I mpl , publ i c I Ol eObj ect I mpl , publ i c I Ol eI nPl aceAct i veObj ect I mpl , publ i c I Vi ewObj ect ExI mpl , publ i c I Ol eI nPl aceObj ect Wi ndowl essI mpl , publ i c I Connect i onPoi ntCont ai ner I mpl , publ i c I Per si st St orageI mpl , publ i c I Speci f yProper t yPagesI mpl , publ i c I Qui ckAct i vateI mpl , publ i c I Dat aObj ect I mpl , publ i c I Provi deCl assI nf o2I mpl , publ i c I Propert yNot i f ySi nkCP, publ i c CComCoCl ass

    {. . . . . . . . .

    };

    #endi f / / __MYATLDI CEOB_H_

    That's a pretty long inheritance list. You've already seen the template implementations of I Unknownand supportfor class objects. They exist in CComObj ect Root Exand CComCoCl ass. You've also seen how ATLimplements I Di spat chwithin the I Di spat chI mpl template. However, for a basic control there are about 11more interfaces required to make everything work. These interfaces can be categorized into several areas as shown

    in the following table.

    Category Interface

    Interfaces for handling self-description

    I Pr ovi deCl assI nf o2

    Interfaces for handling persistenceI Per si stSt r eamI ni tI Per si stSt or age

    Interfaces for handling activationI Qui ckAct i vat e (and some ofI Ol eObj ect )

    Interfaces from the original OLEControl specification

    I Ol eCont r ol

    Interfaces from the OLE Documentspecification

    I Ol eObj ect

    Interfaces for rendering

    I Ol eI nPl aceAct i veObj ectI Vi ewObj ectI Ol eI nPl aceObj ect Wi ndowl essI DataObj ect

    Interfaces for helping the containermanage property pages

    I Speci f yPr oper t yPages

    Interfaces for handling connectionsI Pr oper t yNot i f ySi nkCPI Connect i onPoi nt Cont ai ner

    Table 2.

    These are by and large boilerplate interfaces, ones that a COM class must implement to qualify as an ActiveXcontrol. Most of the implementations are standard and vary only slightly (if at all) from one control to the next. Thebeauty of ATL is that it implements this standard behavior and gives you programmatic hooks where you can plug

    in your custom code. That way, you don't have to burn your eyes out by looking directly at the COM code. You can

  • 8/11/2019 Visual c Plus Mfc 29

    44/70

    live a full and rich life without understanding exactly how these interfaces work. However, if you want to knowmore about the internal workings of ActiveX Controls, be sure to check out these books: Inside OLE by KraigBrockschmidt (Microsoft Press, 1995), ActiveX Controls Inside Out by Adam Denning (Microsoft Press, 1997), andDesigning and Using ActiveX Controls by Tom Armstrong (IDG Books Worldwide, 1997).

    ATL's Control Architecture

    From the highest level, an ActiveX control has two aspects to it: its external state (what it renders on the screen) andits internal state (its properties). Once an ActiveX control is hosted by some sort of container (such as a MicrosoftVisual Basic form or an MFC dialog box), it maintains a symbiotic relationship with that container. The client code

    talks to the control through incoming COM interfaces such as I Di spat chand OLE Documentinterfaces likeI Ol eObj ect and I DataObj ect .The control also has the opportunity to talk back to the client. One method of implementing this two-way

    communication is for the client to implement an I Di spat chinterface to represent the control's event set. Thecontainer maintains a set of properties called ambient propertiesthat the control can use to find out about its host.For instance, a control can camouflage itself within the container because the container makes the information

    stored in these properties available through a specifically named I Di spat chinterface. The container canimplement an interface named I Pr oper t yNot i f ySi nkto find out when the properties within a control mightchange. Finally, the container implements I Ol eCl i ent Si t eand I Ol eCont r ol Si t eas part of the control-embedding protocol.The interfaces listed allow the client and the object to exhibit the behaviors expected of an ActiveX control. We'lltackle some of these interfaces as we go along. The best place to begin looking at ATL-based controls is theCComControl class and its base classes.

    CComControl

    You can find the definition of CComCont r ol in Microsoft's ATLCTL.Hfile under ATL's Includedirectory.

    CComCont r ol is a template class that takes a single class parameter:

    t empl ate cl ass ATL_NO_VTABLE CComCont r ol : publ i c CComCont r ol Base,

    publ i c CWi ndowI mpl {

    . . .

    . . . };

    CComCont r ol is a rather lightweight class that does little by itself; it derives functionality fromCComCont r ol Baseand CWi ndowI mpl . CComCont r ol expects the template parameter to be an ATL-basedCOM object derived from CComObj ect Root Ex. CComCont r ol requires the template parameter for variousreasons, the primary reason being that from time to time the control class uses the template parameter to call back to

    the control's I nt er nal Quer yI nt er f ace( ) .CComCont r ol implements several functions that make it easy for the control to call back to the client. Forexample, CComCont r ol implements a function named Fi r eOnRequest Edi t ( ) to give controls the ability to

    tell the client that a specified property is about to change. This function calls back to the client through the client-implemented interface I Pr oper t yNot i f ySi nk. Fi r eOnRequest Edi t ( ) notifies all connectedI Pr oper t yNot i f ySi nkinterfaces that the property specified by a certain DI SPI Dis about to change.CComCont r ol also implements the Fi r eOnChanged( ) function. Fi r eOnChanged( ) is very much likeFi r eOnRequest Edi t ( ) in that it calls back to the client through the I Pr oper t yNot i f ySi nkinterface. Thisfunction tells the clients of the control (all clients connected to the control through I Pr oper t yNot i f ySi nk) thata property specified by a certain DI SPI Dhas already changed.In addition to mapping the I Pr oper t yNot i f ySi nkinterface to some more easily understood functions,CComCont r ol implements a function named Cont r ol Quer yI nt er f ace( ) , which simply forwards on to thecontrol's I Unknowninterface. This is how you can get a control's IUnknown interface from inside the control.Finally, CComCont r ol implements a function named Cr eat eCont r ol Wi ndow( ) . The default behavior forthis function is to call CWi ndowI mpl : : Cr eat e. Notice that CComControl also derives from CWi ndowI mpl . If

    you want to, you can override this function to do something other than create a single window. For example, youmight want to create multiple windows for your control.

  • 8/11/2019 Visual c Plus Mfc 29

    45/70

    Most of the real functionality for CComCont r ol exists within those two other classes, CComCont r ol BaseandCWi ndowI mpl . Let's take a look at those classes now.

    CComControlBase

    CComCont r ol Baseis a much more substantial class than CComCont r ol . To begin with, CComCont r ol Base

    maintains all the pointers used by the control to talk back to the client. CComCont r ol Baseuses ATL's CComPt r smart pointer to include member variables that wrap the following interfaces implemented for calling back to theclient:

    A wrapper for I Ol eI nPl aceSi t e(m_spI nPl aceSi t e).

    An advise holder for the client's data advise sink (m_spDat aAdvi seHol der ).

    An OLE advise holder for the client's OLE advise sink (m_spOl eAdvi seHol der ).

    A wrapper for I Ol eCl i ent Si t e(m_spCl i ent Si t e). A wrapper for I Advi seSi nk(m_spAdvi seSi nk).

    CComCont r ol Basealso uses ATL's CComDi spat chDr i ver to wrap the client's dispatch interface forexposing its ambient properties.

    CComCont r ol Baseis also where you'll find the member variables that contain the control's sizing andpositioning information: m_s i zeNat ural , m_si zeExt ent , and m_r cPos . The other important data memberwithin CComCont r ol Baseis the control's window handle. Most ActiveX controls are UI gadgets and as suchmaintain a window. CWi ndowI mpl and CWi ndowI mpl Basehandle the windowing aspects of an ATL-basedActiveX control.

    CWindowImpland CWindowImplBase

    CWi ndowI mpl derives from CWi ndowI mpl Base, which in turn derives from CWi ndowand CMessageMap.As a template class, CWi ndowI mpl takes a single parameter upon instantiation. The template parameter is thecontrol being created. CWi ndowI mpl needs the control type because CWi ndowI mpl calls back to the controlduring window creation. Let's take a closer look at how ATL handles windowing.

    ATL Windowing

    Just as CComCont r ol is relatively lightweight (most work happens in CComCont r ol Base), CWi ndowI mpl isalso relatively lightweight. CWi ndowI mpl more or less handles only window creation. In fact, that's the onlyfunction explicitly defined by CWi ndowI mpl . CWi ndowI mpl : : Cr eat ecreates a new window based on thewindow class information managed by a class named_ATLWNDCLASSI NFO. There's an ASCII character versionand a wide character version.

    struct _ATL_ WNDCLASSI NFOA{

    WNDCLASSEXA m_wc; LPCSTR m_l pszOr i gName;

    WNDPROC pWndPr oc; LPCSTR m_l pszCursor I D; BOOL m_bSyst emCur sor ; ATOM m_at om; CHAR m_szAut oName[ 13] ; ATOM Regi st er ( WNDPROC* p) {

    return At l Modul eRegi st erWndCl assI nf oA( &_Modul e, t hi s, p ) ; }

    };

    struct _ATL_ WNDCLASSI NFOW{

    WNDCLASSEXW m_wc; LPCWSTR m_l pszOr i gName; WNDPROC pWndPr oc;

  • 8/11/2019 Visual c Plus Mfc 29

    46/70

    LPCWSTR m_l pszCur sor I D; BOOL m_bSyst emCur sor ; ATOM m_at om; WCHAR m_szAut oName[ 13] ; ATOM Regi st er ( WNDPROC* p) {

    return At l Modul eRegi st erWndCl assI nf oW( &_Modul e, t hi s, p ) ; }};

    Then ATL uses typedefs to alias this structure to a single class named CWndCl assI nf o:

    t ypedef _ATL_WNDCLASSI NFOA CWndCl assI nf oA; t ypedef _ATL_WNDCLASSI NFOW CWndCl assI nf oW; #i f def UNI CODE#def i ne CWndCl assI nf o CWndCl assI nf oW#el se#def i ne CWndCl assI nf o CWndCl assI nf oA#endi f

    CWi ndowI mpl uses a macro named DECLARE_WND_CLASSto add window class information to aCWi ndowI mpl -derived class. DECLARE_WND_CLASSalso adds a function named GetWndCl assI nf o( ) .Here's the DECLARE_WND_CLASSmacro:

    #def i ne DECLARE_WND_CLASS( WndCl assName) \ st at i c CWndCl assI nf o& Get WndCl assI nf o( ) \ { \

    st at i c CWndCl ass I nf o wc = \ { \

    { si zeof( WNDCLASSEX) , CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS, \St art Wi ndowProc, \ 0, 0, NULL, NULL, NULL, ( HBRUSH) ( COLOR_WI NDOW + 1) , \

    NULL, WndCl assName, NULL }, \ NULL, NULL, I DC_ARROW, TRUE, 0, _T( "" ) \ }; \ return wc; \

    }

    This macro expands to provide a CWndCl assI nf ostructure for the control class. Because CWndCl assI nf omanages the information for a single window class, each window created through a specific instance of

    CWi ndowI mpl will be based on the same window class.CWi ndowI mpl derives from CWi ndowI mpl BaseT. CWi ndowI mpl BaseTderives fromCWi ndowI mpl Root , which is specialized around the CWi ndowclass and the CCont r ol Wi nTrai t sclasseslike this:

    t empl ate cl ass ATL_NO_VTABLE CWi ndowI mpl BaseT : publ i c CWi ndowI mpl Root < TBase >{publ i c:

    . . .

    . . .

    . . . };

    CWi ndowI mpl Root derives from CWi ndow(by default) and CMessageMap. CWi ndowI mpl BaseTmanagesthe window procedure of a CWi ndowI mpl -derived class. CWi ndowis a lightweight class that wraps windowhandles in the same way (but not as extensively) as MFC's CWndclass. CMessageMapis a tiny class that defines asingle pure virtual function named Pr ocessWi ndowMessage( ) . ATL-based message-mapping machineryassumes this function is available, so ATL-based classes that want to use message maps need to derive from

    CMessageMap. Let's take a quick look at ATL message maps.

  • 8/11/2019 Visual c Plus Mfc 29

    47/70

    ATL Message Maps

    The root of ATL's message mapping machinery lies within the CMessageMapclass. ATL-based controls exposemessage maps by virtue of indirectly deriving from CWi ndowI mpl Base. In MFC, by contrast, deriving fromCCmdTar get enables message mapping. However, just as in MFC, it's not enough to derive from a class that

    supports message maps. The message maps actually have to be there, and those message maps are implemented viamacros.

    To implement a message map in an ATL-based control, use message map macros. First ATL's BEGI N_MSG_MAPmacro goes into the control class's header file. BEGI N_MSG_MAPmarks the beginning of the default message map.CWi ndowI mpl : : Wi ndowPr ocuses this default message map to process messages sent to the window. Themessage map directs messages either to the appropriate handler function or to another message map. ATL defines

    another macro named END_MSG_MAPto mark the end of a message map. Between BEGI N_MSG_MAP andEND_MSG_MAPlie some other macros for mapping window messages to member functions in the control. Forexample, here's a typical message map you might find in an ATL-based control:

    BEGI N_MSG_MAP( CAFul l Contr ol ) CHAI N_MSG_MAP( CComCont r ol ) DEFAULT_REFLECTI ON_HANDLER( ) MESSAGE_HANDLER(WM_TI MER, OnTi mer ) ; MESSAGE_HANDLER( WM_LBUTTONDOWN, OnLBut t on) ;

    END_MSG_MAP( )

    This message map delegates most of the message processing to the control through the CHAI N_MSG_MAPmacroand handles message reflection through the DEFAULT_REFLECTI ON_HANDLERmacro. The message map alsohandles two window messages explicitly: WM_TI MERand WM_LBUTTONDOWN. These are standard windowmessages that are mapped using the MESSAGE_HANDLERmacro. The macros simply produce a table relatingwindow messages to member functions in the class. In addition to regular messages, message maps are capable ofhandling other sorts of events. Here's a rundown of the kinds of macros that can go in a message map.

    Macro DescriptionMESSAGE_HANDLER Maps a Windows message to a handler function.MESSAGE_RANGE_HANDLER Maps a contiguous range of Windows messages to a handler function.

    COMMAND_HANDLERMaps a WM_COMMANDmessage to a handler function, based on theidentifier and the notification code of the menu item, control, oraccelerator.

    COMMAND_I D_HANDLERMaps a WM_COMMANDmessage to a handler function, based on theidentifier of the menu item, control, or accelerator.

    COMMAND_CODE_HANDLERMaps a WM_COMMANDmessage to a handler function, based on thenotification code.

    COMMAND_RANGE_HANDLERMaps a contiguous range of WM_COMMANDmessages to a handlerfunction, based on the identifier of the menu item, control, or accelerator.

    NOTI FY_HANDLER Maps a WM_NOTI FYmessage to a handler function, based on thenotification code and the control identifier.

    NOTI FY_ I D_HANDLERMaps a WM_NOTI FYmessage to a handler function, based on the controlidentifier.

    NOTI FY_CODE_HANDLERMaps a WM_NOTI FYmessage to a handler function, based on thenotification code.

    NOTI FY_RANGE_HANDLERMaps a contiguous range of WM_NOTI FYmessages to a handler function,based on the control identifier.

    Table 3.

    Handling messages within ATL works much the same as in MFC. ATL includes a single window procedure throughwhich messages are routed. Technically, you can build your controls effectively without understanding everything

    about ATL's control architecture. However, this knowledge is sometimes helpful as you develop a control, and it'seven more useful when debugging a control.

  • 8/11/2019 Visual c Plus Mfc 29

    48/70

    Developing the Control

    Once the control is inserted into the server, you need to add some code to make the control do something. If youwere to compile and load ATL's default control into a container, the results wouldn't be particularly interesting.

    You'd simply see a blank rectangle with the string "ATL 3. 0: Myat l di ceob." You'll want to add code to

    render the control, to represent the internal state of the control, to respond to events, and to generate events to sendback to the container.

    Deciding What to Draw

    A good place to start working on a control is on its drawing code, you get instant gratification that way. This is acontrol that is visually represented by a couple of dice. The easiest way to render to the dice control is to drawbitmaps representing each of the six possible dice sides and then show the bitmaps on the screen. This implies thatthe dice control will maintain some variables to represent its state. For example, the control needs to manage thebitmaps for representing the dice as well as two numbers representing the first value shown by each die. Here is the

    code from MYATLDICEOBJ.Hthat represents the state of the dice:

    #def i ne MAX_DI EFACES 6

    . . .

    . . .

    . . . HBI TMAP m_di eBi t maps[ MAX_DI EFACES] ; unsi gned shor t m_nFi r st Di eVal ue; unsi gned shor t m_nSecondDi eVal ue;

    Before diving headfirst into the control's drawing code, you need to do a bit of preliminary work; the bitmaps need

    to be loaded. Presumably each die rendered by the dice control will show any one of six dice faces, so the controlneeds one bitmap for each face. Figure 56 shows what one of the dice bitmaps looks like.

    Figure 56: A bitmap for the dice control.

    If you draw the bitmaps one at a time, they'll have sequential identifiers in the resource.hfile. Giving the bitmapssequential identifiers will make them easier to load. Otherwise, you might need to modify the resource.hfile, whichcontains the following identifiers:

    #def i ne I DB_DI CE1 207#def i ne I DB_DI CE2 208#def i ne I DB_DI CE3 209#def i ne I DB_DI CE4 210#def i ne I DB_DI CE5 211#def i ne I DB_DI CE6 212

    Loading bitmaps is fairly straightforward. Cycle through the bitmap array, and load the bitmap resources. Whenthey're stored in an array like this, grabbing the bitmap out of the array and showing it is much easier than if youdidn't use an array. Here is the function that loads the bitmaps into the array:

    BOOL Cmyat l di ceob: : LoadBi t maps( ) {

    BOOL bSuccess = TRUE;

    f or ( i nt i =0; i

  • 8/11/2019 Visual c Plus Mfc 29

    49/70

    : : MessageBox( NULL,"Fai l ed t o l oad bi t maps" , NULL,MB_OK) ; bSuccess = FALSE;

    }

    }r eturn bSuccess; }

    The best place to call LoadBi t maps( ) is from within the control's constructor, as shown in the following code.To simulate a random roll of the dice, set the control's state so that the first and second die values are randomnumbers between 0 and 5 (these numbers will be used when the dice control is drawn):

    cl ass Cmyat l di ceob : / / bi g i nher i t ance l i s t {

    Cmyat l di ceob( ) {

    LoadBi t maps( ) ;

    sr and( ( unsi gned) t i me( NULL) ) ;m_nFi r st Di eVal ue = ( r and( ) % ( MAX_DI EFACES) ) + 1; m_nSecondDi eVal ue = ( r and( ) % ( MAX_DI EFACES) ) + 1;

    }

    Once the bitmaps are loaded, you'll want to render them. The dice control should include a function for showingeach die face based on the current internal state of the dice. Here's where you first encounter ATL's drawingmachinery.One of the most convenient things about ATL-based controls (and MFC-based controls) is that all the drawing code

    happens in one place: within the control's OnDr aw( ) function. OnDr aw( ) is a virtual function ofCOl eCont r ol Base. Here's OnDr aw( ) 's signature:

    vi r t ual HRESULT OnDr aw( ATL_DRAWI NFO& di ) ;

    OnDr aw( ) takes a single parameter: a pointer to an ATL_DRAWI NFO structure. Among other things, theATL_DRAWI NFO structure contains a device context on which to render your control. Here's the ATL_DRAWI NFOstructure:

    st r uct ATL_DRAWI NFO {UI NT cbSi ze; DWORD dwDr awAspect ; LONG l i ndex; DVTARGETDEVI CE* pt d; HDC hi cTarget Dev;HDC hdcDr aw; LPCRECTL pr cBounds; / / Rect angl e i n whi ch t o dr aw

    LPCRECTL pr cWBounds; / / Wi ndowOr g and Ext i f metaf i l eBOOL bOpt i mi ze; BOOL bZoomed; BOOL bRect I nHi met r i c; SI ZEL ZoomNum; / / ZoomX = ZoomNum. cx/ ZoomNum. cySI ZEL ZoomDen;

    };

    As you can see, there's a lot more information here than a simple device context. While you can count on theframework filling it out correctly for you, it's good to know where the information in the structure comes from and

    how it fits into the picture.ActiveX Controls are interesting because they are drawn in two contexts. The firstand most obvious context is

    when the control is active and it draws within the actual drawing space of the client. The other, less-obvious context

    in which controls are drawn is during design time(as when an ActiveX control resides in a Visual Basic form indesign mode). In the first context, ActiveX Controls render themselves to a live screen device context. In the secondcontext, ActiveX Controls render themselves to a metafile device context.

  • 8/11/2019 Visual c Plus Mfc 29

    50/70

    Many (though not all) ATL-based controls are composed of at least one window. So ActiveX Controls need to

    render themselves during the WM_PAI NTmessage. Once the control receives the WM_PAI NTmessage, the messagerouting architecture passes control to CComCont r ol Base: : OnPai nt . Remember, CComCont r ol Baseis oneof the control's base classes. CComCont r ol Base: : OnPai nt performs several steps. The function begins bycreating a painting device context (using Begi nPai nt ( ) ). Then OnPai nt ( ) creates an ATL_DRAWI NFOstructure on the stack and initializes the fields within the structure. OnPai nt ( ) sets up ATL_DRAWI NFO to show

    the entire content (the dwDr awAspect field is set to DVASPECT_CONTENT). OnPai nt ( ) also sets the l i ndexfield to_1, sets the drawing device context to the newly created painting device context, and sets up the boundingrectangle to be the client area of the control's window. Then OnPai nt ( ) goes on to call OnDr awAdvanced( ) .The default OnDr awAdvanced( ) function prepares a normalized device context for drawing. You can overridethis method if you want to use the device context passed by the container without normalizing it. ATL then calls

    your control class's OnDr aw( ) method.The second context in which the OnDr aw( ) function is called is when the control draws on to a metafile. Thecontrol draws itself on to a metafile whenever someone calls I Vi ewObj ect Ex: : Dr aw. I Vi ewObj ect Exisone of the interfaces implemented by the ActiveX control. ATL implements the I Vi ewObj ect Exinterfacethrough the template class I Vi ewObj ect ExI mpl . I Vi ewObj ect ExI mpl : : Dr awis called whenever thecontrol needs to take a snapshot of its presentation space for the container to store. In this case, the container creates

    a metafile device context and hands it to the control. I Vi ewObj ect ExI mpl puts an ATL_DRAWI NFO structure

    on the stack and initializes. The bounding rectangle, the index, the drawing aspect, and the device contexts are allpassed in as parameters by the client. The rest of the drawing is the same in this case, the control calls

    OnDr awAdvanced( ) , which in turn calls your version of OnDr aw( ) .Once you're armed with this knowledge, writing functions to render the bitmaps becomes fairly straightforward. Toshow the first die face, create a memory-based device context, select the object into the device context, and BitBltthe memory device context into the real device context. Here's the code:

    voi d Cmyat l di ceob: : ShowFi r st Di eFace( ATL_DRAWI NFO& di ) {

    BI TMAP bmI nf o; Get Obj ect ( m_di eBi t maps[ m_nFi r st Di eVal ue- 1] , si zeof ( bmI nf o),

    &bmI nf o) ;

    SI ZE si ze;

    si ze. cx = bmI nf o. bmWi dth; si ze. cy = bmI nf o. bmHei ght ;

    HDC hMemDC; hMemDC = Cr eat eCompat i bl eDC( di . hdcDr aw) ;

    HBI TMAP hOl dBi t map; HBI TMAP hbm= m_di eBi t maps[ m_nFi r st Di eVal ue- 1] ; hOl dBi t map = ( HBI TMAP) Sel ect Obj ect ( hMemDC, hbm) ;

    i f ( hOl dBi t map == NULL)

    r et ur n; / / destr uctor s wi l l c l ean up

    Bi t Bl t ( di . hdcDr aw, di . pr cBounds- >l ef t +1, di . prcBounds- >t op+1, si ze. cx, si ze. cy, hMemDC, 0, 0, SRCCOPY) ;

    Sel ect Obj ect ( di . hdcDr aw, hOl dBi t map) ; Del et eDC(hMemDC) ;

    }

  • 8/11/2019 Visual c Plus Mfc 29

    51/70

    Showing the second die face is more or less the same process; just make sure that the dice are represented

    separately. For example, you probably want to change the call to Bi t Bl t ( ) so that the two dice bitmaps are shownside by side.

    voi d Cmyat l di ceob: : ShowSecondDi eFace(ATL_DRAWI NFO& di ) {

    / / Thi s code i s exact l y t he same as ShowFi r st Di eFace/ / except t he second di e i s posi t i oned next t o t he f i r st di e. Bi t Bl t ( di . hdcDr aw,

    di . pr cBounds- >l ef t +si ze. cx + 2, di . prcBounds- >t op+1, si ze. cx, si ze. cy, hMemDC, 0, 0, SRCCOPY) ;

    / / The r est i s t he same as i n ShowFi r st Di eFace}

    The last step is to call these two functions whenever the control is asked to render itself, during the control's

    OnDr aw( ) function. ShowFi r st Di eFace( ) and ShowSecondDi eFace( ) will show the correct bitmapbased on the state of m_nFi r st Di eVal ueand m_nSecondDi eVal ue:

    HRESULT OnDr aw( ATL_ DRAWI NFO& di ) {

    RECT& r c = *( RECT*) di . prcBounds;

    ShowFi r st Di eFace( di ) ; ShowSecondDi eFace( di ) ; return S_OK;

    }

    At this point, if you compile and load this control into some ActiveX Control container (like a Visual Basic form or

    an MFC-based dialog), you'll see two die faces staring back at you. Now it's time to add some code to enliven thecontrol and roll the dice.

    Responding to Window Messages

    Just looking at two dice faces isn't that much fun. You want to make the dice work. A good way to get the dice toappear to jiggle is to use a timer to generate events and then respond to the timer by showing a new pair of dice

    faces. Setting up a Windows timer in the control means adding a function to handle the timer message and adding a

    macro to the control's message map. Start by using ClassView to add a handler for WM_TI MER. Right-click on theCmyat l di ceobsymbol in ClassView, and select Add Windows Message Handlerfrom the context menu. Thisadds a prototype for the OnTi mer ( ) function and an entry into the message map to handle the WM_TI MERmessage. Add some code to the OnTi mer ( ) function to handle the WM_TI MERmessage. The OnTi mer ( ) function should look like the code shown below.

    LRESULT Cmyat l di ceob: : OnTi mer ( UI NT msg, WPARAM wParam, LPARAM l Param,BOOL& bHandl ed) {

    i f( m_nTi mesRol l ed > 15) {

    m_nTi mesRol l ed = 0; Ki l l Ti mer ( 1) ;

    } el se {m_nFi r st Di eVal ue = ( r and( ) % ( MAX_DI EFACES) ) + 1; m_nSecondDi eVal ue = ( r and( ) % ( MAX_DI EFACES) ) + 1; Fi r eVi ewChange( ) ;

    m_nTi mesRol l ed++; }

  • 8/11/2019 Visual c Plus Mfc 29

    52/70

    bHandl ed = TRUE; return 0;

    }

    This function responds to the timer message by generating two random numbers, setting up the control's state to

    reflect these two new numbers, and then asking the control to refresh itself by calling Fi r eVi ewChange( ) .

    Notice the function kills the timer as soon as the dice have rolled a certain number of times. Also notice that themessage handler tells the framework that it successfully handled the function by setting the bHandl edvariable toTRUE.Notice there's an entry for WM_TI MERin the control's message map. Because WM_TI MERis just a plain vanillawindow message, it's represented with a standard MESSAGE_HANDLERmacro as follows:

    BEGI N_MSG_MAP(Cmyat l di ceob) CHAI N_MSG_MAP( CComCont r ol ) DEFAULT_REFLECTI ON_HANDLER( ) MESSAGE_HANDLER(WM_TI MER, OnTi mer ) ;

    END_MSG_MAP( )

    As you can tell from this message map, the dice control already handles the gamut of Windows messages through

    the CHAI N_MSG_MAPmacro. However, now the pair of dice has the ability to simulate rolling by responding to thetimer message. Setting a timer causes the control to repaint itself with a new pair of dice numbers every quarter of asecond or so. Of course, there needs to be some way to start the dice rolling. Because this is an ActiveX control, it'sreasonable to allow client code to start rolling the dice via a call to a function in one of its incoming interfaces. Use

    ClassView to add a Rol l Di ce( ) function to the main interface. Do this by right-clicking on theI Myat l di ceobj interface appearing in ClassView on the left side of the screen and selecting Add Methodfromthe pop up menu. Then add a Rol l Di ce( ) function. Microsoft Visual C++ adds a function named Rol l Di ce( ) to your control. Implement Rol l Di ce( ) by setting the timer for a reasonably short interval and then returningS_OK. Add the following code:

    STDMETHODI MP Cmyat l di ceob: : Rol l Di ce( ) {

    SetTi mer ( 1, 250) ; r et urn S_OK;

    }

    If you load the dice into an ActiveX control container, you'll now be able to browse and call the control's methods

    and roll the dice.In addition to using the incoming interface to roll the dice, the user might reasonably expect to roll the dice bydouble-clicking the control. To enable this behavior, just add a message handler to trap the mouse-button-downmessage by adding a function to handle a left-mouse double click.

    LRESULT Cmyat l di ceob: : OnLButt onDbl Cl i ck( UI NT uMsg,WPARAM wPar am,LPARAM l Par am,BOOL& bHandl ed)

    {Rol l Di ce( ) ; bHandl ed = TRUE; r et ur n 0;

    }

    Then be sure you add an entry to the message map to handle the WM_LBUTTONDOWNmessage:

    BEGI N_MSG_MAP(Cmyat l di ceob) / / . . . / / Ot her message handl ers / / . . . MESSAGE_HANDLER( WM_LBUTTONDBLCLK, OnLBut t onDbl Cl i ck)

    END_MSG_MAP( )

  • 8/11/2019 Visual c Plus Mfc 29

    53/70

    When you load the dice control into a container and double-click on it, you should see the dice roll. Now that you'veadded rendering code and given the control the ability to roll, it's time to add some properties.

    Adding Properties and Property Pages

    You've just seen that ActiveX controls have an external presentation state. The presentation state is the state

    reflected when the control draws itself. In addition, most ActiveX controls also have an internal state. The control'sinternal state is a set of variables exposed to the outside world via interface functions. These internal variables arealso known as properties.For example, imagine a simple grid implemented as an ActiveX control. The grid has an external presentation stateand a set of internal variables for describing the state of the grid. The properties of a grid control would probably

    include the number of rows in the grid, the number of columns in the grid, the color of the lines composing the grid,the type of font used, and so forth.

    As you saw in Module 28, adding properties to an ATL-based class means adding member variables to the class andthen using ClassWizard to create get and put functions to access these properties. For example, two membervariables that you might add to the dice control include the dice color and the number of times the dice are supposedto roll before stopping. Those two properties could easily be represented as a pair of short integers as shown here:

    cl ass ATL_NO_VTABLE Cmyat l di ceob :

    . . . . . . {

    . . .

    . . . shor t m_nDi ceCol or ; shor t m_nTi mesToRol l ;. . . . . .

    };

    To make these properties accessible to the client, you need to add get and put functions to the control. Right-clicking on the interface symbol in ClassView brings up a context menu, giving you a choice to Add Property,

    which will present you with the option of adding these functions. Adding Di ceCol or ( ) andTi mesToRol l ( ) properties to the control using ClassView will add four new functions to the control: get _Di ceCol or ( ) ,put _Di ceCol or ( ) , get _Ti mesToRol l ( ) , and put _Ti mesToRol l ( ) . The get _Di ceCol or ( ) function should retrieve the state of m_nDi ceCol or :

    STDMETHODI MP Cmyat l di ceob: : get _Di ceCol or ( shor t * pVal ) {

    *pVal = m_nDi ceCol or ; r et urn S_OK;

    }

    To make the control interesting, put _Di ceCol or ( ) should change the colors of the dice bitmaps and redraw thecontrol immediately. This example uses red and blue dice as well as the original black and white dice. To make the

    control show the new color bitmaps immediately after the client sets the dice color, the put _Di ceCol or ( ) function should load the new bitmaps according to new color, and redraw the control:

    STDMETHODI MP Cmyat l di ceob: : put _Di ceCol or ( shor t newVal ) {

    i f ( newVal < 3 && newVal >= 0) m_nDi ceCol or = newVal ;

    LoadBi t maps( ) ; Fi r eVi ewChange( ) ; r et urn S_OK;

    }

    Of course, this means that LoadBi t maps( ) needs to load the bitmaps based on the state of m_nDi ceCol or , sowe need to add the following code to our existing LoadBi t maps( ) function:

    http://www.tenouk.com/visualcplusmfc/visualcplusmfc28.htmlhttp://www.tenouk.com/visualcplusmfc/visualcplusmfc28.html
  • 8/11/2019 Visual c Plus Mfc 29

    54/70

    BOOL Cmyat l di ceob: : LoadBi t maps( ) {

    i nt i ; BOOL bSuccess = TRUE; i nt nI D = I DB_DI CE1;

    swi t ch(m_nDi ceCol or ) {case 0:

    nI D = I DB_DI CE1; break;

    case 1: nI D = I DB_BLUEDI CE1; break;

    case 2: nI D = I DB_REDDI CE1; break;

    }

    f or ( i =0; i m_nTi mesToRol l )

  • 8/11/2019 Visual c Plus Mfc 29

    55/70

    {m_nTi mesRol l ed = 0; Ki l l Ti mer ( 1) ; Fi r e_Di ceRol l ed( m_nFi r st Di eVal ue, m_nSecondDi eVal ue) ; i f ( m_nFi r st Di eVal ue == m_nSecondDi eVal ue)

    Fi r e_Doubl es( m_nFi r st Di eVal ue) ;

    i f ( m_nFi r st Di eVal ue == 1 && m_nSecondDi eVal ue == 1) Fi r e_SnakeEyes( ) ; } el se {

    m_nFi r st Di eVal ue = ( r and( ) % ( MAX_DI EFACES) ) + 1; m_nSecondDi eVal ue = ( r and( ) % ( MAX_DI EFACES) ) + 1;

    Fi r eVi ewChange( ) ; m_nTi mesRol l ed++;

    }

    bHandl ed = TRUE; r et ur n 0;

    }

    Now these two properties are exposed to the outside world. When the client code changes the color of the dice, thecontrol loads a new set of bitmaps and redraws the control with the new dice faces. When the client code changes

    the number of times to roll, the dice control uses that information to determine the number of times the dice control

    should respond to the WM_TI MERmessage. So the next question is, "How are these properties accessed by the clientcode?" One way is through a control's property pages.

    Property Pages

    Since ActiveX controls are usually UI gadgets meant to be mixed into much larger applications, they often find theirhomes within places such as Visual Basic forms and MFC form views and dialogs. When a control is instantiated,the client code can usually reach into the control and manipulate its properties by calling certain functions on the

    control's incoming interface functions. However, when an ActiveX control is in design mode, accessing theproperties through the interface functions isn't always practical. It would be unkind to tool developers to force them

    to go through the interface functions all the time just to tweak some properties in the control. Why should the toolvendor who is creating the client have to provide UI for managing control properties? That's what property pages arefor. Property pages are sets of dialogs implemented by the control for manipulating properties. That way, the toolvendors don't have to keep re-creating dialog boxes for tweaking the properties of an ActiveX control.

    How Property Pages Are Used. Property pages are usually used in one of two ways. The first way is through the

    control's I Ol eObj ect interface. The client can call I Ol eObj ect 's DoVerb( ) function, passing in theproperties verb identifier (named OLEI VERB_PROPERTI ESand defined as the number -7) to ask the control toshow its property pages. The control then displays a dialog, or property frame, that contains all the control's propertypages. For example, Figure 57 shows the Property Pagesdialog containing the property pages for the MicrosoftFlexGrid 6.0 control.

  • 8/11/2019 Visual c Plus Mfc 29

    56/70

  • 8/11/2019 Visual c Plus Mfc 29

    57/70

    Figure 58: Microsoft Visual C++ inserting the Microsoft FlexGrid 6.0 property pages into its own dialog box for

    editing resource properties.

    For a property page to work correctly, the control that the property page is associated with needs to implement

    I Speci f yPr oper t yPages and the property page object needs to implement an interface named

    I Pr oper t yPage. With this in mind, let's examine exactly how ATL implements property pages.

    Adding a Property Page to Your Control. You can use the Visual Studio ATL Object Wizardto create propertypages in your ATL project. To create a property page, perform the following steps:

    1. Select New ATL Objectfrom the Visual C++ Insertmenu.2. From the ATL Object Wizarddialog, select Controlsfrom the Categorylist.3. Select Property Pagefrom the Objectslist.

    4. Click Next.5. Fill in the required information on the ATL Object Wizard Propertiesdialog, and click OK.

    ATL's Object Wizard generates a dialog template and includes it as part of a control's resources. In the dice controlexample, the two properties you're concerned with are the color of the diceand the number of times to roll the

    dice. The dialog template created by ATL's Object Wizard is blank, so you'll want to add a couple of controls torepresent these properties. In this example, the user will be able to select the dice color from a combo boxand enterthe number of times the dice should roll in an edit control, as shown in Figure 59.

    Figure 59. The property page dialog template.

    The ATL Object Wizard also creates a C++ class for you that implement the interface necessary for the class tobehave as a property page. In addition to generating this C++ class, the ATL Object Wizard makes the class part ofthe project. The ATL Object Wizard adds the new property page class to the IDL file within the coclass section. In

    addition, the ATL Object Wizard appends the property page to the object map so that Dl l Get Cl assObj ect ( ) can find the property page class. Finally, the ATL Object Wizard adds a new Registry scriptso that the DLL makesthe correct Registry entries when the control is registered. Here is the header file created by the ATL Object Wizard

    for a property page named Di ceMai nPropPage:

    #i ncl ude "r esour ce. h" / / mai n symbol s

    cl ass ATL_NO_VTABLE CDi ceMai nPropPage : publ i c CComObj ect Root Ex, publ i c CComCoCl ass, publ i c I Proper t yPageI mpl , publ i c CDi al ogI mpl

    {publ i c:

    CDi ceMai nPr opPage( ){

    m_dwTi t l eI D = I DS_TI TLEDi ceMai nPr opPage; m_dwHel pFi l eI D = I DS_HELPFI LEDi ceMai nPr opPage;

    m_dwDocSt r i ngI D = I DS_DOCSTRI NGDi ceMai nPropPage; }

  • 8/11/2019 Visual c Plus Mfc 29

    58/70

    enum {I DD = I DD_DI CEMAI NPROPPAGE};

    DECLARE_REGI STRY_RESOURCEI D( I DR_DI CEMAI NPROPPAGE)

    DECLARE_PROTECT_FI NAL_CONSTRUCT( )

    BEGI N_COM_MAP(CDi ceMai nPr opPage)COM_I NTERFACE_ENTRY( I Pr opert yPage)

    END_COM_MAP( )

    BEGI N_MSG_MAP(CDi ceMai nPr opPage) CHAI N_MSG_MAP( I Pr opert yPageI mpl )

    END_MSG_MAP( )

    STDMETHOD( Appl y) ( voi d) {

    ATLTRACE(_T( "CDi ceMai nPropPage: : Appl y\ n" ) ) ; f or ( UI NT i = 0; i < m_nObj ect s; i ++) {

    / / Do somet hi ng i nt er est i ng her e/ / I Ci r cCt l * pCi rc ; / / m_ppUnk[ i ] - >Quer yI nt er f ace( I I D_I Ci r cCt l , ( voi d**) &pCi r c); / / pCi r c- >put _Capt i on( CComBSTR( "somethi ng speci al " ) ) ; / / pCi r c- >Rel ease( ) ;

    }m_bDi r t y = FALSE; r et urn S_OK;

    }};

    Examining this property page listing reveals that ATL's property page classes are composed of several ATL

    templates: CComObj ect Root Ex(to implement I Unknown), CComCoCl ass (the class object for the propertypage), I Propert yPageI mpl (for implementing I Pr oper t yPage), and CDi al ogI mpl for implementing thedialog-specific behavior.As with most other COM classes created by ATL's Object Wizard, most of the code involved in getting a propertypage to work is boilerplate code. Notice that besides the constructor and some various maps, the only other functionis one named Apply.

    Before getting into the mechanics of implementing a property page, it's helpful to take a moment to understand howthe property page architecture works. The code you need to type in to get the property pages working will then makemore sense.When the client decides it's time to show some property pages, a modal dialog frame needs to be constructed. Theframe is constructed by either the client or by the control itself. If the property pages are being shown via the

    DoVerb( ) function, the control constructs the frame. If the property pages are being shown within the context ofanother application, as when Visual C++ shows the control's property pages within the IDE, the client constructs the

    dialog frame. The key to the dialog frame is that it holds property page sites (small objects that implementI Pr oper t yPageSi t e) for each property page.The client code (the modal dialog frame, in this case) then enumerates through a list of GUI Ds, callingCoCr eateI nst ance( ) on each one of them and asking for the I Pr oper t yPageinterface. If the COM objectproduced by CoCr eateI nst ance( ) is a property page, it implements the I Proper t yPageinterface. Thedialog frame uses the I Proper t yPageinterface to talk to the property page. Here's the declaration of theI Pr oper t yPageinterface:

    i nt er f ace I Proper t yPage : publ i c I Unknown{

    HRESULT SetPageSi t e( I Propert yPageSi t e *pPageSi t e) = 0; HRESULT Act i vat e( HWND hWndParent ,

    LPCRECT pRect ,

    BOOL bModal ) = 0; HRESULT Deact i vate( voi d) = 0;

  • 8/11/2019 Visual c Plus Mfc 29

    59/70

    HRESULT Get PageI nf o( PROPPAGEI NFO *pPageI nf o) = 0; HRESULT Set Obj ect s( ULONG cObj ect s,

    I Unknown **ppUnk) = 0; HRESULT Show( UI NT nCmdShow) = 0; HRESULT Move( LPCRECT pRect ) = 0; HRESULT I sPageDi r t y( voi d) = 0;

    HRESULT Appl y( voi d) = 0; HRESULT Hel p( LPCOLESTR pszHel pDi r ) = 0; HRESULT Transl at eAccel erat or ( MSG *pMsg) = 0;

    };

    Once a property page has been created, the property page and the client code need some channels to communicate

    back and forth with the control. After the property dialog frame successfully calls Quer yI nt er f ace( ) forI Pr oper t yPageon the property page objects, the frame calls I Pr oper t yPage: : Set PageSi t eon eachI Pr oper t yPageinterface pointer it holds, passing in an I Pr oper t yPageSi t einterface pointer. The propertypage sites within the property frame provide a way for each property page to call back to the frame. The property

    page site provides information to the property page and receives notifications from the page when changes occur.

    Here's the I Pr oper t yPageSi t einterface:

    i nt er f ace I Pr oper t yPageSi t e : publ i c I Unknown{

    publ i c: vi r t ual HRESULT OnStat usChange( DWORD dwFl ags) = 0; vi r t ual HRESULT GetLocal eI D( LCI D *pLocal eI D) = 0; vi r t ual HRESULT Get PageContai ner ( I Unknown *ppUnk) = 0; vi r t ual HRESULT Transl ateAccel erator ( MSG *pMsg) = 0;

    };

    In addition to the frame and control connecting to each other through I Proper t yPageandI Pr oper t yPageSi t e, each property page needs a way to talk back to the control. This is usually done when thedialog frame calls I Pr oper t yPage: : Set Obj ect s , passing in the control's I Unknown. Figure 60 illustratesthe property page architecture.

    Now that you see how ActiveX Control property pages work in general, understanding how they work within ATLwill be a lot easier. You'll see how ATL's property pages work, in cases when the client code exercises the control'sproperties verb as well as in cases when environments like Visual C++ integrate a control's property pages into theIDE.

    Figure 60: How the property pages, the property frame, and the property page sites communicate.

  • 8/11/2019 Visual c Plus Mfc 29

    60/70

    ATL and the Properties Verb. The first way in which an ActiveX control shows its property pages is when the

    client invokes the properties verb by calling I Ol eObj ect : : DoVer busing the constantOLEI VERB_PROPERTI ES. When the client calls DoVerb( ) in an ATL-based control, the call ends up in thefunction CComCont r ol Base: : DoVerbPropert i es, which simply calls Ol eCr eat ePr opert yFrame( ) ,passing in its own I Unknownpointer and the list of property page GUI Ds. Ol eCr eat ePr opert yFrame( ) takes the list of GUI Ds, calling CoCr eateI nst ance( ) on each one to create the property pages, and arranges

    them within the dialog frame. Ol eCr eat ePr opert yFrame( ) uses each property page's I Pr oper t yPageinterface to manage the property page, as described in "How Property Pages Are Used"

    ATL Property Maps. Of course, understanding how Ol eCr eat ePr opert yFrame( ) works from within theATL-based control begs the next question: where does the list of property pages actually come from? ATL usesmacros to generate lists of property pages called property maps. Whenever you add a new property page to an ATL-based control, you need to set up the list of property pages through these macros. ATL includes several macros for

    implementing property maps: BEGI N_PROPERTY_MAP, PROP_ENTRY, PROP_ENTRY_EX, PROP_ PAGE, andEND_PROPERTY_MAP. Here are those macros in the raw:

    st r uct ATL_PROPMAP_ENTRY{

    LPCOLESTR szDesc; DI SPI D di spi d; const CLSI D* pcl si dPropPage; const I I D* pi i dDi spat ch; DWORD dwOf f set Dat a; DWORD dwSi zeDat a; VARTYPE vt ;

    };

    #def i ne BEGI N_PROPERTY_MAP( t heCl ass) \ t ypedef _ATL_PROP_NOTI FY_EVENT_CLASS __ATL_PROP_NOTI FY_EVENT_CLASS;

    \ t ypedef t heCl ass _Pr opMapCl ass; \

    st at i c ATL_PROPMAP_ENTRY* Get Pr oper t yMap( ) \

    {\ st at i c ATL_PROPMAP_ENTRY pPr opMap[ ] = \

    {

    #def i ne PROP_PAGE(cl si d) \ {NULL, NULL, &cl si d, &I I D_NULL},

    #def i ne PROP_ENTRY(szDesc, di spi d, cl si d) \ {OLESTR( szDesc) , di spi d, &cl si d, &I I D_I Di spat ch},

    #def i ne PROP_ENTRY_EX( szDesc, di spi d, cl si d, i i dDi spat ch) \ {OLESTR( szDesc) , di spi d, &cl si d, &i i dDi spat ch},

    #def i ne END_PROPERTY_MAP( ) \ {NULL, 0, NULL, &I I D_NULL} \

    }; \ r eturn pPropMap; \

    }

    When you decide to add property pages to a COM class using ATL's property page macros, according to the ATLdocumentation you should put these macros into your class's header file. For example, if you want to add propertypages to the dice control, you'd add the following code to the C++ class:

    cl ass ATL_NO_VTABLE Cmyat l di ceob :. . . . . .

    . . . {

  • 8/11/2019 Visual c Plus Mfc 29

    61/70

  • 8/11/2019 Visual c Plus Mfc 29

    62/70

    publ i c I Proper t yPageI mpl , publ i c CDi al ogI mpl

    {. . . . . . . . .

    STDMETHOD( Show) ( UI NT nCmdShow ) {HRESULT hr ;

    USES_ CONVERSI ON;

    i f ( nCmdShow == SW_SHOW | | nCmdShow == SW_SHOWNORMAL) {f or ( UI NT i = 0; i < m_nObj ect s; i ++) {

    CComQI Pt r < I ATLDi eceObj , &I I D_I ATLDi eceObj >pMyat l di ceob( m_ppUnk[ i ] ) ;

    shor t nCol or = 0;

    i f FAI LED( pMyat l di ceob- >get _Di ceCol or ( &nCol or ) ) {

    CComPt r pEr r or; CComBSTR st r Er r or ; Get Er r or I nf o( 0, &pEr r or ) ; pEr r or - >Get Descr i pt i on( &st r Er r or ) ; MessageBox( OLE2T( st r Er r or ) , _T(" Er r or ") ,

    MB_I CONEXCLAMATI ON) ; r et ur n E_FAI L;

    }HWND hWndComboBox = Get Dl gI t em( I DC_COLOR) ; : : SendMessage( hWndComboBox,

    CB_SETCURSEL, nCol or , 0) ;

    shor t nTi mesToRol l = 0;i f FAI LED(

    pMyat l di ceob- >get_Ti mesToRol l ( &nTi mesToRol l ) ) {

    CComPt r pEr r or; CComBSTR st r Er r or ; Get Er r or I nf o( 0, &pEr r or ) ; pEr r or - >Get Descr i pt i on( &st r Er r or ) ; MessageBox( OLE2T(st r Er r or ) ,

    _T( "Er r or " ) , MB_I CONEXCLAMATI ON) ; r et ur n E_FAI L;

    }Set Dl gI t emI nt ( I DC_TI MESTOROLL, nTi mesToRol l , FALSE) ;

    }}m_bDi r t y = FALSE; hr = I Pr opert yPageI mpl : : Show( nCmdShow) ; return hr;

    }};

    In addition to adding code to prepare to show the dialog box, you need to add code allowing users to set the control's

    properties. Whenever the user changes a property, the property dialog activates the Applybutton, indicating that theuser can apply the newly set properties. When the user presses the Apply button, control jumps to the property

    page's Apply function so you need to insert some code in here to make the Apply button work.

  • 8/11/2019 Visual c Plus Mfc 29

    63/70

    Handling the Apply Button. After the user finishes manipulating the properties, he or she clicks either the Applybutton or the OK button to save the changes. In response, the client code asks the property page to apply the newproperties to the control. Remember that the ActiveX control and the property page are separate COM objects, sothey need to communicate via interfaces. Here's how the process works.

    When you create a property page using the ATL Object Wizard, ATL overrides the Appl y( ) function fromI Pr oper t yPagefor you. The property page site uses this function for notifying the property page of changes that

    need to be made to the control. When the property page's Appl y( ) function is called, it's time to synch up the stateof the property page with the state of the control. Remember, the control's I Unknowninterface was passed into theproperty page early in the game via a call to I Pr oper t yPage: : Set Obj ect s . The interface pointers are storedin the property page's m_ppUnkarray. Most property pages respond to the Appl y( ) function by setting the stateof the ActiveX control properties through the interface provided. In the case of our example ATL-based property

    page, this means examining the value in the combo box and the edit box and setting the new values inside thecontrol itself, like this:

    #i ncl ude "atl di cesr vr . h"

    cl ass ATL_NO_VTABLE CDi ceMai nPropPage : publ i c CComObj ect Root Ex, publ i c CComCoCl ass,

    publ i c I Proper t yPageI mpl , publ i c CDi al ogI mpl

    {. . . . . . STDMETHOD( Appl y) ( voi d) {

    USES_ CONVERSI ON; ATLTRACE( _T( "CDi ceMai nPropPage: : Appl y\ n") ) ; f or ( UI NT i = 0; i < m_nObj ect s; i ++) {

    CComQI Pt r pMyat l di ceob(m_ppUnk[ i ] ) ;

    HWND hWndComboBox = Get Dl gI t em( I DC_COLOR) ; shor t nCol or = ( shor t ) : : SendMessage( hWndComboBox,

    CB_GETCURSEL, 0, 0) ;

    i f ( nCol or >= 0 && nCol or put _Di ceCol or ( nCol or) ) {

    CComPt r pEr r or; CComBSTR st r Er r or ; Get Er r or I nf o( 0, &pEr r or ) ; pEr r or - >Get Descr i pt i on( &st r Er r or ) ; MessageBox( OLE2T(st r Er r or ) ,

    _T( "Er r or " ) ,MB_I CONEXCLAMATI ON) ;

    r et ur n E_FAI L; }

    }

    shor t nTi mesToRol l = ( shor t ) GetDl gI t emI nt ( I DC_TI MESTOROLL) ; i f FAI LED( pMyat l di ceob- >put _Ti mesToRol l ( nTi mesToRol l ) ) {

    CComPt r pEr r or ; CComBSTR st r Er r or ; Get Er r or I nf o( 0, &pEr r or ) ; pEr r or - >Get Descri pt i on( &st r Er r or ) ;

    MessageBox( OLE2T(st r Er r or ) ,_T( "Er r or " ) ,MB_I CONEXCLAMATI ON) ;

  • 8/11/2019 Visual c Plus Mfc 29

    64/70

    r et ur n E_FAI L; }

    }m_bDi r t y = FALSE; r et urn S_OK;

    }

    Property Persistence

    Once you have added properties to the control, it's logical that you might want to have those properties persist withtheir container. For example, imagine Hasbro buys your dice control to include in its new Windows version of

    Monopoly. The game vendor uses your dice control within one of the Monopoly dialog boxes and configures thecontrol so that the dice are blue and they roll 23 times before stopping. If the dice control had a sound property, theMonopoly authors could configure the dice to emit a beep every time they roll. When someone plays the game androlls the dice, that person will see a pair of blue dice that roll 23 times before stopping and they will hear the dicemake a sound while they roll. Remember that these properties are all properties of the control. If you're using the

    control in an application, chances are good you'll want these properties to be saved with the application.Fortunately, adding persistence support to your control is almost free when you use the ATL property macros.You've already seen how to add the property pages to the control DLL using the property map macros. As it turns

    out, these macros also make the properties persistent.You can find ATL's code for handling the persistence of a control's properties within the CComCont r ol Baseclass. CComCont r ol Basehas a member function named I Per si st St r eamI ni t _Save( ) that handlessaving a control's properties to a stream provided by the client. Whenever the container calls

    I Per si stSt r eamI ni t : : Save, ATL ends up calling I Per si st St r eamI ni t _Save( ) to do the actualwork. I Per si st St r eamI ni t _Save( ) works by retrieving the control's property map, the list of propertiesmaintained by the control. Remember that the BEGI N_PROPERTY_MAPmacro adds a function namedGetProper t yMap( ) to the control. The first item written out by I Per si st St r eamI ni t _Save( ) is thecontrol's extents (its size on the screen). I Per si st St r eamI ni t _Save( ) then cycles through the property mapto write the contents of the property map out to the stream. For each property, the control calls

    Quer yI nt er f ace( ) on itself to get its own dispatch interface. As I Per si st St r eamI ni t _Save( ) goesthrough the list of properties, the control calls I Di spat ch: : I nvokeon itself to get the property based on the

    DI SPI Dassociated with the property. The property's DI SPI Dis included as part of the property map structure. Theproperty comes back from I Di spat ch: : I nvokeas a Var i ant , and I Per si st St r eamI ni t _Save( ) writes the property to the stream provided by the client.

    Bidirectional Communication (Events)

    Now that the dice control has properties and property pages and renders itself to a device context, the last thing to dois to add some events to the control. Events provide a way for the control to call back to the client code and informthe client code of certain events as they occur.For example, the user can roll the dice. Then when the dice stop rolling, the client application can fish the dicevalues out of the control. However, another way to implement the control is to set it up so that the control notifies

    the client application when the dice have rolled using an event. Here you'll see how to add some events to the dicecontrol. We'll start by understanding how ActiveX Control events work.

    How Events Work. When a control is embedded in a container (such as a Visual Basic form or an MFC-baseddialog box), one of the steps the client code takes is to establish a connection to the control's event set. That is, theclient implements an interface that has been described by the control and makes that interface available to the

    control. That way, the control can talk back to the container.Part of developing a control involves defining an interface that the control can use to call back to the client. Forexample, if you're developing the control using MFC, ClassWizard will define the interface and produce somefunctions you can call from within the control to fire events back to the client. If you're developing the control inATL, you can accomplish the same result by defining the event callback interface in the control's IDL and usingClassView to create a set of callback proxy functions for firing the events to the container. When the callbackinterface is defined by the control, the container needs to implement that interface and hand it over to the control.

    The client and the control do this through the I Connect i onPoi nt Cont ai ner and I Connect i onPoi nt interfaces.

    I Connect i onPoi nt Cont ai ner is the interface that a COM object implements to indicate that it supportsconnections. I Connect i onPoi nt Cont ai ner represents a collection of connections available to the client.

  • 8/11/2019 Visual c Plus Mfc 29

    65/70

    Within the context of ActiveX Controls, one of these connections is usually the control's main event set. Here's the

    I Connect i onPoi nt Cont ai ner interface:

    i nt er f ace I Connect i onPoi nt Cont ai ner : I Unknown{

    HRESULT Fi ndConnect i onPoi nt ( REFI I D r i i d, I Connect i onPoi nt **ppcp) =

    0; HRESULT EnumConnect i onPoi nt s( I EnumConnect i onsPoi nt **ppec) = 0; };

    I Connect i onPoi nt Cont ai ner represents a collection of I Connect i onPoi nt interfaces. Here's theI Connect i onPoi nt interface:

    i nt er f ace I Connect i onPoi nt : I Unknown{

    HRESULT GetConnect i onI nter f ace( I I D *pi d) = 0; HRESULT GetConnect i onPoi ntCont ai ner( I Connect i onPoi ntCont ai ner

    **ppcpc) = 0; HRESULT Advi se( I Unknown *pUnk, DWORD *pdwCooki e) = 0;

    HRESULT Unadvi se( dwCooki e) = 0; HRESULT EnumConnect i ons( I EnumConnect i ons **ppec) = 0; }

    The container creates the control by calling CoCr eateI nst ance( ) on the control. As the control and thecontainer are establishing the interface connections between themselves, one of the interfaces the container asks for

    is I Connect i onPoi nt Cont ai ner (that is, the container calls Quer yI nt er f ace( ) asking forI I D_I Connect i onPoi nt Cont ai ner ). If the control supports connection points (the control answers "Yes"when queried for I Connect i onPoi nt Cont ai ner ), the control usesI Connect i onPoi nt Cont ai ner : : Fi ndConnect i onPoi nt to get the I Connect i onPoi nt interfacerepresenting the main event set. The container knows the GUI Drepresenting the main event set by looking at thecontrol's type information as the control is inserted into the container.If the container can establish a connection point to the control's main event set (that is,

    I Connect i onPoi nt Cont ai ner : : Fi ndConnect i onPoi nt returns an I Connect i onPoi nt interfacepointer), the container uses I Connect i onPoi nt : : Advi seto subscribe to the callbacks. Of course, to do thisthe container needs to implement the callback interface defined by the control (which the container can learn aboutby using the control's type library). Once the connection is established, the control can call back to the containerwhenever the control fires off an event. Here's what it takes to make events work within an ATL-based ActiveXcontrol.

    Adding Events to the Dice Control. There are several steps to adding event sets to your control. Some of them are

    hidden by clever wizardry. First, use IDL to describe the events. Second, add a proxy that encapsulates theconnection points and event functions. Finally, fill out the control's connection map so that the client and the objecthave a way to connect to each other. Let's examine each step in detail.When using ATL to write an ActiveX control, IDL is the place to start adding events to your control. The event

    callback interface is described within the IDL so the client knows how to implement the callback interface correctly.The IDL is compiled into a type library that the client will use to figure out how to implement the callback interface.

    For example, if you wanted to add events indicating the dice were rolled, doubles were rolled, and snake eyes wererolled, you'd describe the callback interface like this in the control's IDL file:

    l i br ar y ATLDI CESRVRLi b{

    i mport l i b( "stdol e32. t l b") ; i mport l i b( "stdol e2. t l b") ;

    [ uui d( 21C85C43- 0BFF- 11d1- 8CAA- FD10872CC837) , hel pst r i ng( "Event s creat ed f r om r ol l i ng di ce")

    ] di spi nt er f ace _I Myat l di ceobj Event s {

  • 8/11/2019 Visual c Plus Mfc 29

    66/70

    proper t i es: met hods:

    [ i d( 1) ] voi d Di ceRol l ed( [ i n] shor t x, [ i n] short y) ; [ i d( 2) ] voi d Doubl es([ i n] shor t x) ; [ i d( 3) ] voi d SnakeEyes( ) ;

    }

    [ uui d( 6AED4EBD- 0991- 11D1- 8CAA- FD10872CC837) , hel pst r i ng( "Myat l di ceob Cl ass")

    ] cocl ass Myat l di ceob{

    [ def aul t ] i nt er f ace I ATLDi eceObj ; [ def aul t , sour ce] di spi nt er f ace _I Myat l di ceobj Event s;

    };

    The control's callback interface is defined as a dispatch interface (note the dispinterface keyword) b