Basic Control Containment


Control Creation

The control-creation process in ATL exposes the core of how ATL hosts controls. Figure 12.2 shows the overall process. What follows is a detailed look at the relevant bits of code involved.

Figure 12.2. A UML sequence diagram of the ATL control-creation process


ATL's implementation of the required container interfaces is called CAxHostWindow.[3]

[3] This class is defined in atlhost.h.

// This class is not cocreateable                  class ATL_NO_VTABLE CAxHostWindow :                  public CComCoClass<CAxHostWindow, &CLSID_NULL>,    public CComObjectRootEx<CComSingleThreadModel>,    public CWindowImpl<CAxHostWindow>,                 public IAxWinHostWindow,                           public IOleClientSite,                             public IOleInPlaceSiteWindowless,                  public IOleControlSite,                            public IOleContainer,                              public IObjectWithSiteImpl<CAxHostWindow>,         public IServiceProvider,                           public IAdviseSink,                              #ifndef _ATL_NO_DOCHOSTUIHANDLER                     public IDocHostUIHandler,                        #endif                                               public IDispatchImpl<IAxWinAmbientDispatch,          &IID_IAxWinAmbientDispatch,                        &LIBID_ATLLib>                                 {...};                                             


Notice that a CAxHostWindow is two things: a window (from CWindowImpl) and a COM implementation (from CComObjectRootEx). When the container wants to host a control, it creates an instance of CAxHostWindow, but not directly. Instead, it creates an instance of a window class defined by ATL, called AtlAxWin80. This window acts as the parent window for the control and eventually is subclassed by an instance of CAxHostWindow. Before an instance of this window class can be created, the window class must first be registered. ATL provides a function called AtlAxWinInit to register the AtlAxWin80 window class.

// This either registers a global class                         // (if AtlAxWinInit is in ATL.DLL)                              // or it registers a local class                                ATLINLINE ATLAPI_(BOOL) AtlAxWinInit() {                          CComCritSecLock<CComCriticalSection> lock(                        _AtlWinModule.m_csWindowCreate, false);                       if (FAILED(lock.Lock())) {                                        ATLTRACE(atlTraceHosting, 0,                                      _T("ERROR : Unable to lock critical section "                     "in AtlAxWinInit\n"));                                      ATLASSERT(0);                                                   return FALSE;                                                 }                                                               WM_ATLGETHOST = RegisterWindowMessage(_T("WM_ATLGETHOST"));     WM_ATLGETCONTROL = RegisterWindowMessage(                         _T("WM_ATLGETCONTROL"));                                      WNDCLASSEX wc;                                                  // first check if the class is already registered               wc.cbSize = sizeof(WNDCLASSEX);                                 BOOL bRet = ::GetClassInfoEx(                                     _AtlBaseModule.GetModuleInstance(),                             CAxWindow::GetWndClassName(), &wc);                           // register class if not                                        if(!bRet) {                                                       wc.cbSize = sizeof(WNDCLASSEX);                             #ifdef _ATL_DLL_IMPL                                                wc.style = CS_GLOBALCLASS | CS_DBLCLKS;                         bAtlAxWinInitialized = true;                                #else                                                               wc.style = CS_DBLCLKS;                                      #endif                                                              wc.lpfnWndProc = AtlAxWindowProc;                               wc.cbClsExtra = 0;                                              wc.cbWndExtra = 0;                                              wc.hInstance = _AtlBaseModule.GetModuleInstance();              wc.hIcon = NULL;                                                wc.hCursor = ::LoadCursor(NULL, IDC_ARROW);                     wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);                  wc.lpszMenuName = NULL;                                         wc.lpszClassName =                                                CAxWindow::GetWndClassName(); // "AtlAxWin80"                 wc.hIconSm = NULL;                                              ATOM atom= ::RegisterClassEx(&wc);                              if(atom) {                                                       _AtlWinModule.m_rgWindowClassAtoms.Add(atom);                   bRet=TRUE;                                                     } else {                                                          bRet=FALSE;                                                   }                                                             }                                                               if(bRet) {                                                        // first check if the class is already registered               memset(&wc, 0, sizeof(WNDCLASSEX));                             wc.cbSize = sizeof(WNDCLASSEX);                                 bRet = ::GetClassInfoEx(_AtlBaseModule.GetModuleInstance(),       CAxWindow2::GetWndClassName(), &wc);                          // register class if not                                        if(!bRet) {                                                       wc.cbSize = sizeof(WNDCLASSEX);                           #ifdef _ATL_DLL_IMPL                                                  wc.style = CS_GLOBALCLASS | CS_DBLCLKS;                   #else                                                                 wc.style = CS_DBLCLKS;                                    #endif                                                                wc.lpfnWndProc = AtlAxWindowProc2;                              wc.cbClsExtra = 0;                                              wc.cbWndExtra = 0;                                              wc.hInstance = _AtlBaseModule.GetModuleInstance();              wc.hIcon = NULL;                                                wc.hCursor = ::LoadCursor(NULL, IDC_ARROW);                     wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);                  wc.lpszMenuName = NULL;                                         wc.lpszClassName =                                                CAxWindow2::GetWndClassName();//"AtlAxWinLic80"               wc.hIconSm = NULL;                                              ATOM atom= RegisterClassEx(&wc);                                if (atom) {                                                       _AtlWinModule.m_rgWindowClassAtoms.Add(atom);                   bRet=TRUE;                                                    } else {                                                        bRet=FALSE;                                                   }                                                              }                                                              }                                                               return bRet;                                                  }                                                               


This function actually registers two window classes: AtlAxWin80 and AtlAxWinLic80. The difference, if you haven't guessed from the names, is that the latter supports embedding controls that require a runtime license.

You don't need to manually call AtlAxWinInit: It is called from the ATL code at the various places where these window classes are needed, as you'll see later. Multiple calls are fine: The function checks to make sure the window classes are registered first before doing anything.

After the AtlAxWin80 class has been registered, creating an instance of one also creates an instance of CAxHostWindow. The CAxHostWindow object uses the title of the window as the name of the control to create and to host. For example, the following code creates a CAxHostWindow and causes it to host a new instance of the BullsEye control developed in Chapter 10, "Windowing":

