Common Control Fundamentals

[Previous] [Next]

MFC provides classes to wrap the common controls just as it provides classes to wrap the core control types implemented in User.exe. The following table shows the 20 types of common controls, the WNDCLASSes on which they're based, and the corresponding MFC classes. It also shows aliases for those WNDCLASSes defined in the header file Commctrl.h. Image lists and property sheets don't have WNDCLASSes because they're not controls in the strict sense of the word, but they're nearly always counted among the common controls because their code resides in Comctl32.dll. You'll sometimes see drag list boxes shown with the common controls. I didn't include them here because drag list boxes aren't stand-alone controls; they're conventional list boxes that are converted into "drag" list boxes by a function in Comctl32.dll. MFC provides a convenient implementation of drag list boxes in CDragListBox, so for more information, see the documentation for CDragListBox.

The Common Controls

Control Type WNDCLASS WNDCLASS Alias MFC Class
Animation "SysAnimate32" ANIMATE_CLASS CAnimateCtrl
ComboBoxEx* "ComboBoxEx32" WC_COMBOBOXEX CComboBoxEx
Date-Time* "SysDateTimePick32" DATETIMEPICK_CLASS CDateTimeCtrl
Header "SysHeader32" WC_HEADER CHeaderCtrl
Hotkey "msctls_hotkey32" HOTKEY_CLASS CHotKeyCtrl
Image list N/A N/A CImageList
IP address** "SysIPAddress32" WC_IPADDRESS CIPAddressCtrl
List view "SysListView32" WC_LISTVIEW CListCtrl
Month calendar* "SysMonthCal32" MONTHCAL_CLASS CMonthCalCtrl
Progress "msctls_progress32" PROGRESS_CLASS CProgressCtrl
Property sheet N/A N/A CPropertySheet
Rebar* "ReBarWindow32" REBARCLASSNAME CReBarCtrl
Rich edit "RichEdit20A" (ANSI) or "RichEdit20W" (Unicode) RICHEDIT_CLASS CRichEditCtrl
Slider "msctls_trackbar32" TRACKBAR_CLASS CSliderCtrl
Spin button "msctls_updown32" UPDOWN_CLASS CSpinButtonCtrl
Status bar "msctls_statusbar32" STATUSCLASSNAME CStatusBarCtrl
Tab "SysTabControl32" WC_TABCONTROL CTabCtrl
Toolbar "ToolbarWindow32" TOOLBARCLASSNAME CToolBarCtrl
ToolTip "tooltips_class32" TOOLTIPS_CLASS CToolTipCtrl
Tree view "SysTreeView32" WC_TREEVIEW CTreeCtrl

* Requires Internet Explorer 3.0 or later.

** Requires Internet Explorer 4.0 or later.

As you can see from the table, some of the common controls are only supported on systems that have a particular version of Internet Explorer installed. That's because when you install Internet Explorer, the setup program silently upgrades Comctl32.dll, too. Many times in this chapter I'll say something like "This style is only supported on systems equipped with Internet Explorer 3.0 or later" or "This feature requires Internet Explorer 4.0." In truth, it's not Internet Explorer that's required but the version of Comctl32.dll that comes with that version of Internet Explorer. Because installing a more recent version of Internet Explorer is presently the only legal way to get the latest version of Comctl32.dll, Internet Explorer is a reasonable basis for documenting version dependencies.

