|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Contents
Introduction to Part IVDialogs and controls are one area where MFC really saves you time and effort. Without MFC's control classes, you'd be stuck filling in structs and writing tons of Refresher on ATL DialogsRecall from Part I that ATL has two dialog classes, To create a new dialog class, you do three things:
Then you can add message handlers just like in a frame window. WTL doesn't change that process, but it does add other features that you can use in dialogs. Control Wrapper ClassesWTL has lots of control wrappers which should be familiar to you because the WTL classes are usually named the same (or almost the same) as their MFC counterparts. The methods are usually named the same as well, so you can use the MFC documentation while using the WTL wrappers. Failing that, the F12 key comes in handy when you need to jump to the definition of one of the classes. Here are the wrapper classes for built-in controls:
There are also a few WTL-specific classes: One thing to note is that most of the wrapper classes are window interface classes, like Since these articles are aimed at experienced MFC programmers, I won't be spending much time on the details of the wrapper classes that are similar to their MFC counterparts. I will, however, be covering the new classes in WTL; Creating a Dialog-Based App with the AppWizardFire up VC and start the WTL AppWizard. I'm sure you're as tired as I am with clock apps, so let's call our next app ControlMania1. On the first page of the AppWizard, click Dialog Based. We also have a choice between making a modal or modeless dialog. The difference is important and I will cover it in Part V, but for now let's go with the simpler one, modal. Check Modal Dialog and Generate .CPP Files as shown here:
All the options on the second page (for the VC6 wizard) or the User Interface Features tab (in VC7) are only meaningful when the main window is a frame window, so they are all disabled. Click Finish to complete the wizard. As you might expect, the AppWizard-generated code is much simpler for a dialog-based app. ControlMania1.cpp has the int WINAPI _tWinMain ( HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPTSTR lpstrCmdLine, int nCmdShow ) { HRESULT hRes = ::CoInitialize(NULL); AtlInitCommonControls(ICC_COOL_CLASSES | ICC_BAR_CLASSES); hRes = _Module.Init(NULL, hInstance); int nRet = 0; // BLOCK: Run application { CMainDlg dlgMain; nRet = dlgMain.DoModal(); } _Module.Term(); ::CoUninitialize(); return nRet; } The code first initializes COM and creates a single-threaded apartment. This is necessary for dialogs that host ActiveX controls; if your app isn't using COM, you can safely remove the The block around the You can build and run the app right away, although the dialog is pretty bare:
The code in This sample project will demonstrate how to hook up variables to the controls. Here's the app with a couple more controls; you can refer back to this diagram for the following discussions.
Since the app uses a list view control, the call to AtlInitCommonControls ( ICC_WIN95_CLASSES ); That registers more classes than necessary, but it saves us having to remember to add Using the Control Wrapper ClassesThere are several ways to associate a member variable with a control. Some use plain ATL Way 1 - Attaching a CWindowThe simplest method is to declare a This code demonstrates all three methods of associating variables with the list control: HWND hwndList = GetDlgItem(IDC_LIST); CListViewCtrl wndList1 (hwndList); // use constructor CListViewCtrl wndList2, wndList3; wndList2.Attach ( hwndList ); // use Attach method wndList3 = hwndList; // use assignment operator Remember that the ATL Way 2 - CContainedWindow
The actual class, To hook up a
In ControlMania1, we'll use a Let's go through the steps. First, we add class CMainDlg : public CDialogImpl<CMainDlg> { // ... protected: CContainedWindow m_wndOKBtn, m_wndExitBtn; }; Second, we add class CMainDlg : public CDialogImpl<CMainDlg> { public: BEGIN_MSG_MAP_EX(CMainDlg) MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog) COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout) COMMAND_ID_HANDLER(IDOK, OnOK) COMMAND_ID_HANDLER(IDCANCEL, OnCancel) ALT_MSG_MAP(1) MSG_WM_SETCURSOR(OnSetCursor_OK) ALT_MSG_MAP(2) MSG_WM_SETCURSOR(OnSetCursor_Exit) END_MSG_MAP() LRESULT OnSetCursor_OK(HWND hwndCtrl, UINT uHitTest, UINT uMouseMsg); LRESULT OnSetCursor_Exit(HWND hwndCtrl, UINT uHitTest, UINT uMouseMsg); }; Third, we call the CMainDlg::CMainDlg() : m_wndOKBtn(this, 1), m_wndExitBtn(this, 2) { } The constructor parameters are a
Finally, we associate each LRESULT CMainDlg::OnInitDialog(...)
{
// ...
// Attach CContainedWindows to OK and Exit buttons
m_wndOKBtn.SubclassWindow ( GetDlgItem(IDOK) );
m_wndExitBtn.SubclassWindow ( GetDlgItem(IDCANCEL) );
return TRUE;
}
Here are the new LRESULT CMainDlg::OnSetCursor_OK (
HWND hwndCtrl, UINT uHitTest, UINT uMouseMsg )
{
static HCURSOR hcur = LoadCursor ( NULL, IDC_HAND );
if ( NULL != hcur )
{
SetCursor ( hcur );
return TRUE;
}
else
{
SetMsgHandled(false);
return FALSE;
}
}
LRESULT CMainDlg::OnSetCursor_Exit (
HWND hwndCtrl, UINT uHitTest, UINT uMouseMsg )
{
static HCURSOR hcur = LoadCursor ( NULL, IDC_NO );
if ( NULL != hcur )
{
SetCursor ( hcur );
return TRUE;
}
else
{
SetMsgHandled(false);
return FALSE;
}
}
If you wanted to use CContainedWindowT<CButton> m_wndOKBtn; and then the You can see the
ATL Way 3 - SubclassingMethod 3 involves creating a ControlMania1 uses this method to subclass the About button in the main dialog. Here is the class CButtonImpl : public CWindowImpl<CButtonImpl, CButton> { BEGIN_MSG_MAP_EX(CButtonImpl) MSG_WM_SETCURSOR(OnSetCursor) END_MSG_MAP() LRESULT OnSetCursor(HWND hwndCtrl, UINT uHitTest, UINT uMouseMsg) { static HCURSOR hcur = LoadCursor ( NULL, IDC_SIZEALL ); if ( NULL != hcur ) { SetCursor ( hcur ); return TRUE; } else { SetMsgHandled(false); return FALSE; } } }; Then in the main dialog, we declare a class CMainDlg : public CDialogImpl<CMainDlg> { // ... protected: CContainedWindow m_wndOKBtn, m_wndExitBtn; CButtonImpl m_wndAboutBtn; }; And finally, in LRESULT CMainDlg::OnInitDialog(...)
{
// ...
// Attach CContainedWindows to OK and Exit buttons
m_wndOKBtn.SubclassWindow ( GetDlgItem(IDOK) );
m_wndExitBtn.SubclassWindow ( GetDlgItem(IDCANCEL) );
// CButtonImpl: subclass the About button
m_wndAboutBtn.SubclassWindow ( GetDlgItem(ID_APP_ABOUT) );
return TRUE;
}
WTL Way 1 - DDX_CONTROLWTL's DDX (dialog data exchange) support works a lot like MFC's, and it can rather painlessly connect a variable to a control. To begin, you need a To add DDX support to class CMainDlg : public CDialogImpl<CMainDlg>, public CWinDataExchange<CMainDlg> { //... }; Next you create a DDX map in the class, which is similar to the class CEditImpl : public CWindowImpl<CEditImpl, CEdit> { BEGIN_MSG_MAP_EX(CEditImpl) MSG_WM_CONTEXTMENU(OnContextMenu) END_MSG_MAP() void OnContextMenu ( HWND hwndCtrl, CPoint ptClick ) { MessageBox("Edit control handled WM_CONTEXTMENU"); } }; class CMainDlg : public CDialogImpl<CMainDlg>, public CWinDataExchange<CMainDlg> { //... BEGIN_DDX_MAP(CMainDlg) DDX_CONTROL(IDC_EDIT, m_wndEdit) END_DDX_MAP() protected: CContainedWindow m_wndOKBtn, m_wndExitBtn; CButtonImpl m_wndAboutBtn; CEditImpl m_wndEdit; }; Finally, in LRESULT CMainDlg::OnInitDialog(...)
{
// ...
// Attach CContainedWindows to OK and Exit buttons
m_wndOKBtn.SubclassWindow ( GetDlgItem(IDOK) );
m_wndExitBtn.SubclassWindow ( GetDlgItem(IDCANCEL) );
// CButtonImpl: subclass the About button
m_wndAboutBtn.SubclassWindow ( GetDlgItem(ID_APP_ABOUT) );
// First DDX call, hooks up variables to controls.
DoDataExchange(false);
return TRUE;
}
The parameter to If you run the ControlMania1 project, you can see all this subclassing in action. Right-clicking in the edit box will pop up the message box, and the cursor will change shape over the buttons as shown earlier. WTL Way 2 - DDX_CONTROL_HANDLEA new feature that was added in WTL 7.1 is the If you're still using WTL 7.0, you can use this macro to define #define DDX_CONTROL_IMPL(x) \ class x##_ddx : public CWindowImpl<x##_ddx, x> \ { public: DECLARE_EMPTY_MSG_MAP() }; You can then write: DDX_CONTROL_IMPL(CListViewCtrl) and you will have a class called More on DDXDDX can, of course, actually do data exchange too. WTL supports exchanging string data between an edit box and a string variable. It can also parse a string as a number, and transfer that data between an integer or floating-point variable. And it also supports transferring the state of a check box or group of radio buttons to/from an DDX macrosEach DDX macro expands to a
There is also an additional floating-point macro that was added in WTL 7.1:
A note about using #define _ATL_USE_DDX_FLOAT
This is necessary because by default, floating-point support is disabled as a size optimization. More about DoDataExchange()You call the BOOL DoDataExchange ( BOOL bSaveAndValidate = FALSE,
UINT nCtlID = (UINT)-1 );
The parameters are:
Using DDXLet's add a couple of variables to class CMainDlg : public ... { //... BEGIN_DDX_MAP(CMainDlg) DDX_CONTROL(IDC_EDIT, m_wndEdit) DDX_TEXT(IDC_EDIT, m_sEditContents) DDX_INT(IDC_EDIT, m_nEditNumber) END_DDX_MAP() protected: // DDX variables CString m_sEditContents; int m_nEditNumber; }; In the OK button handler, we first call LRESULT CMainDlg::OnOK ( UINT uCode, int nID, HWND hWndCtl ) { CString str; // Transfer data from the controls to member variables. if ( !DoDataExchange(true) ) return; m_wndList.DeleteAllItems(); m_wndList.InsertItem ( 0, _T("DDX_TEXT") ); m_wndList.SetItemText ( 0, 1, m_sEditContents ); str.Format ( _T("%d"), m_nEditNumber ); m_wndList.InsertItem ( 1, _T("DDX_INT") ); m_wndList.SetItemText ( 1, 1, str ); }
If you enter non-numerical text in the edit box, void CMainDlg::OnDataExchangeError ( UINT nCtrlID, BOOL bSave ) { CString str; str.Format ( _T("DDX error during exchange with control: %u"), nCtrlID ); MessageBox ( str, _T("ControlMania1"), MB_ICONWARNING ); ::SetFocus ( GetDlgItem(nCtrlID) ); }
As our last DDX example, let's add a check box to show the usage of
This check box will never be in the indeterminate state, so we can use a class CMainDlg : public ... { //... BEGIN_DDX_MAP(CMainDlg) DDX_CONTROL(IDC_EDIT, m_wndEdit) DDX_TEXT(IDC_EDIT, m_sEditContents) DDX_INT(IDC_EDIT, m_nEditNumber) DDX_CHECK(IDC_SHOW_MSG, m_bShowMsg) END_DDX_MAP() protected: // DDX variables CString m_sEditContents; int m_nEditNumber; bool m_bShowMsg; }; At the end of void CMainDlg::OnOK ( UINT uCode, int nID, HWND hWndCtl ) { // Transfer data from the controls to member variables. if ( !DoDataExchange(true) ) return; //... if ( m_bShowMsg ) MessageBox ( _T("DDX complete!"), _T("ControlMania1"), MB_ICONINFORMATION ); } The sample project has examples of using the other Handling Notifications from ControlsHandling notifications in WTL is similar to API-level programming. A control sends its parent a notification in the form of a Handling notifications in the parentNotifications sent as Message map macrosTo handle a
Examples:
There are also macros for handling The prototype for a void func ( UINT uCode, int nCtrlID, HWND hwndCtrl );
LRESULT func ( NMHDR* phdr ); The return value of the handler is used as the message result. This is different from MFC, where the handler receives an We'll add a notification handler to class CMainDlg : public ... { BEGIN_MSG_MAP_EX(CMainDlg) NOTIFY_HANDLER_EX(IDC_LIST, LVN_ITEMCHANGED, OnListItemchanged) END_MSG_MAP() LRESULT OnListItemchanged(NMHDR* phdr); //... }; Here's the message handler: LRESULT CMainDlg::OnListItemchanged ( NMHDR* phdr )
{
NMLISTVIEW* pnmlv = (NMLISTVIEW*) phdr;
int nSelItem = m_wndList.GetSelectedIndex();
CString sMsg;
// If no item is selected, show "none". Otherwise, show its index.
if ( -1 == nSelItem )
sMsg = _T("(none)");
else
sMsg.Format ( _T("%d"), nSelItem );
SetDlgItemText ( IDC_SEL_ITEM, sMsg );
return 0; // retval ignored
}
This handler doesn't use the Reflecting NotificationsIf you have a When you want to reflect notifications back to the control classes, you just add one macro to the dialog's message map, class CMainDlg : public ... { public: BEGIN_MSG_MAP_EX(CMainDlg) //... NOTIFY_HANDLER_EX(IDC_LIST, LVN_ITEMCHANGED, OnListItemchanged) REFLECT_NOTIFICATIONS() END_MSG_MAP() }; That macro adds some code to the message map that handles any notification messages that are not handled by any earlier macros. The code examines the There are 18 messages which are reflected:
In the control class, you add handlers for the reflected messages you are interested in, then at the end, add class CODButtonImpl : public CWindowImpl<CODButtonImpl, CButton> { public: BEGIN_MSG_MAP_EX(CODButtonImpl) MSG_OCM_DRAWITEM(OnDrawItem) DEFAULT_REFLECTION_HANDLER() END_MSG_MAP() void OnDrawItem ( UINT idCtrl, LPDRAWITEMSTRUCT lpdis ) { // do drawing here... } }; WTL macros for handling reflected messagesWe just saw one of the WTL macros for reflected messages, class CMyTreeCtrl : public CWindowImpl<CMyTreeCtrl, CTreeViewCtrl> { public: BEGIN_MSG_MAP_EX(CMyTreeCtrl) REFLECTED_NOTIFY_CODE_HANDLER_EX(TVN_ITEMEXPANDING, OnItemExpanding) DEFAULT_REFLECTION_HANDLER() END_MSG_MAP() LRESULT OnItemExpanding ( NMHDR* phdr ); }; If you check out the ControlMania1 dialog in the sample code, there is a tree control that handles LRESULT CBuffyTreeCtrl::OnItemExpanding ( NMHDR* phdr )
{
NMTREEVIEW* pnmtv = (NMTREEVIEW*) phdr;
if ( pnmtv->action & TVE_COLLAPSE )
return TRUE; // don't allow it
else
return FALSE; // allow it
}
If you run ControlMania1 and click the +/- buttons in the tree, you'll see this handler in action - once you expand a node, it will not collapse again. Odds & EndsDialog FontsIf you're picky about UI like me, and you're using Win 2000 or XP, you might be wondering why the dialogs are using MS Sans Serif instead of Tahoma. Since VC 6 is so old, the resource files it generates work fine for NT 4, but not later versions of NT. You can fix this, but it requires hand-editing the resource file. There are three things you need to change in each dialog's entry in the resource file
Unfortunately, the first two changes get lost whenever you modify and save the resources, so you'll need to make the changes repeatedly. Here's a "before" picture of a dialog: IDD_ABOUTBOX DIALOG DISCARDABLE 0, 0, 187, 102 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "About" FONT 8, "MS Sans Serif" BEGIN ... END And the "after" picture: IDD_ABOUTBOX DIALOGEX DISCARDABLE 0, 0, 187, 102 STYLE DS_SHELLFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "About" FONT 8, "MS Shell Dlg" BEGIN ... END After making these changes, the dialog will use Tahoma on newer OSes, but still use MS Sans Serif when necessary on older OSes. In VC 7, you just need to change one setting in the dialog editor to use the right font:
When you change Use System Font to True, the editor will change the font to MS Shell Dlg for you. _ATL_MIN_CRTAs explained in this VC Forum FAQ, ATL contains an optimization that lets you create an app that does not link to the C runtime library (CRT). This optimization is enabled by adding the Up NextIn Part V, we'll cover dialog data validation (DDV), the new controls in WTL, and some advanced UI features like owner draw and custom draw. Copyright and licenseThis article is copyrighted material, (c)2003-2005 by Michael Dunn. I realize this isn't going to stop people from copying it all around the 'net, but I have to say it anyway. If you are interested in doing a translation of this article, please email me to let me know. I don't foresee denying anyone permission to do a translation, I would just like to be aware of the translation so I can post a link to it here. The demo code that accompanies this article is released to the public domain. I release it this way so that the code can benefit everyone. (I don't make the article itself public domain because having the article available only on CodeProject helps both my own visibility and the CodeProject site.) If you use the demo code in your own application, an email letting me know would be appreciated (just to satisfy my curiosity about whether folks are benefiting from my code) but is not required. Attribution in your own source code is also appreciated but not required. Revision History
Series Navigation: « Part III (Toolbars and Status Bars) | » Part V (Advanced Dialog UI Classes)
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||