previous page
next page

Hosting a Control in a Dialog

Inserting a Control into a Dialog Resource

So far, I've discussed the basics of control containment using a frame window as a control container. An even more common place to contain controls is the ever-popular dialog. For quite a while, the Visual C++ resource editor has allowed a control to be inserted into a dialog resource by right-clicking a dialog resource and choosing Insert ActiveX Control. As of Visual C++ 6.0, ATL supports creating dialogs that host the controls inserted into dialog resources.

To add an ActiveX Control to a dialog, you must first add it to the Visual Studio toolbox. This is pretty simple. Get the toolbox onto the screen, and then right-click and select Choose Items. This brings up the Choose Toolbox Items dialog box, shown in Figure 12.3.

Figure 12.3. Choose Toolbox Items


Select the ActiveX controls you want, and they're in the toolbox to be added to dialogs. To add the control, simply drag and drop it from the toolbox onto the dialog editor.

The container example provided as part of this chapter has a simple dialog box with a BullsEye control inserted, along with a couple static controls and a button. This is what that dialog resource looks like in the .rc file:

IDD_BULLSEYE DIALOG DISCARDABLE 0, 0, 342, 238
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "BullsEye"
FONT 8, "MS Sans Serif"
BEGIN
    CONTROL "",IDC_BULLSEYE,
        "{7DC59CC5-36C0-11D2-AC05-00A0C9C8E50D}",
        WS_TABSTOP,7,7,269,224
    LTEXT "&Score:",IDC_STATIC,289,7,22,8
    CTEXT "Static",IDC_SCORE,278,18,46,14,
        SS_CENTERIMAGE | SS_SUNKEN
    PUSHBUTTON "Close",IDCANCEL,276,41,50,14
END

Control Initialization

Notice that the window text part of the CONTROL resource is a CLSIDspecifically, the CLSID of the BullsEye control. This window text is passed to an instance of the AtlAxWin80 window class to determine the type of control to create. In addition, another part of the .rc file maintains a separate resource called a DLGINIT resource, which is identified with the same ID as the BullsEye control on the dialog: IDC_BULLSEYE. This resource contains the persistence information, converted to text format, that is handed to the BullsEye control at creation time (via IPersistStreamInit):

IDD_BULLSEYE DLGINIT
BEGIN
    IDC_BULLSEYE, 0x376, 154, 0
0x0026, 0x0000, 0x007b, 0x0039, 0x0035,
...
0x0000, 0x0040, 0x0000, 0x0020, 0x0000,
    0
END

Because most folks prefer not to enter this information directly, the properties show up in the Visual Studio properties window. Simply click on the control and bring up the Property tab. Figure 12.4 shows the BullsEye properties window.

Figure 12.4. VS Dialog Editor properties window for BullsEye control


Also note that the RingCount property has an ellipsis button next to it. If you click that button, Visual Studio brings up the property page for that property.

The DLGINIT resource for each control is constructed by asking each control for IPersistStreamInit, calling Save, converting the result to a text format, and dumping it into the .rc file. In this way, all information set at design time is automatically restored at runtime.

Sinking Control Events in a Dialog

Recall that sinking control events requires adding one IDispEventImpl per control to the list of base classes of your dialog class and populating the sink map. Although this has to be done by hand if a window is the container, it can be performed automatically if a dialog is to be the container. By right-clicking on the control and choosing Events, you can choose the events to handle; the IDispEventImpl and sink map entries are added for you. Figure 12.5 shows the Event Handlers dialog box.

Figure 12.5. BullsEye Event Handlers dialog box


The Event Handler Wizard adds the IDispEventImpl classes and manages the sink map. The CAxDialogImpl class (discussed next) handles the call to AtlAdvise-SinkMap that actually connects to the events in the control.

CAxDialogImpl

In Chapter 10, I discussed the CDialogImpl class, which, unfortunately, is not capable of hosting controls. Recall that the member function wrappers DoModal and Create merely call the Win32 functions DialogBoxParam and CreateDialogParam. Because the built-in dialog box manager window class has no idea how to host controls, ATL has to perform some magic on the dialog resource. Specifically, it must preprocess the dialog box resource looking for CONTROL enTRies and replacing them with entries that will create an instance of an AtlAxWin80 window. AtlAxWin80 then uses the name of the window to create the control and the DLGINIT data to initialize it, providing all the control hosting functionality we've spent most of this chapter dissecting. To hook up this preprocessing step when hosting controls in dialogs, we use the CAxDialogImpl base class:

template <class T, class TBase /* = CWindow */>                     
class CAxDialogImpl : public CDialogImplBaseT< TBase > {            
public:                                                             
...                                                                 
  static INT_PTR CALLBACK DialogProc(HWND hWnd, UINT uMsg,          
    WPARAM wParam, LPARAM lParam);                                  
                                                                    