Given the common controls' myriad dependencies on the version of Comctl32.dll that's installed and the fact that some systems don't have Internet Explorer installed at all, you might wonder how to determine at run time whether a given feature is supported provided that you know what version of Comctl32.dll it requires. Here's a simple routine that returns Comctl32.dll's major and minor version numbers. It returns 4.0 if the Comctl32.dll installed on the host system is one that predates Internet Explorer 3.0, and 0.0 if Comctl32.dll isn't installed at all:

 void GetComctlVersion(DWORD &dwMajor, DWORD &dwMinor) {     dwMajor = dwMinor = 0;     HINSTANCE hLib = ::LoadLibrary (_T ("Comctl32.dll"));     if (hLib != NULL) {         DLLGETVERSIONPROC pDllGetVersion =             (DLLGETVERSIONPROC) ::GetProcAddress (hLib, _T ("DllGetVersion"));         if (pDllGetVersion) { // IE 3.0 or higher             DLLVERSIONINFO dvi;             ::ZeroMemory (&dvi, sizeof (dvi));             dvi.cbSize = sizeof (dvi);             HRESULT hr = (*pDllGetVersion) (&dvi);             if (SUCCEEDED (hr)) {                 dwMajor = dvi.dwMajorVersion;                 dwMinor = dvi.dwMinorVersion;             }         }         else { // Pre-IE 3.0             dwMajor = 4;             dwMinor = 0;         }         ::FreeLibrary (hLib);     } } 

You also need a way to translate Internet Explorer version numbers into Comctl32.dll version numbers. Here's a table that will help:

Internet Explorer Version Comctl32.dll Version
3.0 4.70
4.0 4.71
4.01 4.72

Now if I say that a certain feature requires Internet Explorer 3.0 or later and you want to determine at run time whether that feature is supported, you can do this:

 DWORD dwMajor, dwMinor; GetComctlVersion (dwMajor, dwMinor); if ((dwMajor == 4 && dwMinor >= 70) ¦¦ dwMajor > 4) {     // The feature is supported. } else {     // The feature is not supported. } 

Yes, it's ugly. But it's the only option currently available.

Creating a Common Control

There are two ways to create a common control without resorting to API functions. The first method is to instantiate the corresponding MFC control class and call the resulting object's Create function, as demonstrated here:

 #include <afxcmn.h>      CProgressCtrl wndProgress; wndProgress.Create (WS_CHILD ¦ WS_VISIBLE ¦ WS_BORDER,     CRect (x1, y1, x2, y2), this, IDC_PROGRESS); 

The header file Afxcmn.h contains the declarations for CProgressCtrl and other common control classes. The second method is to add a CONTROL statement to a dialog template. When the dialog box is created, the control is created, too. The following CONTROL statement creates a progress control in a dialog box:

 CONTROL "", IDC_PROGRESS, PROGRESS_CLASS, WS_BORDER, 32, 32, 80, 16 

When you create a common control this way, you can specify either the literal WNDCLASS name or its alias, whichever you prefer. The Visual C++ dialog editor writes CONTROL statements for you when you use it to add common controls to a dialog box.

Most of the common controls support their own window styles, which you can combine with WS_CHILD, WS_VISIBLE, and other standard window styles. The table below shows the "generic" common control styles that, at least in theory, aren't specific to any particular control type. As an MFC programmer, you'll rarely have occasion to manipulate these styles directly because many of them apply only to toolbars and status bars, and if you use CToolBar and CStatusBar instead of the more primitive CToolBarCtrl and CStatusBarCtrl classes to implement toolbars and status bars, the appropriate CCS styles are built in. These are by no means all the styles you can use with common controls. I'll point out control-specific styles when we examine individual control types.

Common Control Styles

Style Description
CCS_TOP Positions the control at the top of its parent's client area and matches the control's width to the width of its parent. Toolbars have this style by default.
CCS_BOTTOM Positions the control at the bottom of its parent's client area and matches the control's width to the width of its parent. Status bars have this style by default.
CCS_LEFT* Positions the control at the left end of its parent's client area.
CCS_RIGHT* Positions the control at the right end of its parent's client area.
CCS_VERT* Orients the control vertically rather than horizontally.
CCS_NOMOVEX* Causes the control to resize and move itself vertically but not horizontally when its parent is resized.
CCS_NOMOVEY Causes the control to resize and move itself horizontally but not vertically when its parent is resized. Header controls have this style by default.
CCS_NORESIZE Prevents the control from resizing itself when the size of its parent changes. If this style is specified, the control assumes the width and height specified in the control rectangle.
CCS_NOPARENTALIGN Prevents the control from sticking to the top or bottom of its parent's client area. A control with this style retains its position relative to the upper left corner of its parent's client area. If this style is combined with CCS_TOP or CCS_BOTTOM, the control assumes a default height but its width and position don't change when its parent is resized.
CCS_NODIVIDER Eliminates the divider drawn at the top of a toolbar control.
CCS_ADJUSTABLE Enables a toolbar control's built-in customization features. Double-clicking a toolbar of this type displays a Customize Toolbar dialog box.

* Requires Internet Explorer 3.0 or later

Once you've created a common control, you manipulate it using member functions of the corresponding control class. For controls created from dialog templates, you can use any of the techniques described in Chapter 8 to manufacture type-specific references for accessing a control's function and data members. For example, the following statement links a CProgressCtrl member variable named m_wndProgress to the progress control whose ID is IDC_PROGRESS:

 DDX_Control (pDX, IDC_PROGRESS, m_wndProgress); 

This statement must appear in a dialog class's DoDataExchange function. Rather than add the statement manually, you can use ClassWizard if you'd like. See Chapter 8 for a description of how to use ClassWizard to bind a member variable in a dialog class to a control in the dialog box.

When you use the common controls in an SDK-style application, you must call either ::InitCommonControls or the newer ::InitCommonControlsEx to load Comctl32.dll and register the controls' WNDCLASSes before creating the first control. In an MFC application, MFC calls these functions for you. It first tries to call ::InitCommonControlsEx. If the attempt fails because Internet Explorer 3.0 or later isn't installed (Internet Explorer adds ::InitCommonControlsEx to the Win32 API), MFC falls back and calls ::InitCommonControls, which is supported on any system running Windows 95 or higher or Windows NT 3.51 or higher.

MFC calls ::InitCommonControls(Ex) whenever a dialog box is created or a common control class's Create function is called. If for some reason you decide to create a common control or a dialog box that contains a common control using the Windows API instead of MFC, or if you create a common control with CreateEx instead of Create, you should call ::InitCommonControls or ::InitCommonControlsEx yourself. A good place to do that is in the main window's OnCreate handler or InitInstance, although you can defer the call until just before the control or dialog box is created if you'd prefer. It's not harmful to call ::InitCommonControls(Ex) multiple times during an application's lifetime.

Processing Notifications: The WM_NOTIFY Message

Unlike the classic controls, which send notifications to their parents using WM_COMMAND messages, most common controls package their notifications in WM_NOTIFY messages. A WM_NOTIFY message's wParam holds the child window ID of the control that sent the message, and lParam holds a pointer to either an NMHDR structure or a structure that's a superset of NMHDR. NMHDR is defined as follows:

 typedef struct tagNMHDR {     HWND hwndFrom;     UINT idFrom;     UINT code; } NMHDR; 

hwndFrom holds the control's window handle, idFrom holds the control ID (the same value that's passed in wParam), and code specifies the notification code. The following notifications are transmitted by virtually all of the common controls.

Notification Sent When
NM_CLICK The control is clicked with the left mouse button.
NM_DBLCLK The control is double-clicked with the left mouse button.
NM_RCLICK The control is clicked with the right mouse button.
NM_RDBLCLK The control is double-clicked with the right mouse button.
NM_RETURN The Enter key is pressed while the control has the input focus.
NM_KILLFOCUS The control loses the input focus.
NM_SETFOCUS The control gains the input focus.
NM_OUTOFMEMORY An operation on the control has failed because of insufficient memory.

Systems on which Internet Explorer 3.0 or later is installed support a richer assortment of NM notifications. For example, certain control types, including some of the original common controls that aren't unique to Internet Explorer but that are enhanced when Internet Explorer is installed, send NM_CUSTOMDRAW notifications so that their owners can customize their appearance. Others send NM_SETCURSOR notifications that their owners can use to apply custom cursors. The documentation for individual controls notes the "special" NM notifications, if any, that the controls send.

Most common controls define additional notification codes to signify control-specific events. For example, a tree view control notifies its parent when a subtree is expanded by sending it a WM_NOTIFY message with code equal to TVN_ITEMEXPANDED. lParam points to an NM_TREEVIEW structure, which contains the following data members:

 typedef struct _NM_TREEVIEW {     NMHDR    hdr;     UINT     action;     TV_ITEM  itemOld;     TV_ITEM  itemNew;     POINT    ptDrag; } NM_TREEVIEW; 

Notice that the structure's first member is an NMHDR structure, making NM_TREEVIEW a functional superset of NMHDR. The type of structure lParam points to depends on the type of control the notification came from. It sometimes even depends on the notification code. For instance, the lParam accompanying a TVN_GETDISPINFO notification from a tree view control points to a TV_DISPINFO structure, which is defined differently than NM_TREEVIEW is:

 typedef struct _TV_DISPINFO {     NMHDR   hdr;     TV_ITEM item; } TV_DISPINFO; 

How do you know what kind of pointer to cast lParam to? You start by casting to an NMHDR pointer and examining the notification code. Then, if necessary, you can recast to a more specific pointer type, as demonstrated here:

 NMHDR* pnmh = (NMHDR*) lParam; switch (pnmh->code) { case TVN_ITEMEXPANDED:     NM_TREEVIEW* pnmtv = (NM_TREEVIEW*) pnmh;     // Process the notification.     break; case TVN_GETDISPINFO:     NM_DISPINFO* pnmdi = (NM_DISPINFO*) pnmh;     // Process the notification.     break; } 

If the window that processes these notifications contains two or more tree view controls, it can examine the hwndFrom or idFrom field of the NMHDR structure to determine which control sent the notification.

switch statements like the one above are usually unnecessary in MFC applications, because notifications encapsulated in WM_NOTIFY messages are mapped to class member functions with ON_NOTIFY and ON_NOTIFY_RANGE macros. In addition, WM_NOTIFY notifications can be reflected to derived control classes using ON_NOTIFY_REFLECT. (MFC also supports extended forms of these macros named ON_NOTIFY_EX, ON_NOTIFY_EX_RANGE, and ON_NOTIFY_REFLECT_EX.) The following message-map entries map TVN_ITEMEXPANDED and TVN_GETDISPINFO notifications from a tree view control whose ID is IDC_TREEVIEW to handling functions named OnItemExpanded and OnGetDispInfo:

 ON_NOTIFY (TVN_ITEMEXPANDED, IDC_TREEVIEW, OnItemExpanded) ON_NOTIFY (TVN_GETDISPINFO, IDC_TREEVIEW, OnGetDispInfo) 

Casting to specific pointer types is performed inside the notification handlers:

 void CMyWindow::OnItemExpanded (NMHDR* pnmh, LRESULT* pResult) {     NM_TREEVIEW* pnmtv = (NM_TREEVIEW*) pnmh;     // Process the notification. } void CMyWindow::OnGetDispInfo (NMHDR* pnmh, LRESULT* pResult) {     NM_DISPINFO* pnmdi = (NM_DISPINFO*) pnmh;     // Process the notification. } 

The pnmh parameter passed to an ON_NOTIFY handler is identical to the WM_NOTIFY message's lParam. The pResult parameter points to a 32-bit LRESULT variable that receives the handler's return value. Many notifications attach no meaning to the return value, in which case the handler can safely ignore pResult. But sometimes what happens after the handler returns depends on the value of *pResult. For example, you can prevent branches of a tree view control from being expanded by processing TVN_ITEMEXPANDING notifications and setting *pResult to a nonzero value. A 0 return value, on the other hand, allows the expansion to occur:

 // In the message map ON_NOTIFY (TVN_ITEMEXPANDING, IDC_TREEVIEW, OnItemExpanding)      void OnItemExpanding (NMHDR* pnmh, LRESULT* pResult) {     NM_TREEVIEW* pnmtv = (NM_TREEVIEW*) pnmh;     if (...) {         *pResult = TRUE; // Under certain conditions, prevent         return;          // the expansion from taking place.     }     *pResult = 0;        // Allow the expansion to proceed. } 

A TVN_ITEMEXPANDING notification differs from a TVN_ITEMEXPANDED notification in that it is sent before an item in a tree view control is expanded, not after. As with the standard control types, you can ignore notifications you're not interested in and process only those that are meaningful to your application. Windows provides appropriate default responses for unhandled notifications.



Programming Windows with MFC
Programming Windows with MFC, Second Edition
ISBN: 1572316950
EAN: 2147483647
Year: 1999
Pages: 101
Authors: Jeff Prosise

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