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.
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.
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).
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.
|