  // modal dialogs                                                  
  INT_PTR DoModal(HWND hWndParent = ::GetActiveWindow(),            
    LPARAM dwInitParam = NULL) {                                    
    _AtlWinModule.AddCreateWndData(&m_thunk.cd,                     
      (CDialogImplBaseT< TBase >*)this);                            
      return AtlAxDialogBox(_AtlBaseModule.GetResourceInstance(),   
        MAKEINTRESOURCE(static_cast<T*>(this)->IDD),                
        hWndParent, T::StartDialogProc, dwInitParam);               
  }                                                                 
                                                                    
  BOOL EndDialog(int nRetCode) {                                    
    return ::EndDialog(m_hWnd, nRetCode);                           
  }                                                                 
                                                                    
  // modeless dialogs                                               
  HWND Create(HWND hWndParent, LPARAM dwInitParam = NULL) {         
    _AtlWinModule.AddCreateWndData(&m_thunk.cd,                     
      (CDialogImplBaseT< TBase >*)this);                            
    HWND hWnd = AtlAxCreateDialog(                                  
      _AtlBaseModule.GetResourceInstance(),                         
      MAKEINTRESOURCE(static_cast<T*>(this)->IDD),                  
      hWndParent, T::StartDialogProc, dwInitParam);                 
    return hWnd;                                                    
}                                                                   
                                                                    
  // for CComControl                                                
  HWND Create(HWND hWndParent, RECT&,                               
    LPARAM dwInitParam = NULL) {                                    
    return Create(hWndParent, dwInitParam);                         
  }                                                                 
  BOOL DestroyWindow() {                                            
    return ::DestroyWindow(m_hWnd);                                 
  }                                                                 
                                                                    
  // Event handling support and Message map                         
  HRESULT AdviseSinkMap(bool bAdvise) {                             
    if(!bAdvise && m_hWnd == NULL) {                                
    // window is gone, controls are already unadvised               
      return S_OK;                                                  
    }                                                               
    HRESULT hRet = E_NOTIMPL;                                       
    __if_exists(T::_GetSinkMapFinder) {                             
      T* pT = static_cast<T*>(this);                                
      hRet = AtlAdviseSinkMap(pT, bAdvise);                         
    }                                                               
    return hRet;                                                    
  }                                                                 
                                                                    
  typedef CAxDialogImpl< T, TBase > thisClass;                      
  BEGIN_MSG_MAP(thisClass)                                          
    MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)                    
    MESSAGE_HANDLER(WM_DESTROY, OnDestroy)                          
  END_MSG_MAP()                                                     
                                                                    
  virtual HRESULT CreateActiveXControls     (UINT nID) {            
    // Load dialog template and InitData                            
    // Walk through template and create ActiveX controls            
    // Code omitted for clarity                                     
  }                                                                 
                                                                    
  LRESULT OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/,            
    LPARAM /*lParam*/, BOOL& bHandled) {                            
    // initialize controls in dialog with DLGINIT                   
    // resource section                                             
    ExecuteDlgInit(static_cast<T*>(this)->IDD);                     
    AdviseSinkMap(true);                                            
    bHandled = FALSE;                                               
    return 1;                                                       
  }                                                                 
                                                                    
  LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/,               
    LPARAM /*lParam*/, BOOL& bHandled) {                            
    AdviseSinkMap(false);                                           
    bHandled = FALSE;                                               
    return 1;                                                       
  }                                                                 
                                                                    
    // Accelerator handling - needs to be called from a message loop
    BOOL IsDialogMessage(LPMSG pMsg) {                              
         // Code omitted for clarity                                
    }                                                               
};                                                                  
                                                                    
