previous page
next page

Composite Controls

Declarative User Interfaces for Controls

There's beauty in using a dialog resource for managing the user interface (UI) of a window. Instead of writing pages of code to create, initialize, and place controls on a rectangle of gray, we can use the resource editor to do it for us. At design time, we lay out the size and location of the elements of the UI, and the ATL-augmented dialog manager is responsible for the heavy lifting. This is an extremely useful mode of UI development, and it's not limited to dialogs. It can also be used for composite controls. A composite control is a COM control that uses a dialog resource to lay out its UI elements. These UI elements can be Windows controls or other COM controls.

To a Windows control, a composite control appears as a parent window. To a COM control, the composite control appears as a control container. To a control container, the composite control appears as a control itself. To the developer of the control, a composite control is all three. In fact, if you combine all the programming techniques from Chapter 10 with the techniques I've shown you thus far in this chapter, you have a composite control.

CComCompositeControl

ATL provides support for composite controls via the CComCompositeControl base class:

template <class T>                                  
class CComCompositeControl :                        
  public CComControl< T, CAxDialogImpl< T > > {...};

Notice that CComCompositeControl derives from both CComControl and CAxDialogImpl, combining the functionality of a control and the drawing of the dialog manager, augmented with the COM control hosting capabilities of AtlAxWin80. Both of the wizard-generated composite control types (composite control and lite composite control) derive from CComCompositeControl instead of CComControl and provide an IDD symbol mapping to the control's dialog resource:

class ATL_NO_VTABLE CDartBoard :                             
  public CComObjectRootEx<CComSingleThreadModel>,            
  public IDispatchImpl<IDartBoard, &IID_IDartBoard,          
    &LIBID_CONTROLSLib>,                                     
  public CComCompositeControl<CDartBoard>,                   
  ...                                                        
  public CComCoClass<CDartBoard, &CLSID_DartBoard> {         
public:                                                      
...                                                          
  enum { IDD = IDD_DARTBOARD };                              
                                                             
  CDartBoard() {                                             
    // Composites can't be windowless
    m_bWindowOnly = TRUE;                                    
                                                             
    // Calculate natural extent based on dialog resource size
    CalcExtent(m_sizeExtent);                                
  }                                                          
...                                                          
};                                                           

Notice that the construction of the composite control sets the m_bWindowOnly flag, disabling windowless operation. The control's window must be of the same class as that managed by the dialog manager. Also notice that the m_sizeExtent member variable is set by a call to CalcExtent, a helper function provided in CComCompositeControl. CalcExtent is used to set the initial preferred size of the control to be exactly that of the dialog box resource.

Composite Control Drawing

Because a composite control is based on a dialog resource, and its drawing will be managed by the dialog manager and the child controls, no real drawing chores have to be performed. Instead, setting the state of the child controls, which causes them to redraw, is all that's required to update the visual state of a control.

For example, the DartBoard example available with the source code of this book uses a dialog resource to lay out its elements, as shown in Figure 12.6.

Figure 12.6. DartBoard composite control dialog resource


This dialog resource holds a BullsEye control, two static controls, and a button. When the user clicks on a ring of the target, the score is incremented. When the Reset button is pressed, the score is cleared. The composite control takes care of all the logic, but the dialog manager performs the drawing.

However, when a composite control is shown but not activated, the composite control's window is not created and the drawing must be done manually. For example, a composite control must perform its own drawing when hosted in a dialog resource during the design mode of the Visual C++ resource editor. The ATL Object Wizard generates an implementation of OnDraw that handles this case, as shown in Figure 12.7.

Figure 12.7. Default CComCompositeControl's inactive OnDraw implementation


I find this implementation somewhat inconvenient because it doesn't show the dialog resource as I'm using the control. Specifically, it doesn't show the size of the resource. Toward that end, I've provided another implementation that is a bit more helpful (see Figure 12.8).

Figure 12.8. Updated CComCompositeControl's inactive OnDraw implementation


This implementation shows the light gray area as the recommended size of the control based on the control's dialog resource. The dark gray area is the part of the control that is still managed by the control but that is outside the area managed for the control by the dialog manager. The updated OnDraw implementation is shown here:

// Draw an inactive composite control
virtual HRESULT OnDraw(ATL_DRAWINFO& di) {
  if( m_bInPlaceActive ) return S_OK;

  // Draw background rectangle
  SelectObject(di.hdcDraw, GetStockObject(BLACK_PEN));
  SelectObject(di.hdcDraw, GetStockObject(GRAY_BRUSH));
  Rectangle(di.hdcDraw, di.prcBounds->left, di.prcBounds->top,
            di.prcBounds->right, di.prcBounds->bottom);

  // Draw proposed dialog rectangle
  SIZE sizeMetric; CalcExtent(sizeMetric);
  SIZE sizeDialog; AtlHiMetricToPixel(&sizeMetric, &sizeDialog);
  SIZE sizeBounds = {
    di.prcBounds->right - di.prcBounds->left,
    di.prcBounds->bottom - di.prcBounds->top };
  SIZE sizeDialogBounds = {
    min(sizeDialog.cx, sizeBounds.cx),
    min(sizeDialog.cy, sizeBounds.cy) };
  RECT rectDialogBounds = {
    di.prcBounds->left, di.prcBounds->top,
    di.prcBounds->left + sizeDialogBounds.cx,
    di.prcBounds->top + sizeDialogBounds.cy };
  SelectObject(di.hdcDraw, GetStockObject(LTGRAY_BRUSH));
  Rectangle(di.hdcDraw,
    rectDialogBounds.left, rectDialogBounds.top,
    rectDialogBounds.right, rectDialogBounds.bottom);

  // Report natural and current size of dialog resource
  SetTextColor(di.hdcDraw, ::GetSysColor(COLOR_WINDOWTEXT));
  SetBkMode(di.hdcDraw, TRANSPARENT);

  TCHAR sz[256];
  wsprintf(sz, __T("Recommended: %d x %d\r\nCurrent: %d x %d"),
    sizeDialog.cx, sizeDialog.cy,
    sizeBounds.cx, sizeBounds.cy);

  DrawText(di.hdcDraw, sz, -1, &rectDialogBounds, DT_CENTER);

  return S_OK;
}

Using a dialog resource and deriving from CComCompositeControl are the only differences between a control that manages its own UI elements and one that leans on the dialog manager. A composite control is a powerful way to lay out a control's UI elements at design time. However, if you really want to wield the full power of a declarative UI when building a control, you need an HTML control.


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