class CMainWindow : public CWindowImpl<CMainWindow, ...> { ...   LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam,     BOOL& lResult) {     // Create the host window, the CAxHostWindow object, and     // the BullsEye control, and host the control     RECT rect; GetClientRect(&rect);     LPCTSTR pszName = __T("ATLInternals.BullsEye");     HWND hwndContainer = m_ax.Create(__T("AtlAxWin80"),       m_hWnd, rect, pszName, WS_CHILD | WS_VISIBLE);     if( !hwndContainer ) return -1;     return 0;   } private:   CWindow m_ax; }; 


The creation of the CAxHostWindow object and the corresponding control is initiated in the WM_CREATE handler of the AtlAxWin80 window procedure AtlAxWindowProc:

static LRESULT CALLBACK                                           AtlAxWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam,                LPARAM lParam) {                                                  switch(uMsg) {                                                    case WM_CREATE: {                                                   // create control from a PROGID in the title                      // This is to make sure drag drop works                           ::OleInitialize(NULL);                                            CREATESTRUCT* lpCreate = (CREATESTRUCT*)lParam;                   int nLen = ::GetWindowTextLength(hWnd);                           CAutoStackPtr<TCHAR> spName(                                        (TCHAR *)_malloca((nLen + 1) * sizeof(TCHAR)));                 if(!spName) {                                                       return -1;                                                      }                                                                 // Extract window text to be used as name of control to host     ::GetWindowText(hWnd, spName, nLen + 1);                          ::SetWindowText(hWnd, _T(""));                                    IAxWinHostWindow* pAxWindow = NULL;                               ...                                                               USES_CONVERSION_EX;                                               CComPtr<IUnknown> spUnk;                                          // Create AxHostWindow instance and host the control     HRESULT hRet = AtlAxCreateControlLic(T2COLE_EX_DEF(spName),         hWnd, spStream, &spUnk, NULL);                                  if(FAILED(hRet)) return -1; // abort window creation              hRet = spUnk->QueryInterface(__uuidof(IAxWinHostWindow),            (void**)&pAxWindow);                                            if(FAILED(hRet)) return -1; // abort window creation              // Keep a CAxHostWindow interface pointer in the window's     // user data     ::SetWindowLongPtr(hWnd, GWLP_USERDATA,                             (DWORD_PTR)pAxWindow);                                          // continue with DefWindowProc                                    }                                                               break;                                                            case WM_NCDESTROY: {                                                IAxWinHostWindow* pAxWindow =                                       (IAxWinHostWindow*)::GetWindowLongPtr(hWnd, GWLP_USERDATA);     // When window goes away, release the host (and the control)     if(pAxWindow != NULL) pAxWindow->Release();                       OleUninitialize();                                              }                                                                 break;                                                            case WM_PARENTNOTIFY: {                                             if((UINT)wParam == WM_CREATE) {                                     ATLASSERT(lParam);                                                // Set the control parent style for the AxWindow                  DWORD dwExStyle = ::GetWindowLong((HWND)lParam,                     GWL_EXSTYLE);                                                   if(dwExStyle & WS_EX_CONTROLPARENT) {                               dwExStyle = ::GetWindowLong(hWnd, GWL_EXSTYLE);                   dwExStyle |= WS_EX_CONTROLPARENT;                                 ::SetWindowLong(hWnd, GWL_EXSTYLE, dwExStyle);                  }                                                               }                                                               }                                                                 break;                                                            default:                                                              break;                                                        }                                                                 return ::DefWindowProc(hWnd, uMsg, wParam, lParam);             }                                                                 


Notice that the window's text, as passed to the call to CWindow::Create, is used as the name of the control to create. The call to AtlAxCreateControlLic, passing the name of the control, forwards to AtlAxCreateControlLicEx, which furthers things by creating a CAxHostWindow object and asking it to create and host the control:

ATLINLINE ATLAPI AtlAxCreateControlLicEx(                         LPCOLESTR lpszName,                                             HWND hWnd,                                                      IStream* pStream,                                               IUnknown** ppUnkContainer,                                      IUnknown** ppUnkControl,                                        REFIID iidSink,                                                 IUnknown* punkSink,                                             BSTR bstrLic) {                                                 AtlAxWinInit();                                                 HRESULT hr;                                                     CComPtr<IUnknown> spUnkContainer;                               CComPtr<IUnknown> spUnkControl;                                 hr = CAxHostWindow::_CreatorClass::CreateInstance(NULL,           __uuidof(IUnknown), (void**)&spUnkContainer);                 if(SUCCEEDED(hr)) {                                               CComPtr<IAxWinHostWindowLic> pAxWindow;                         spUnkContainer->QueryInterface(__uuidof(IAxWinHostWindow),        (void**)&pAxWindow);                                          CComBSTR bstrName(lpszName);                                    hr = pAxWindow->CreateControlLicEx(bstrName, hWnd, pStream,       &spUnkControl, iidSink, punkSink, bstrLic);                 }                                                               if(ppUnkContainer != NULL) {                                      if (SUCCEEDED(hr)) {                                              *ppUnkContainer = spUnkContainer.p;                             spUnkContainer.p = NULL;                                      }                                                               else                                                              *ppUnkContainer = NULL;                                     }                                                               if (ppUnkControl != NULL) {                                       if (SUCCEEDED(hr)) {                                              *ppUnkControl = SUCCEEDED(hr) ? spUnkControl.p : NULL;          spUnkControl.p = NULL;                                        }                                                               else                                                              *ppUnkControl = NULL;                                       }                                                               return hr;                                                    }                                                               


IAxWinHostWindow

AtlAxCreateControlEx uses the IAxWinHostWindow interface to create the control. IAxWinHostWindow is one of the few interfaces that ATL defines and is one of the interfaces that CAxHostWindow implements. Its job is to allow for management of the control that it's hosting:

interface IAxWinHostWindow : IUnknown {                   HRESULT CreateControl([in] LPCOLESTR lpTricsData,         [in] HWND hWnd, [in] IStream* pStream);               HRESULT CreateControlEx([in] LPCOLESTR lpTricsData,       [in] HWND hWnd, [in] IStream* pStream,                  [out] IUnknown** ppUnk,                                 [in] REFIID riidAdvise, [in] IUnknown* punkAdvise);   HRESULT AttachControl([in] IUnknown* pUnkControl,         [in] HWND hWnd);                                      HRESULT QueryControl([in] REFIID riid,                    [out, iid_is(riid)] void **ppvObject);                HRESULT SetExternalDispatch([in] IDispatch* pDisp);     HRESULT SetExternalUIHandler(                             [in] IDocHostUIHandlerDispatch* pDisp);             };                                                      


A second interface, IAxWinHostWindowLic, derives from IAxWinHostWindow and supports creating licensed controls:

interface IAxWinHostWindowLic : IAxWinHostWindow {               HRESULT CreateControlLic([in] LPCOLESTR lpTricsData,             [in] HWND hWnd, [in] IStream* pStream, [in] BSTR bstrLic);   HRESULT CreateControlLicEx([in] LPCOLESTR lpTricsData,           [in] HWND hWnd, [in] IStream* pStream,                         [out]IUnknown** ppUnk, [in] REFIID riidAdvise,                 [in]IUnknown* punkAdvise, [in] BSTR bstrLic);              };                                                             


To create a new control in CAxHostWindow, IAxWinHostWindow provides CreateControl or CreateControlEx, which AtlAxCreateControlEx then uses after the CAxHostWindow object is created. The parameters for CreateControl[Ex] are as follows:

  • lpTricsData. The name of the control to create. It can take the form of a CLSID, a ProgID, a URL, a filename, or raw HTML. We discuss this more later.

  • hWnd. Parent window in which to host the control. This window is subclassed by CAxHostWindow.

  • pStream. Stream that holds object-initialization data. The control is initialized via IPersistStreamInit. If pStream is non-NULL, Load is called. Otherwise, InitNew is called.

  • ppUnk. Filled with an interface to the newly created control.

  • riidAdvise. If not IID_NULL, CAxHostWindow attempts to set up a connection-point connection between the control and the sink object represented by the punkAdvise parameter. CAxHostWindow manages the resultant cookie and tears down the connection when the control is destroyed.

  • punkAdvise. An interface to the sink object that implements the sink interface specified by riidAdvise.

    The CreateControlLicEx method adds one more parameter:

  • bstrLic. This string contains the licensing key. If the string is empty, the control is created using the normal CoCreateInstance call. If there is a licensing key in the string, the control is created via the IClassFactory2 interface, which supports creating licensed controls.

The AttachControl method of the IAxWinHostWindow method attaches a control that has already been created and initialized to an existing CAxHostWindow object. The QueryControl method allows access to the control's interfaces being hosted by the CAxHostWindow object. Both SetExternalDispatch and SetExternalUIHandler are used when hosting the Internet Explorer HTML control and will be discussed at the end of the chapter in the HTML Controls section.

CreateControlLicEx

CAxHostWindow's implementation of CreateControlLicEx subclasses[4] the parent window for the new control, creates a new control, and then activates it. If an initial connection point is requested, AtlAdvise is used to establish that connection. If the newly created control is to be initialized from raw HTML or to be navigated to via a URL, CreateControlLicEx does that, too:

[4] This is subclassing in the User32 sense, not the C++ sense.

STDMETHODIMP CAxHostWindow::CreateControlLicEx(                   LPCOLESTR lpszTricsData,                                        HWND hWnd,                                                      IStream* pStream,                                               IUnknown** ppUnk,                                               REFIID iidAdvise,                                               IUnknown* punkSink,                                             BSTR bstrLic)                                                 {                                                                 HRESULT hr = S_FALSE;                                           // Used to keep track of whether we subclass the window         bool bReleaseWindowOnFailure = false;                         // Release previously held control   ReleaseAll();                                                   ...                                                             if (::IsWindow(hWnd)) {                                           if (m_hWnd != hWnd) { // Don't need to subclass the window                            // if we already own it                     // Route all messages to CAxHostWindow       SubclassWindow(hWnd);                                           bReleaseWindowOnFailure = true;                               }                                                               ...                                                             bool bWasHTML = false;                                          // Create control based on lpszTricsData     hr = CreateNormalizedObject(lpszTricsData,                         __uuidof(IUnknown),                                             (void**)ppUnk, bWasHTML, bstrLic);                           // Activate the control                                         if (SUCCEEDED(hr)) hr = ActivateAx(*ppUnk, false, pStream);     // Try to hook up any sink the user might have given us.        m_iidSink = iidAdvise;                                          if(SUCCEEDED(hr) && *ppUnk && punkSink) {                         AtlAdvise(*ppUnk, punkSink, m_iidSink, &m_dwAdviseSink);      }                                                               ...                                                             // If raw HTML, give HTML control its HTML                      ...                                                             // If it's an URL, navigate the Browser control to the URL      ...                                                             if (FAILED(hr) || m_spUnknown == NULL) {                          // We don't have a control or something failed so release       ReleaseAll();                                                   ...                                                           }                                                             }                                                               return hr;                                                    }                                                               


How the control name is interpreted depends on another function, called CreateNormalizedObject. The actual COM activation process is handled by the ActivateAx function (discussed shortly).

CreateNormalizedObject

The CreateNormalizedObject function creates an instance of a COM object using strings of the form shown in Table 12.1.

Table 12.1. String Formats Understood by CreateNormalizedObject

Type

Example

CLSID of Created Object

HTML

mshtml:<body><b>Wow!</b></body>

CLSID_HTMLDocument

CLSID

{7DC59CC5-36C0-11D2-AC05-00A0C9C8E50D}

Result of CLSIDFromString

ProgID

ATLInternals.BullsEye

Result of CLSIDFromProgID

URL

http://www.awl.com

res://htmlapp.exe/main.htm

CLSID_WebBrowser

Active document

D:\Atl Internals\10 Controls.doc

file://D:\Atl Internals\10 Controls.doc

CLSID_WebBrowser


Because CAxHostWindow uses the title of the window to obtain the name passed to CreateNormalizedObject, you can use any of these string formats when creating an instance of the AtlWinInit window class.

If no license key is supplied, CreateNormalizedObject uses CoCreateInstance to instantiate the object. If there is a license key, CreateNormalizedObject instead calls CoGetClassFactory and uses the factory's IClassFactory2::CreateInstanceLic method to create the control using the given license key.

ActivateAx

The ActivateAx function is the part of the control-creation process that really performs the magic. Called after CreateControlLicEx[5] actually creates the underlying COM object, ActivateAx takes an interface pointer from the object that CreateNormalizedObject creates and activates it as a COM control in the parent window. ActivateAx is responsible for the following:

[5] Despite the name, the CreateControlLicEx function handles the creation of COM controls with or without licensing information.

  • Setting the client sitethat is, the CAxHostWindow's implementation of IOleClientSitevia the control's implementation of IOleObject.

  • Calling either InitNew or Load (depending on whether the pStream argument to AtlAxCreateControlEx is NULL or non-NULL) via the control's implementation of IPersistStreamInit.

  • Passing the CAxHostWindow's implementation of IAdviseSink to the control's implementation of IViewObject.

  • Setting the control's size to the size of the parent window, also via the control's implementation of IOleObject.

  • Finally, to show the control and allow it to handle input and output, calling DoVerb(OLEIVERB_INPLACEACTIVATE) via the control's implementation of, again, IOleObject.

This process completes the activation of the control. However, creating an instance of CAxHostWindow via direct reference to the AtlAxWin80 window class is not typical. The implementation details of AtlAxWin80 and CAxHostWindow are meant to be hidden from the average ATL programmer. The usual way a control is hosted under ATL is via an instance of a wrapper class. Two such wrappers exist in ATL: CAxWindow and CAxWindow2.

CAxWindow

CAxWindow simplifies the use of CAxHostWindow with a set of wrapper functions. The initial creation part of CAxWindow class is defined as follows:

#define ATLAXWIN_CLASS "AtlAxWin80"                               template <class TBase /* = CWindow */> class CAxWindowT :           public TBase {                                                  public:                                                           // Constructors                                                     CAxWindowT(HWND hWnd = NULL) : TBase(hWnd) { AtlAxWinInit(); }      CAxWindowT< TBase >& operator=(HWND hWnd) {                         m_hWnd = hWnd; return *this;                                    }                                                               // Attributes                                                       static LPCTSTR GetWndClassName() { return _T(ATLAXWIN_CLASS); } // Operations                                                       HWND Create(HWND hWndParent, _U_RECT rect = NULL, ...) {            return CWindow::Create(GetWndClassName(), hWndParent,               rect, ... );                                                  }                                                               ...                                                               };                                                                typedef CAxWindowT<CWindow> CAxWindow;                            


Notice that the Create function still requires the parent window and the name of the control but does not require passing the name of the CAxHostWindow window class. Instead, CAxWindow knows the name of the appropriate class itself (available via its static member function GetWndClassName) and passes it to the CWindow base class just like we had done manually. Using CAxWindow reduces the code required to host a control to the following:

class CMainWindow : public CWindowImpl<CMainWindow, ...> { ...   LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam,     BOOL& lResult) {     // Create the host window, the CAxHostWindow object, and     // the BullsEye control, and host the control     RECT rect; GetClientRect(&rect);     LPCTSTR pszName = __T("ATLInternals.BullsEye");     HWND hwndContainer = m_ax.Create(m_hWnd, rect,         pszName, WS_CHILD | WS_VISIBLE);     if( !hwndContainer ) return -1;     return 0;   } private:   CAxWindow m_ax; }; 


The combination of a custom window class and a CWindow-based wrapper provides exactly the same model as the window control wrappers that I discussed in Chapter 10, "Windowing." For example, EDIT is a window class, and the CEdit class provides the client-side wrapper. The implementation of the EDIT window class happens to use the window text passed via CreateWindow as the text to edit. The AtlAxWin80 class, on the other hand, uses the window text as the name of the control to create. The job of both wrapper classes is to provide a set of member functions that replace calls to SendMessage. CEdit provides member functions such as CanUndo and GetLineCount, which send the EM_CANUNDO and EM_GETLINECOUNT messages. CAxWindow, on the other hand, provides member functions that also send window messages to the AtlAxWin80 class (which I discuss later). The only real difference between EDIT and AtlAxWin80 is that EDIT is provided with the operating system, whereas AtlAxWin80 is provided only with ATL.[6]

[6] Arguably, a window class whose function is to host COM controls should be part of the OS.

Two-Step Control Creation

You might have noticed that AtlAxCreateControlEx takes some interesting parameters, such as an IStream interface pointer and an interface ID/interface pointer pair, to specify an initial connection point. However, although the window name can be used to pass the name of the control, there are no extra parameters to CreateWindow for a couple interface pointers and a globally unique identifier (GUID). Instead, CAxWindow provides a few extra wrapper functions: CreateControl and CreateControlEx:

template <class TBase> class CAxWindowT : public TBase {          public:                                                             ...                                                               HRESULT CreateControl(LPCOLESTR lpszName,                           IStream* pStream = NULL,                                          IUnknown** ppUnkContainer = NULL) {                               return CreateControlEx(lpszName, pStream, ppUnkContainer);      }                                                                 HRESULT CreateControl(DWORD dwResID, IStream* pStream = NULL,       IUnknown** ppUnkContainer = NULL) {                               return CreateControlEx(dwResID, pStream, ppUnkContainer);       }                                                                 HRESULT CreateControlEx(LPCOLESTR lpszName,                         IStream* pStream = NULL,                                          IUnknown** ppUnkContainer = NULL,                                 IUnknown** ppUnkControl = NULL,                                   REFIID iidSink = IID_NULL, IUnknown* punkSink = NULL) {           ATLASSERT(::IsWindow(m_hWnd));                                    // We must have a valid window!                                   // Get a pointer to the container object                          // connected to this window                                       CComPtr<IAxWinHostWindow> spWinHost;                              HRESULT hr = QueryHost(&spWinHost);                               // If QueryHost failed, there is no host attached to this         // window. We assume that the user wants to create a new          // host and subclass the current window                           if (FAILED(hr)) {                                                   return AtlAxCreateControlEx(lpszName, m_hWnd, pStream,              ppUnkContainer, ppUnkControl, iidSink, punkSink);             }                                                                 // Create the control requested by the caller                     CComPtr<IUnknown> pControl;                                       if (SUCCEEDED(hr)) {                                                hr = spWinHost->CreateControlEx(lpszName, m_hWnd, pStream,          &pControl, iidSink, punkSink);                                }                                                                 // Send back the necessary interface pointers                     if (SUCCEEDED(hr)) {                                                if (ppUnkControl) { *ppUnkControl = pControl.Detach(); }          if (ppUnkContainer) {                                               hr = spWinHost.QueryInterface(ppUnkContainer);                    ATLASSERT(SUCCEEDED(hr)); // This should not fail!              }                                                               }                                                                 return hr;                                                      }                                                                 HRESULT CreateControlEx(DWORD dwResID, IStream* pStream = NULL,     IUnknown** ppUnkContainer = NULL,                                 IUnknown** ppUnkControl = NULL,                                   REFIID iidSink = IID_NULL, IUnknown* punkSink = NULL) {           TCHAR szModule[MAX_PATH];                                         DWORD dwFLen =                                                      GetModuleFileName(_AtlBaseModule.GetModuleInstance(),               szModule, MAX_PATH);                                          if( dwFLen == 0 ) return AtlHresultFromLastError();               else if( dwFLen == MAX_PATH )                                       return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);           CComBSTR bstrURL(OLESTR("res://"));                               HRESULT hr=bstrURL.Append(szModule);                              if(FAILED(hr)) { return hr; }                                     hr=bstrURL.Append(OLESTR("/"));                                   if(FAILED(hr)) { return hr; }                                     TCHAR szResID[11];                                            #if _SECURE_ATL && !defined(_ATL_MIN_CRT)                             if (_stprintf_s(szResID, _countof(szResID),                         _T("%0d"), dwResID) == -1) {                                      return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);           }                                                             #else                                                                 wsprintf(szResID, _T("%0d"), dwResID);                        #endif                                                                hr=bstrURL.Append(szResID);                                       if(FAILED(hr)) { return hr; }                                     ATLASSERT(::IsWindow(m_hWnd));                                    return CreateControlEx(bstrURL, pStream, ppUnkContainer,            ppUnkControl, iidSink, punkSink);                             }                                                                 ...                                                             };                                                                


CreateControl and CreateControlEx allow for the extra parameters that AtlAxCreateControlEx supports. The extra parameter that the CAxWindow wrappers support beyond those passed to AtlAxCreateControlEx is the dwResID parameter, which serves as an ID of an HTML page embedded in the resources of the module. This parameter is formatted into a string of the format res://<module path>/<dwResID> before being passed to AtlAxCreateControlEx.

These functions are meant to be used in a two-stage construction of first the host and then its control. For example:

LRESULT CMainWindow::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam,   BOOL& lResult) {   RECT rect; GetClientRect(&rect);   // Phase one: Create the container (passing a null (0)   // window title)   // m_ax is declared in the CMainWindow class as:   // CAxWindow m_ax;   HWND hwndContainer = m_ax.Create(m_hWnd, rect, 0,     WS_CHILD | WS_VISIBLE);   if( !hwndContainer ) return -1;   // Phase two: Create the control   LPCOLESTR pszName = OLESTR("ATLInternals.BullsEye");   HRESULT hr = m_ax.CreateControl(pszName);   return (SUCCEEDED(hr) ? 0 : -1); }; 


I show you how to persist a control and how to handle events from a control later in this chapter. If you've already created a control and initialized it, you can still use the hosting functionality of ATL by attaching the existing control to a host window via the AttachControl function:

template <class TBase = CWindow> class CAxWindowT :        public TBase {                                         public:                                                  ...                                                        HRESULT AttachControl(IUnknown* pControl,                  IUnknown** ppUnkContainer) {                             ATLASSERT(::IsWindow(m_hWnd));                           // We must have a valid window!                          // Get a pointer to the container object connected       // to this window                                        CComPtr<IAxWinHostWindow> spWinHost;                     HRESULT hr = QueryHost(&spWinHost);                      // If QueryHost failed, there is no host attached        // to this window. We assume that the user wants to      // create a new host and subclass the current window     if (FAILED(hr))                                            return AtlAxAttachControl(pControl, m_hWnd,                ppUnkContainer);                                     // Attach the control specified by the caller            if (SUCCEEDED(hr))                                         hr = spWinHost->AttachControl(pControl, m_hWnd);       // Get the IUnknown interface of the container           if (SUCCEEDED(hr) && ppUnkContainer) {                     hr = spWinHost.QueryInterface(ppUnkContainer);           ATLASSERT(SUCCEEDED(hr)); // This should not fail!     }                                                        return hr;                                             }                                                      ...                                                      };                                                       


AttachControl is used like this:

LRESULT CMainWindow::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam,   BOOL& lResult) {   RECT rect; GetClientRect(&rect);   // Phase one: Create the container   HWND hwndContainer = m_ax.Create(m_hWnd, rect, 0,     WS_CHILD | WS_VISIBLE);   if( !hwndContainer ) return -1;   // Create and initialize a control   CComPtr<IUnknown> spunkControl; // ...   // Phase two: Attach an existing control   HRESULT hr = m_ax.AttachControl(spunkControl);   return (SUCCEEDED(hr) ? 0 : -1); }; 


CAxWindow2 and the AtlAxWinLic80 Window Class

Earlier, I mentioned that ATL actually registers two window classes: AtlAxWin80 and AtlAxWinLic80. Why two window classes? AtlAxWinLic80 supports one feature that AtlAxWin80 does not: the creation of licensed controls.

This might sound odd at first. After all, the actual control-creation step is done by the CreateControlLicEx function, which handles the creation of controls with or without license keys. However, the AtlAxWin80 class never actually passes a license key, so CreateControlLicEx never uses the IClassFactory2 when AtlAxWin80 is the calling class.

The AtlAxWinLic80 window class, on the other hand, has a slightly different window proc:

static LRESULT CALLBACK AtlAxWindowProc2(HWND hWnd, UINT uMsg,       WPARAM wParam, LPARAM lParam) {                                    switch(uMsg) {                                                     case WM_CREATE: {                                                    // ... same as AtlAxWindowProc ...                                 // Format of data in lpCreateParams                                // int nCreateSize;   // size of Create data in bytes              // WORD nMsg;         // constant used to indicate type                                  // of DLGINIT data.                                                // See _DialogSplitHelper for values.        // DWORD dwLen;       // Length of data stored for control                               // in DLGINIT format in bytes.               // DWORD cchLicKey;   // Length of license key in OLECHAR's        // OLECHAR *szLicKey; // This will be present only if                                    // cchLicKey is greater than 0.                                    // This is of variable length and will                             // contain cchLicKey OLECHAR's                                     // that represent the license key.           // The following two fields will be present only if nMsg is        // WM_OCC_LOADFROMSTREAM_EX or WM_OCC_LOADFROMSTORAGE_EX.          // If present this information will be ignored since               // databinding is not supported.                                   // ULONG cbDataBinding;    // Length of databinding                                           // information in bytes.                // BYTE *pbDataBindingInfo // cbDataBinding bytes that contain                                // databinding information              // BYTE *pbControlData;    // Actual control data persisted                                   // by the control.                      // ... Load persistence data into stream ...                       CComBSTR bstrLicKey;                                               HRESULT hRet = _DialogSplitHelper::ParseInitData(spStream,           &bstrLicKey.m_str);                                              if (FAILED(hRet)) return -1;                                       USES_CONVERSION_EX;                                                CComPtr<IUnknown> spUnk;                                           hRet = AtlAxCreateControlLic(T2COLE_EX_DEF(spName), hWnd,            spStream, &spUnk, bstrLicKey);                                   if(FAILED(hRet)) {                                                   return 1; // abort window creation                              }                                                                  hRet = spUnk->QueryInterface(__uuidof(IAxWinHostWindowLic),          (void**)&pAxWindow);                                             if(FAILED(hRet)) return -1; // abort window creation               ::SetWindowLongPtr(hWnd, GWLP_USERDATA,                              (DWORD_PTR)pAxWindow);                                           // continue with DefWindowProc                                   }                                                                  break;                                                             // ... Rest is the same as AtlAxWindowProc ...                   }                                                                  


The difference is in the WM_CREATE handler. The CREATESTRUCT passed in can contain the license key information; if it does, AtlAxWinLic80 extracts the license key and passes it on to be used in the creation of the control.

Just as the CAxWindow class wraps the use of AtlAxWin80, the CAxWindow2 class wraps the use of AtlAxWinLic80:

template <class TBase /* = CWindow */>                            class CAxWindow2T : public CAxWindowT<TBase> {                    public:                                                           // Constructors                                                     CAxWindow2T(HWND hWnd = NULL) : CAxWindowT<TBase>(hWnd) { }         CAxWindow2T< TBase >& operator=(HWND hWnd);                     // Attributes                                                       static LPCTSTR GetWndClassName() {                                  return _T(ATLAXWINLIC_CLASS);                                   }                                                               // Operations                                                       HWND Create(HWND hWndParent, _U_RECT rect = NULL,                   LPCTSTR szWindowName = NULL,                                      DWORD dwStyle = 0, DWORD dwExStyle = 0,                           _U_MENUorID MenuOrID = 0U, LPVOID lpCreateParam = NULL) {         return CWindow::Create(GetWndClassName(), hWndParent, rect,         szWindowName, dwStyle, dwExStyle, MenuOrID, lpCreateParam);   }                                                                 HRESULT CreateControlLic(LPCOLESTR lpszName,                        IStream* pStream = NULL,                                          IUnknown** ppUnkContainer = NULL, BSTR bstrLicKey = NULL) {       return CreateControlLicEx(lpszName, pStream, ppUnkContainer,        NULL, IID_NULL, NULL, bstrLicKey);                            }                                                                 HRESULT CreateControlLic(DWORD dwResID,                             IStream* pStream = NULL,                                          IUnknown** ppUnkContainer = NULL, BSTR bstrLicKey = NULL) {       return CreateControlLicEx(dwResID, pStream, ppUnkContainer,         NULL, IID_NULL, NULL, bstrLicKey);                            }                                                                 HRESULT CreateControlLicEx(LPCOLESTR lpszName,                      IStream* pStream = NULL,                                          IUnknown** ppUnkContainer = NULL,                                 IUnknown** ppUnkControl = NULL,                                   REFIID iidSink = IID_NULL, IUnknown* punkSink = NULL,             BSTR bstrLicKey = NULL);                                        HRESULT CreateControlLicEx(DWORD dwResID,                           IStream* pStream = NULL,                                          IUnknown** ppUnkContainer = NULL,                                 IUnknown** ppUnkControl = NULL,                                   REFIID iidSink = IID_NULL, IUnknown* punkSink = NULL,             BSTR bstrLickey = NULL);                                      };                                                                typedef CAxWindow2T<CWindow> CAxWindow2;                          


The first thing to note is that CAxWindow2 derives from CAxWindow, so you can use CAxWindow to create nonlicensed controls just as easily. CAxWindow2 adds the various CreateControlLic[Ex] overloads to enable you to pass the licensing information when you create the control.

For the rest of this chapter, I talk about CAxWindow and AtlAxWin80. Everything in the discussion also applies to CAxWindow2 and AtlAxWinLic80.

Using the Control

After you've created the control, it's really two things: a window and a control. The window is an instance of AtlAxWin80 and hosts the control, which might or might not have its own window. (CAxHostWindow provides full support for windowless controls.) Because CAxWindow derives from CWindow, you can treat it like a window (that is, you can move it, resize it, and hide it); AtlAxWin80 handles those messages by translating them into the appropriate COM calls on the control. For example, if you want the entire client area of a frame window to contain a control, you can handle the WM_SIZE message like this:

class CMainWindow : public CWindowImpl<CMainWindow, ...> { ...   LRESULT OnSize(UINT, WPARAM, LPARAM lParam, BOOL&) {     if( m_ax ) { // m_ax will be Create'd earlier, e.g. WM_CREATE       RECT rect = { 0, 0, LOWORD(lParam), HIWORD(lParam) };       m_ax.MoveWindow(&rect); // Resize the control     }     return 0;   } private:   CAxWindow m_ax; }; 


In addition to handling Windows messages, COM controls are COM objects. They expect to be programmed via their COM interfaces. To obtain an interface on the control, CAxWindow provides the QueryControl method:

template <class TBase = CWindow> class CAxWindowT :              public TBase {                                               public:                                                          ...                                                            HRESULT QueryControl(REFIID iid, void** ppUnk) {                 CComPtr<IUnknown> spUnk;                                       HRESULT hr = AtlAxGetControl(m_hWnd, &spUnk);                  if (SUCCEEDED(hr)) hr = spUnk->QueryInterface(iid, ppUnk);     return hr;                                                   }                                                              template <class Q> HRESULT QueryControl(Q** ppUnk)             { return QueryControl(__uuidof(Q), (void**)ppUnk); }         };                                                             


Like QueryHost, QueryControl uses a global function (AtlAxGetControl, in this case) that sends a window message to the AtlAxWin80 window to retrieve an interface, but this time from the hosted control itself. When the control has been created, QueryControl can be used to get at the interfaces of the control:

// Import interface definitions for BullsEye #import "D:\ATLBook\src\atlinternals\Debug\BullsEyeCtl.dll" \   raw_interfaces_only raw_native_types no_namespace named_guids LRESULT CMainWindow::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam,   BOOL& lResult) {   // Create the control   ...   // Set initial BullsEye properties   CComPtr<IBullsEye> spBullsEye;   HRESULT hr = m_ax.QueryControl(&spBullsEye);   if( SUCCEEDED(hr) ) {     spBullsEye->put_Beep(VARIANT_TRUE);     spBullsEye->put_CenterColor(RGB(0, 0, 255));   }   return 0; }; 


Notice the use of the #import statement to pull in the definitions of the interfaces of the control you're programming against. This is necessary if you have only the control's server DLL and the bundled type library but no original IDL (a common occurrence when programming against controls). Notice also the use of the #import statement attributesfor example, raw_interfaces_only. These attributes are used to mimic as closely as possible the C++ language mapping you would have gotten had you used midl.exe on the server's IDL file. Without these attributes, Visual C++ creates a language mapping that uses the compiler-provided wrapper classes (such as _bstr_t, _variant_t, and _com_ptr_t), which are different from the ATL-provided types (such as CComBSTR, CComVariant, and CComPtr). Although the compiler-provided classes have their place, I find that it is best not to mix them with the ATL-provided types. Apparently, the ATL team agrees with me because the ATL Wizardgenerated #import statements also use these attributes (we talk more about the control containmentrelated wizards later).

Sinking Control Events

Not only are you likely to want to program against the interfaces that the control implements, but you're also likely to want to handle events fired by the control. Most controls have an event interface, which, for maximum compatibility with the largest number of clients, is often a dispinterface.[7] For example, the BullsEye control from the last chapter defined the following event interface:

[7] The scripting engines that Internet Explorer (IE) hosts allow you to handle only events defined in a dispinterface.

const int DISPID_ONRINGHIT      = 1; const int DISPID_ONSCORECHANGED = 2; dispinterface _IBullsEyeEvents { properties: methods:   [id(DISPID_ONRINGHIT)]   void OnRingHit(short ringNumber);   [id(DISPID_ONSCORECHANGED)]   void OnScoreChanged(long ringValue); }; 


An implementation of IDispatch is required for a control container to handle events fired on a dispinterface. Implementations of IDispatch are easy if the interface is defined as a dual interface, but they are much harder if it is defined as a raw dispinterface.[8] However, as you recall from Chapter 9, "Connection Points," ATL provides a helper class called IDispEventImpl for implementing an event dispinterface:

[8] ATL's IDispatchImpl can be used only to implement dual interfaces.

template <UINT nID, class T, const IID* pdiid = &IID_NULL,   const GUID* plibid = &GUID_NULL,                           WORD wMajor = 0, WORD wMinor = 0,                          class tihclass = CComTypeInfoHolder>                     class ATL_NO_VTABLE IDispEventImpl :                         public IDispEventSimpleImpl<nID, T, pdiid> {...};        


IDispEventImpl uses a data structure called a sink map, established via the following macros:

#define BEGIN_SINK_MAP(_class) ...                     #define SINK_ENTRY_INFO(id, iid, dispid, fn, info) ... #define SINK_ENTRY_EX(id, iid, dispid, fn) ...         #define SINK_ENTRY(id, dispid, fn) ...                 #define END_SINK_MAP() ...                             


Chapter 9 explains the gory details of these macros, but the gist is that the sink map provides a mapping between a specific object/iid/dispid that defines an event and a member function to handle that event. If the object is a nonvisual one, the sink map can be a bit involved. However, if the object is a COM control, use of IDispEventImpl and the sink map is quite simple, as you're about to see.

To handle events, the container of the controls derives from one instance of IDispEventImpl per control. Notice that the first template parameter of IDisp-EventImpl is an ID. This ID matches the contained control via the child window IDthat is, the nID parameter to Create. This same ID is used in the sink map to route events from a specific control to the appropriate event handler. The child window ID makes IDispEventImpl so simple in the control case. Nonvisual objects have no child window ID, and the mapping is somewhat more difficult (although, as Chapter 9 described, still entirely possible).

So, handling the events of the BullsEye control merely requires an IDisp-EventImpl base class and an appropriately constructed sink map:

const UINT ID_BULLSEYE = 1; class CMainWindow :   public CWindowImpl<CMainWindow, CWindow, CMainWindowTraits>,   public IDispEventImpl<ID_BULLSEYE,                         CMainWindow,                         &DIID__IBullsEyeEvents,                         &LIBID_BullsEyeLib, 1, 0> { public: ...   LRESULT OnCreate(...) {     RECT rect; GetClientRect(&rect);     m_ax.Create(m_hWnd, rect, __T("AtlInternals.BullsEye"),                 WS_CHILD | WS_VISIBLE, 0, ID_BULLSEYE);     ...     return (m_ax.m_hWnd ? 0 : -1);   } BEGIN_SINK_MAP(CMainWindow)   SINK_ENTRY_EX(ID_BULLSEYE, DIID__IBullsEyeEvents,     1, OnRingHit)   SINK_ENTRY_EX(ID_BULLSEYE, DIID__IBullsEyeEvents,     2, OnScoreChanged) END_SINK_MAP()   void __stdcall OnRingHit(short nRingNumber);   void __stdcall OnScoreChanged(LONG ringValue); private:   CAxWindow m_ax; }; 


Notice that the child window control ID (ID_BULLSEYE) is used in four places. The first is the IDispEventImpl base class. The second is the call to Create, marking the control as the same one that will be sourcing events. The last two uses of ID_BULLSEYE are the entries in the sink map, which route events from the ID_BULLSEYE control to their appropriate handlers.

Notice also that the event handlers are marked __stdcall. Remember that we're using IDispEventImpl to implement IDispatch for a specific event interface (as defined by the DIID_IBullsEyeEvents interface identifier). That means that IDispEventImpl must unpack the array of VARIANTs passed to Invoke, push them on the stack, and call our event handler. It does this using type information at runtime, but, as mentioned in Chapter 9, it still has to know about the calling conventionthat is, in what order the parameters should be passed on the stack and who's responsible for cleaning them up. To alleviate any confusion, IDispEventImpl requires that all event handlers have the same calling convention, which __stdcall defines.

When we have IDispEventImpl and the sink map set up, we're not done. Unlike Windows controls, COM controls have no real sense of their "parent." This means that instead of implicitly knowing to whom to send events, as an edit control does, a COM control must be told who wants the events. Because events are established between controls and containers with the connection-point protocol, somebody has to call QueryInterface for IConnectionPointContainer, call FindConnectionPoint to obtain the IConnectionPoint interface, and finally call Advise to establish the container as the sink for events fired by the control. For one control, that's not so much work, and ATL even provides a function called AtlAdvise to help. However, for multiple controls, it can become a chore to manage the communication with each of them. And because we've got a list of all the controls with which we want to establish communications in the sink map, it makes sense to leverage that knowledge to automate the chore. Luckily, we don't even have to do this much because ATL has already done it for us with AtlAdviseSinkMap:

template <class T>                                   inline HRESULT AtlAdviseSinkMap(T* pT, bool bAdvise) 


The first argument to AtlAdviseSinkMap is a pointer to the object that wants to set up the connection points with the objects listed in the sink map. The second parameter is a Boolean determining whether we are setting up or tearing down communication. Because AtlAdviseSinkMap depends on the child window ID to map to a window that already contains a control, both setting up and tearing down connection points must occur when the child windows are still living and contain controls. Handlers for the WM_CREATE and WM_DESTROY messages are excellent for this purpose:

LRESULT CMainWindow::OnCreate(...) {   ... // Create the controls   AtlAdviseSinkMap(this, true); // Establish connection points   return 0; } LRESULT CMainWindow::OnDestroy(...) {   // Controls still live   AtlAdviseSinkMap(this, false); // Tear down connection points   return 0; } 


The combination of IDispEventImpl, the sink map, and the AtlAdviseSinkMap function is all that is needed to sink events from a COM control. However, we can further simplify things. Most controls implement only a single event interface and publish this fact in one of two places. The default source interface can be provided by an implementation of IProvideClassInfo2[9] and can be published in the coclass statement in the IDL (and, therefore, as part of the type library). For example:

[9] The scripting engines that Internet Explorer hosts enable you to handle events only on the default source interface as reported by IProvideClassInfo2.

coclass BullsEye {   [default] interface IBullsEye;   [default, source] dispinterface _IBullsEyeEvents; }; 


If IDispEventImpl is used with IID_NULL as the template parameter (which is the default value) describing the sink interface, ATL does its best to establish communications with the default source interface via a function called AtlGetObjectSourceInterface. This function attempts to obtain the object's default source interface, using the type information obtained via the GetTypeInfo member function of IDispatch. It first attempts the use of IProvideClassInfo2; if that's not available, it digs through the coclass looking for the [default, source] interface. The upshot is that if you want to source the default interface of a control, the parameters to IDispEventImpl are fewer, and you can use the simpler SINK_ENTRY. For example, the following is the complete code necessary to sink events from the BullsEye control:

#import "D:\ATLBook\src\atlinternals\Debug\BullsEyeCtl.dll" \   raw_interfaces_only raw_native_types no_namespace named_guids #define ID_BULLSEYE 1 class CMainWindow :   public CWindowImpl<CMainWindow, CWindow, CMainWindowTraits>,   // Sink the default source interface   public IDispEventImpl< ID_BULLSEYE, CMainWindow> { ...   LRESULT OnCreate(...) {     RECT rect; GetClientRect(&rect);     m_ax.Create(m_hWnd, rect, __T("AtlInternals.BullsEye"),                 WS_CHILD | WS_VISIBLE, 0, ID_BULLSEYE);     AtlAdviseSinkMap(this, true);     return (m_ax.m_hWnd ? 0 : -1);   }   LRESULT CMainWindow::OnDestroy(...)   { AtlAdviseSinkMap(this, false); return 0; } BEGIN_SINK_MAP(CMainWindow)   // Sink events from the default BullsEye event interface   SINK_ENTRY(ID_BULLSEYE, 1, OnRingHit)   SINK_ENTRY(ID_BULLSEYE, 2, OnScoreChanged) END_SINK_MAP()   void __stdcall OnRingHit(short nRingNumber);   void __stdcall OnScoreChanged(LONG ringValue); private:   CAxWindow m_ax; }; 


Property Changes

In addition to a custom event interface, controls often source events on the IPropertyNotifySink interface:

interface IPropertyNotifySink : IUnknown {     HRESULT OnChanged([in] DISPID dispID);       HRESULT OnRequestEdit([in] DISPID dispID); }                                            


A control uses the IPropertyNotifySink interface to ask the container if it's okay to change a property (OnRequestEdit) and to notify the container that a property has been changed (OnChanged). OnRequestEdit is used for data binding, which is beyond the scope of this book, but OnChanged can be a handy notification, especially if the container expects to persist the control and wants to use OnChanged as an is-dirty notification. Even though IPropertyNotifySink is a connection-point interface, it's not a dispinterface, so neither IDispEventImpl nor a sink map is required. Normal C++ inheritance and AtlAdvise will do. For example:

class CMainWindow :   public CWindowImpl<CMainWindow, ...>,   public IPropertyNotifySink { public: ...   // IUnknown, assuming an instance on the stack   STDMETHODIMP QueryInterface(REFIID riid, void** ppv) {     if( riid == IID_IUnknown || riid == IID_IPropertyNotifySink )       *ppv = static_cast<IPropertyNotifySink*>(this);     else return *ppv = 0, E_NOINTERFACE;     return reinterpret_cast<IUnknown*>(*ppv)->AddRef(), S_OK;   }   STDMETHODIMP_(ULONG) AddRef() { return 2; }   STDMETHODIMP_(ULONG) Release() { return 1; }   // IPropertyNotifySink   STDMETHODIMP OnRequestEdit(DISPID dispID) { return S_OK; }   STDMETHODIMP OnChanged(DISPID dispID) {     m_bDirty = true; return S_OK;   } private:   CAxControl m_ax;   bool       m_bDirty; }; 


You have two choices when setting up and tearing down the IPropertyNotifySink connection point with the control. You can use AtlAdvise after the control is successfully created and AtlUnadvise just before it is destroyed. This requires managing the connection point cookie yourself. For example:

LRESULT CMainWindow::OnCreate(...) {   ... // Create the control   // Set up IPropertyNotifySink connection point   CComPtr<IUnknown> spunkControl;   m_ax.QueryControl(spunkControl);   AtlAdvise(spunkControl, this, IID_IPropertyNotifySink,     &m_dwCookie);   return 0; } LRESULT CMainWindow::OnDestroy(...) {   // Tear down IPropertyNotifySink connection point   CComPtr<IUnknown> spunkControl;   m_ax.QueryControl(spunkControl);   AtlUnadvise(spunkControl, IID_IPropertyNotifySink,     &m_dwCookie);   return 0; } 


The second choice is to use the CAxWindow member function CreateControlEx, which allows for a single connection-point interface to be established and the cookie to be managed by the CAxHostWindow object. This simplifies the code considerably:

LRESULT CMainWindow::OnCreate(...) {   ... // Create the control host   // Create the control and set up IPropertyNotifySink   // connection point   m_ax.CreateControlEx(OLESTR("AtlInternals.BullsEye"), 0, 0, 0,                      IID_IPropertyNotifySink, this);   return 0; } 


The connection-point cookie for IPropertyNotifySink is managed by the CAxHostWindow object; when the control is destroyed, the connection is torn down automatically. Although this trick works for only one connection-point interface, this technique, combined with the sink map, is likely all you'll ever need when handling events from controls.

Ambient Properties

In addition to programming the properties of the control, you might want to program the properties of the control's environment, known as ambient properties. For this purpose, CAxHostWindow implements the IAxWinAmbientDispatch interface:

interface IAxWinAmbientDispatch : IDispatch {                       [propput]                                                         HRESULT AllowWindowlessActivation([in]VARIANT_BOOL b);            [propget]                                                         HRESULT AllowWindowlessActivation(                                  [out,retval]VARIANT_BOOL* pb);                                  // DISPID_AMBIENT_BACKCOLOR                                       [propput, id(DISPID_AMBIENT_BACKCOLOR)]                           HRESULT BackColor([in]OLE_COLOR clrBackground);                   [propget, id(DISPID_AMBIENT_BACKCOLOR)]                           HRESULT BackColor([out,retval]OLE_COLOR* pclrBackground);         // DISPID_AMBIENT_FORECOLOR                                       [propput, id(DISPID_AMBIENT_FORECOLOR)]                           HRESULT ForeColor([in]OLE_COLOR clrForeground);                   [propget, id(DISPID_AMBIENT_FORECOLOR)]                           HRESULT ForeColor([out,retval]OLE_COLOR* pclrForeground);         // DISPID_AMBIENT_LOCALEID                                        [propput, id(DISPID_AMBIENT_LOCALEID)]                            HRESULT LocaleID([in]LCID lcidLocaleID);                          [propget, id(DISPID_AMBIENT_LOCALEID)]                            HRESULT LocaleID([out,retval]LCID* plcidLocaleID);                // DISPID_AMBIENT_USERMODE                                        [propput, id(DISPID_AMBIENT_USERMODE)]                            HRESULT UserMode([in]VARIANT_BOOL bUserMode);                     [propget, id(DISPID_AMBIENT_USERMODE)]                            HRESULT UserMode([out,retval]VARIANT_BOOL* pbUserMode);           // DISPID_AMBIENT_DISPLAYASDEFAULT                                [propput, id(DISPID_AMBIENT_DISPLAYASDEFAULT)]                    HRESULT DisplayAsDefault([in]VARIANT_BOOL bDisplayAsDefault);     [propget, id(DISPID_AMBIENT_DISPLAYASDEFAULT)]                    HRESULT DisplayAsDefault(                                           [out,retval]VARIANT_BOOL* pbDisplayAsDefault);                  // DISPID_AMBIENT_FONT                                            [propput, id(DISPID_AMBIENT_FONT)]                                HRESULT Font([in]IFontDisp* pFont);                               [propget, id(DISPID_AMBIENT_FONT)]                                HRESULT Font([out,retval]IFontDisp** pFont);                      // DISPID_AMBIENT_MESSAGEREFLECT                                  [propput, id(DISPID_AMBIENT_MESSAGEREFLECT)]                      HRESULT MessageReflect([in]VARIANT_BOOL bMsgReflect);             [propget, id(DISPID_AMBIENT_MESSAGEREFLECT)]                      HRESULT MessageReflect([out,retval]VARIANT_BOOL* pbMsgReflect);   // DISPID_AMBIENT_SHOWGRABHANDLES                                 [propget, id(DISPID_AMBIENT_SHOWGRABHANDLES)]                     HRESULT ShowGrabHandles(VARIANT_BOOL* pbShowGrabHandles);         // DISPID_AMBIENT_SHOWHATCHING                                    [propget, id(DISPID_AMBIENT_SHOWHATCHING)]                        HRESULT ShowHatching(VARIANT_BOOL* pbShowHatching);               // IDocHostUIHandler Defaults                                     ...                                                             };                                                                


QueryHost can be used on a CAxWindow to obtain the IAxWinAmbientDispatch interface so that these ambient properties can be changed. For example:

LRESULT CMainWindow::OnSetGreenBackground(...) {   // Set up green ambient background   CComPtr<IAxWinAmbientDispatch> spAmbient;   hr = m_ax.QueryHost(&spAmbient);   if( SUCCEEDED(hr) ) {     spAmbient->put_BackColor(RGB(0, 255, 0));   }   return 0; } 


Whenever an ambient property is changed, the control is notified via its implementation of the IOleControl member function OnAmbientPropertyChange. The control can then QueryInterface any of its container interfaces for IDispatch to obtain the interface for retrieving the ambient properties (which is why IAxWinAmbientDispatch is a dual interface).

Hosting Property Pages

If your container is a development environment, you might want to allow the user to show the control's property pages. This can be accomplished by calling the IOleObject member function DoVerb, passing in the OLEIVERB_PROPERTIES verb ID:

LRESULT CMainWindow::OnEditProperties(...) {   CComPtr<IOleObject> spoo;   HRESULT hr = m_ax.QueryControl(&spoo);   if( SUCCEEDED(hr) ) {     CComPtr<IOleClientSite> spcs; m_ax.QueryHost(&spcs);     RECT rect; m_ax.GetClientRect(&rect);     hr = spoo->DoVerb(OLEIVERB_PROPERTIES, 0, spcs,       -1, m_ax.m_hWnd, &rect);     if( FAILED(hr) )       MessageBox(__T("Properties unavailable"), __T("Error"));   }   return 0; } 


If you want to add your own property pages to those of the control or you want to show the property pages of a control that doesn't support the OLEIVERB_PROPERTIES verb, you can take matters into your own hands with a custom property sheet. First, you need to ask the control for its property pages via the ISpecifyPropertyPages member function GetPages. Second, you might want to augment the control's property pages with your own. Finally, you show the property pages (each a COM object with its own CLSID) via the COM global function OleCreatePropertyFrame, as demonstrated in the ShowProperties function I developed for this purpose:

HRESULT ShowProperties(IUnknown* punkControl, HWND hwndParent) {   HRESULT hr = E_FAIL;   // Ask the control to specify its property pages   CComQIPtr<ISpecifyPropertyPages> spPages = punkControl;   if (spPages) {     CAUUID pages;     hr = spPages->GetPages(&pages);     if( SUCCEEDED(hr) ) {       // TO DO: Add your custom property pages here       CComQIPtr<IOleObject> spObj = punkControl;       if( spObj ) {         LPOLESTR pszTitle = 0;         spObj->GetUserType(USERCLASSTYPE_SHORT, &pszTitle);         // Show the property pages         hr = OleCreatePropertyFrame(hwndParent, 10, 10, pszTitle,           1, &punkControl, pages.cElems,           pages.pElems, LOCALE_USER_DEFAULT, 0, 0);         CoTaskMemFree(pszTitle);       }       CoTaskMemFree(pages.pElems);     }   }   return hr; } 


The ShowProperties function can be used instead of the call to DoVerb. For example:

LRESULT CMainWindow::OnEditProperties(...) {   CComPtr<IUnknown> spunk;   if( SUCCEEDED(m_ax.QueryControl(&spunk)) ) {     if( FAILED(ShowProperties(spunk, m_hWnd)) ) {     MessageBox(__T("Properties unavailable"), __T("Error"));    }   }   return 0; } 


Either way, if the control's property pages are shown and the Apply or OK button is pressed, your container should receive one IPropertyNotifySink call per property that has changed.

Persisting a Control

You might want to persist between application sessions. As discussed in Chapter 7, "Persistence in ATL," you can do this with any number of persistence interfaces. Most controls implement IPersistStreamInit (although IPersistStream is a common fallback). For example, saving a control to a file can be done with a stream in a structured storage document:

bool CMainWindow::Save(LPCOLESTR pszFileName) {   // Make sure object can be saved   // Note: Our IPersistStream interface pointer could end up   // holding an IPersistStreamInit interface. This is OK   // since IPersistStream is a layout-compatible subset of   // IPersistStreamInit.   CComQIPtr<IPersistStream> spPersistStream;   HRESULT hr = m_ax.QueryControl(&spPersistStream);   if( FAILED(hr) ) {     hr = m_ax.QueryControl(IID_IPersistStreamInit,       (void**)&spPersistStream);     if( FAILED(hr) ) return false;   }   // Save object to stream in a storage   CComPtr<IStorage> spStorage;   hr = StgCreateDocfile(pszFileName,     STGM_DIRECT | STGM_WRITE |     STGM_SHARE_EXCLUSIVE | STGM_CREATE,     0, &spStorage);   if( SUCCEEDED(hr) ) {     CComPtr<IStream> spStream;     hr = spStorage->CreateStream(OLESTR("Contents"),       STGM_DIRECT | STGM_WRITE |       STGM_SHARE_EXCLUSIVE | STGM_CREATE,       0, 0, &spStream);   if( SUCCEEDED(hr) ) {     // Get and store the CLSID     CLSID clsid;     hr = spPersistStream->GetClassID(&clsid);     if( SUCCEEDED(hr) ) {       hr = spStream->Write(&clsid, sizeof(clsid), 0);       // Save the object       hr = spPersistStream->Save(spStream, TRUE);     }    }   }   if( FAILED(hr) ) return false;   return true; } 


Restoring a control from a file is somewhat easier because both the CreateControl and the CreateControlEx member functions of CAxWindow take an IStream interface pointer to use for persistence. For example:

bool CMainWindow::Open(LPCOLESTR pszFileName) {   // Open object a stream in the storage   CComPtr<IStorage>   spStorage;   CComPtr<IStream>    spStream;   HRESULT                   hr;   hr = StgOpenStorage(pszFileName, 0,     STGM_DIRECT | STGM_READ | STGM_SHARE_EXCLUSIVE,     0, 0, &spStorage);   if( SUCCEEDED(hr) ) {     hr = spStorage->OpenStream(OLESTR("Contents"), 0,       STGM_DIRECT | STGM_READ | STGM_SHARE_EXCLUSIVE,       0, &spStream);   }   if( FAILED(hr) ) return false;   // Read a CLSID from the stream   CLSID clsid;   hr = spStream->Read(&clsid, sizeof(clsid), 0);   if( FAILED(hr) ) return false;   RECT rect; GetClientRect(&rect);   OLECHAR szClsid[40];   StringFromGUID2(clsid, szClsid, lengthof(szClsid));   // Create the control's host window   if( !m_ax.Create(m_hWnd, rect, 0, WS_CHILD | WS_VISIBLE, 0,                    ID_CHILD_CONTROL) {     return false;   }   // Create the control, persisting from the stream   hr = m_ax.CreateControl(szClsid, spStream);   if( FAILED(hr) ) return false;   return true; } 


When a NULL IStream interface pointer is provided to either CreateControl or CreateControlEx, ATL attempts to call the IPersistStreamInit member function InitNew to make sure that either InitNew or Load is called, as appropriate.

Accelerator Translations

It's common for contained controls to contain other controls. For keyboard accelerators (such as the Tab key) to provide for navigation between controls, the main message loop must be augmented with a call to each window hosting a control, to allow it to pretranslate the message as a possible accelerator. This functionality must ask the host of the control with focus if it wants to handle the message. If the control does handle the message, no more handling need be done on that message. Otherwise, the message processing can proceed as normal. A typical implementation of a function to attempt to route messages from the container window to the control itself (whether it's a windowed or a windowless control) is shown here:

BOOL CMainWnd:: PreTranslateAccelerator(MSG* pMsg) {   // Accelerators are only keyboard or mouse messages   if ((pMsg->message < WM_KEYFIRST ||     pMsg->message > WM_KEYLAST) &&     (pMsg->message < WM_MOUSEFIRST ||     pMsg->message > WM_MOUSELAST))     return FALSE;   // Find a direct child of this window from the window that has   // focus. This will be AxAtlWin80 window for the hosted   // control.   HWND hWndCtl = ::GetFocus();   if( IsChild(hWndCtl) && ::GetParent(hWndCtl) != m_hWnd ) {   do hWndCtl = ::GetParent(hWndCtl);   while( ::GetParent(hWndCtl) != m_hWnd );   }   // Give the control (via the AtlAxWin80) a chance to   // translate this message   if (::SendMessage(hWndCtl, WM_FORWARDMSG, 0, (LPARAM)pMsg) )     return TRUE;   // Check for dialog-type navigation accelerators   return IsDialogMessage(pMsg); } 


The crux of this function forwards the message to the AtlAxWin80 via the WM_FORWARDMSG message. This message is interpreted by the host window as an attempt to let the control handle the message, if it so desires. This message is forwarded to the control via a call to the IOleInPlaceActiveObject member function translateAccelerator. The PreTranslateAccelerator function should be called from the application's main message pump like this:

int WINAPI WinMain(...) {   ...   CMainWindow wndMain;   ...   HACCEL haccel = LoadAccelerators(_Module.GetResourceInstance(),     MAKEINTRESOURCE(IDC_MYACCELS));   MSG msg;   while( GetMessage(&msg, 0, 0, 0) ) {     if( !TranslateAccelerator(msg.hwnd, haccel, &msg) &&       !wndMain.PreTranslateAccelerator(&msg) ) {       TranslateMessage(&msg);       DispatchMessage(&msg);     }   }   ... } 


The use of a PreTranslateAccelerator function on every window that contains a control gives the keyboard navigation keys a much greater chance of working, although the individual controls have to cooperate, too.




ATL Internals. Working with ATL 8
ATL Internals: Working with ATL 8 (2nd Edition)
ISBN: 0321159624
EAN: 2147483647
Year: 2004
Pages: 172

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