template <class T, class TBase>                                     
INT_PTR CALLBACK CAxDialogImpl< T, TBase >::DialogProc(             
  HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {             
  CAxDialogImpl< T, TBase >* pThis = (                              
    CAxDialogImpl< T, TBase >*)hWnd;                                
  if (uMsg == WM_INITDIALOG) {                                      
    HRESULT hr;                                                     
    if (FAILED(hr = pThis->CreateActiveXControls(                   
      pThis->GetIDD()))) {                                          
      pThis->DestroyWindow();                                       
      SetLastError(hr & 0x0000FFFF);                                
      return FALSE;                                                 
    }                                                               
  }                                                                 
  return CDialogImplBaseT< TBase >::DialogProc(                     
    hWnd, uMsg, wParam, lParam);                                    
}                                                                   

Notice that the DoModal and Create wrapper functions call AtlAxDialogBox and AtlAxCreateDialog instead of DialogBoxParam and CreateDialogParam, respectively. These functions take the original dialog template (including the ActiveX control information that Windows can't handle) and create a second in-memory template that has those controls stripped out. The stripped dialog resource is then passed to the appropriate DialogBoxParam function so that Windows can do the heavy lifting. The actual creation of the ActiveX controls is done in the CreateActiveXControls method, which is called as part of the WM_INITDIALOG processing.

Using CAxDialogImpl as the base class, we can have a dialog that hosts COM controls like this:

class CBullsEyeDlg :
  public CAxDialogImpl<CBullsEyeDlg>,
  public IDispEventImpl<IDC_BULLSEYE, CBullsEyeDlg> {
public:
BEGIN_MSG_MAP(CBullsEyeDlg)
  MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
  MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
  COMMAND_ID_HANDLER(IDCANCEL, OnCancel)
END_MSG_MAP()

BEGIN_SINK_MAP(CBullsEyeDlg)
  SINK_ENTRY(IDC_BULLSEYE, 0x2, OnScoreChanged)
END_SINK_MAP()

  // Map this class to a specific dialog resource
  enum { IDD = IDD_BULLSEYE };

  // Hook up connection points
  LRESULT OnInitDialog(...)
  { AtlAdviseSinkMap(this, true); return 0; }

  // Tear down connection points
  LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam,
    BOOL& bHandled)
  { AtlAdviseSinkMap(this, false); return 0; }
  // Window control event handlers
  LRESULT OnCancel(WORD, UINT, HWND, BOOL&);

  // COM control event handlers
  VOID __stdcall OnScoreChanged(LONG ringValue);
};

Notice that, just like a normal dialog, the message map handles messages for the dialog itself (such as WM_INITDIALOG and WM_DESTROY) and also provides a mapping between the class and the dialog resource ID (via the IDD symbol). The only thing new is that, because we've used CAxDialogImpl as the base class, the COM controls are created as the dialog is created.

Attaching a CAxWindow

During the life of the dialog, you will likely need to program against the interfaces of the contained COM controls, which means you'll need some way to obtain an interface on a specific control. One way to do this is with an instance of CAxWindow. Because ATL has created an instance of the AtlAxWin80 window class for each of the COM controls on the dialog, you use the Attach member function of a CAxWindow to attach to a COM control; thereafter, you use the CAxWindow object to manipulate the host window. This is very much like you'd use the Attach member function of the window wrapper classes discussed in Chapter 10 to manipulate an edit control. After you've attached a CAxWindow object to an AtlAxWin80 window, you can use the member functions of CAxWindow to communicate with the control host window. Recall the QueryControl member function to obtain an interface from a control, as shown here:

class CBullsEyeDlg :
  public CAxDialogImpl<CBullsEyeDlg>,
  public IDispEventImpl<IDC_BULLSEYE, CBullsEyeDlg> {
public:
...
  LRESULT OnInitDialog(...) {
    // Attach to the BullsEye control
    m_axBullsEye.Attach(GetDlgItem(IDC_BULLSEYE));

    // Cache BullsEye interface
    m_axBullsEye.QueryControl(&m_spBullsEye);
    ...
    return 0;
  }
...
private:
  CAxWindow                m_axBullsEye;
  CComPtr<IBullsEye> m_spBullsEye;
};

In this example, I've cached both the HWND to the AtlAxWin80, for continued communication with the control host window, and one of the control's interfaces, for communication with the control itself. If you need only an interface, not the HWND, you might want to consider using GetdlgControl instead.

GetDlgControl

Because the CDialogImpl class derives from CWindow, it provides the GetdlgItem function to retrieve the HWND of a child window, given the ID of the child. Likewise, CWindow provides a GetdlgControl member function, but to retrieve an interface pointer instead of an HWND:

HRESULT GetDlgControl(int nID, REFIID iid, void** ppCtrl) {   
  if (ppCtrl == NULL) return E_POINTER;                       
  *ppCtrl = NULL;                                             
  HRESULT hr = HRESULT_FROM_WIN32(ERROR_CONTROL_ID_NOT_FOUND);
  HWND hWndCtrl = GetDlgItem(nID);                            
  if (hWndCtrl != NULL) {                                     
    *ppCtrl = NULL;                                           
    CComPtr<IUnknown> spUnk;                                  
    hr = AtlAxGetControl(hWndCtrl, &spUnk);                   
    if (SUCCEEDED(hr))                                        
      hr = spUnk->QueryInterface(iid, ppCtrl);                
  }                                                           
  return hr;                                                  
}                                                             

The GetdlgControl member function calls the AtlAxGetControl function, which uses the HWND of the child window to retrieve an IUnknown interface. AtlAxGetControl does this by sending the WM_GETCONTROL window message that windows of the class AtlAxWin80 understand. If the child window is not an instance of the AtlAxWin80 window class, or if the control does not support the interface being requested, GetdlgControl returns a failed HRESULT. Using GetdlgControl simplifies the code to cache an interface on a control considerably:

LRESULT OnInitDialog(...) {
  // Cache BullsEye interface
  GetDlgControl(IDC_BULLSEYE, IID_IBullsEye,
    (void**)&m_spBullsEye);
  ...
  return 0;
}

The combination of the CAxDialogImpl class, the control-containment wizards in Visual C++, and the GetdlgControl member function makes managing COM controls in a dialog much like managing Windows controls.


previous page
next page
Converted from CHM to HTML with chm2web Pro 2.75 (unicode)