Child Window ManagementYou 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 WrappersNow, 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? Figure 10.6. A dialog with a list box
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[9] 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:
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).[10] 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.
|