previous page
next page

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?

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:

[9] As of this writing, this sample is available online at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vcsample/html/_sample_atl_ATLCON.asp (http://tinysells.com/52).

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

[10] Available at http://wtl.sourceforge.net.


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