Window Control
Wrappers
Child Window
Management
You might have noticed in the last two samples
that whenever I needed to manipulate a child controlfor example,
when getting and setting the edit control's text, or enabling and
disabling the OK buttonI used a dialog item function. The ultimate
base class of CDialogImpl, CWindow, provides a
number of helper functions to manipulate child controls:
class CWindow {
public:
...
// Dialog-Box Item Functions
BOOL CheckDlgButton(int nIDButton, UINT nCheck);
BOOL CheckRadioButton(int nIDFirstButton, int nIDLastButton,
int nIDCheckButton);
int DlgDirList(LPTSTR lpPathSpec, int nIDListBox,
int nIDStaticPath, UINT nFileType);
int DlgDirListComboBox(LPTSTR lpPathSpec, int nIDComboBox,
int nIDStaticPath, UINT nFileType);
BOOL DlgDirSelect(LPTSTR lpString, int nCount,
int nIDListBox);
BOOL DlgDirSelectComboBox(LPTSTR lpString, int nCount,
int nIDComboBox);
UINT GetDlgItemInt(int nID, BOOL* lpTrans = NULL,
BOOL bSigned = TRUE) const;
UINT GetDlgItemText(int nID, LPTSTR lpStr,
int nMaxCount) const;
BOOL GetDlgItemText(int nID, BSTR& bstrText) const;
HWND GetNextDlgGroupItem(HWND hWndCtl,
BOOL bPrevious = FALSE) const;
HWND GetNextDlgTabItem(HWND hWndCtl,
BOOL bPrevious = FALSE) const;
UINT IsDlgButtonChecked(int nIDButton) const;
LRESULT SendDlgItemMessage(int nID, UINT message,
WPARAM wParam = 0, LPARAM lParam = 0);
BOOL SetDlgItemInt(int nID, UINT nValue, BOOL bSigned = TRUE);
BOOL SetDlgItemText(int nID, LPCTSTR lpszString);
};
ATL adds no overhead to these functions, but
because they're just inline wrappers of Windows functions, you can
feel that something's not quite as efficient as it could be when
you use one of these functions. Every time we pass in a child
control ID, the window probably does a lookup to figure out the
HWND and then calls the actual function on the window. For
example, if I ran the zoo, SetDlgItemText would be
implemented like this:
BOOL SetDlgItemText(HWND hwndParent, int nID,
LPCTSTR lpszString) {
HWND hwndChild = GetDlgItem(hwndParent, nID);
if( !hwndChild ) return FALSE;
return SetWindowText(hwndChild, lpszString);
}
That implementation is fine for family, but when
your friends come over, it's time to pull out the good dishes. I'd
prefer to cache the HWND and use SetWindowText.
Plus, if I want to refer to a dialog and or a main window with an
HWND, why would I want to refer to my child windows with
UINT? Instead, I find it convenient to wrap windows
created by the dialog manager with CWindow objects in
OnInitDialog:
LRESULT CStringDlg::OnInitDialog(UINT, WPARAM, LPARAM, BOOL&) {
CenterWindow();
// Cache HWNDs
m_edit.Attach(GetDlgItem(IDC_STRING));
m_ok.Attach(GetDlgItem(IDOK));
// Copy the string from the data member
// to the child control (DDX)
m_edit.SetWindowText(m_sz);
// Check the string length (DDV)
CheckValidString();
return 1; // Let dialog manager set initial focus
}
Now, the functions that used any of the dialog
item family of functions can use CWindow member functions
instead:
void CStringDlg::CheckValidString() {
// Check the length of the string
int cchString = m_edit.GetWindowTextLength();
// Enable the OK button only if the string is
// of non-zero length
m_ok.EnableWindow(cchString ? TRUE : FALSE);
}
LRESULT CStringDlg::OnOK(WORD, UINT, HWND, BOOL&) {
// Copy the string from the child control to
// the data member (DDX)
m_edit.GetWindowText(m_sz, lengthof(m_sz));
EndDialog(IDOK);
return 0;
}
A Better Class of
Wrappers
Now, my examples have been purposefully simple. A
dialog box with a single edit control is not much work, no matter
how you build it. However, let's mix things up a little. What if we
were to build a dialog with a single list box control instead, as
shown in Figure 10.6?
This simple change makes the implementation more
complicated. Instead of being able to use SetWindowText,
as we could with an edit control, we must use special window
messages to manipulate this list box control. For example,
populating the list box and setting the initial selection involves
the following code:
LRESULT
CStringListDlg::OnInitDialog(UINT, WPARAM, LPARAM, BOOL&) {
CenterWindow();
// Cache list box HWND
m_lb.Attach(GetDlgItem(IDC_LIST));
// Populate the list box
m_lb.SendMessage(LB_ADDSTRING, 0,
(LPARAM)__T("Hello, ATL"));
m_lb.SendMessage(LB_ADDSTRING, 0,
(LPARAM)__T("Ain't ATL Cool?"));
m_lb.SendMessage(LB_ADDSTRING, 0,
(LPARAM)__T("ATL is your friend"));
// Set initial selection
int n = m_lb.SendMessage(LB_FINDSTRING, 0, (LPARAM)m_sz);
if( n == LB_ERR ) n = 0;
m_lb.SendMessage(LB_SETCURSEL, n);
return 1; // Let dialog manager set initial focus
}
Likewise, pulling out the final selection in
OnOK is just as much fun:
LRESULT CStringListDlg::OnOK(WORD, UINT, HWND, BOOL&) {
// Copy the selected item
int n = m_lb.SendMessage(LB_GETCURSEL);
if( n == LB_ERR ) n = 0;
m_lb.SendMessage(LB_GETTEXT, n, (LPARAM)(LPCTSTR)m_sz);
EndDialog(IDOK);
return 0;
}
The problem is that although CWindow
provides countless wrapper functions that are common to all
windows, it does not provide any wrappers for the built-in Windows
controls (for example, list boxes). And although MFC provides such
wrapper classes as CListBox, ATL doesn't. However,
unofficially, buried deep in the atlcon
sample lives an undocumented and unsupported set of classes that
fit the bill nicely. The atlcontrols.h file defines the
following window control wrappers inside the ATLControls
namespace:
CAnimateCtrl
CButton
CComboBox
CComboBoxEx
CDateTimePickerCtrl
CDragListBox
CEdit
CFlatScrollBar
CheaderCtrl
CHotKeyCtrl
CImageList
CIPAddressCtrl
ClistBox
CListViewCtrl
CMonthCalendarCtrl
CPagerCtrl
CProgressBarCtrl
CReBarCtrl
CRichEditCtrl
CScrollBar
CStatic
CStatusBarCtrl
CTabCtrl
CToolBarCtrl
CToolInfo
CToolTipCtrl
CTrackBarCtrl
CTreeItem
CTreeViewCtrl
CTreeViewCtrlEx
CupDownCtrl
For example, the CListBox class
provides a set of inline wrapper functions, one per LB_XXX
message. The following shows the ones that would be useful for the
sample:
template <class Base>
class CListBoxT : public Base {
public:
...
// for single-selection listboxes
int GetCurSel() const
{ return (int)::SendMessage(m_hWnd, LB_GETCURSEL, 0, 0L); }
int SetCurSel(int nSelect)
{
return (int)::SendMessage(m_hWnd, LB_SETCURSEL, nSelect, 0L);
}
...
// Operations
// manipulating listbox items
int AddString(LPCTSTR lpszItem)
{ return (int)::SendMessage(m_hWnd, LB_ADDSTRING, 0, (LPARAM)lpszItem);
}
...
// selection helpers
int FindString(int nStartAfter, LPCTSTR lpszItem) const
{ return (int)::SendMessage(m_hWnd, LB_FINDSTRING, nStartAfter,
(LPARAM)lpszItem); }
...
};
typedef CListBoxT<CWindow> CListBox;
Assuming a data member of type
ATLControls::CListBox, the updated sample dialog code now
looks much more pleasing:
LRESULT
CStringListDlg::OnInitDialog(UINT, WPARAM, LPARAM, BOOL&) {
CenterWindow();
// Cache listbox HWND
m_lb.Attach(GetDlgItem(IDC_LIST));
// Populate the listbox
m_lb.AddString(__T("Hello, ATL"));
m_lb.AddString(__T("Ain't ATL Cool?"));
m_lb.AddString(__T("ATL is your friend"));
// Set initial selection
int n = m_lb.FindString(0, m_sz);
if( n == LB_ERR ) n = 0;
m_lb.SetCurSel(n);
return 1; // Let dialog manager set initial focus
}
LRESULT CStringListDlg::OnOK(WORD, UINT, HWND, BOOL&) {
// Copy the selected item
int n = m_lb.GetCurSel();
if( n == LB_ERR ) n = 0;
m_lb.GetText(n, m_sz);
EndDialog(IDOK);
return 0;
}
Because the window control wrappers are merely a
collection of inline functions that call SendMessage for
you, the generated code is the same. The good news, of course, is
that you don't have to pack the messages yourself.
The ATLCON sample itself hasn't been updated
since 2001, but the atlcontrols.h header still works just
fine. Unfortunately, a lot of new and updated common controls have
been released since then, and, of course, the header doesn't
reflect these updates. If you need updated control wrappers or want
support for more sophisticated UI functions, check out the Windows
Template Library (WTL). WTL is a library that is written on top of and in
the same style as ATL, but it provides much of the functionality
that MFC does: message routing, idle time processing, convenient
control wrappers, DDX/DDV support, and more.
|