Using the ATL Windowing Classes

In this article I’ll provide an introduction to the ATL windowing classes, and a simple cookbook tutorial on an ATL frame-view application — you’ll see that it’s actually quite easy to implement front-end functionality equivalent to the MFC. The learning curve with ATL windowing is much less steep and much shorter than learning the MFC because the ATL is so much smaller.

Although the ATL is designed primarily to support COM, it does contain a range of classes for modeling windows. You can use these classes for COM objects that have windows, such as ActiveX Controls, and for Windows applications that do not necessarily involve COM. The most important ATL windowing classes are listed in the table below:

CWindow A thin wrapper to the Win32 APIs for manipulating a window, including a window handle and an HWND operator that converts a CWindow object to an HWND. Thus you can pass a CWindow object to any function that requires a handle to a window.
CWindowImpl You can use CWindowImpl to create a window based on a new Windows class, superclass an existing class, or subclass an existing window.
CContainedWindow A class that implements a window which routes messages to the message map of another class, allowing you to centralize message processing in one class.
CAxWindow Allows you to implement a window that hosts an ActiveX control, with functions to create a control or attach to an existing control.
CDialogImpl Used as a base class for implementing a modal or modeless dialog box. CDialogImpl provides a dialog box procedure that routes messages to the default message map in your derived class. Does not support ActiveX controls.
CSimpleDialog Implements a simple modal dialog box given the resource ID of the dialog box. CSimpleDialog has a predefined message map that handles known commands such as IDOK and IDCANCEL.
CAxDialogImpl Like CDialogImpl, this is used as a base class for implementing a modal or modeless dialog box, and provides a dialog box procedure that routes messages to the default message map in your derived class. Additionally supports ActiveX controls. The ATL Object Wizard supports adding a CAxDialogImpl-derived class to your project and generates an accompanying dialog resource.
CWndClassInfo Manages the information of a new window class — essentially encapsulates WNDCLASSEX.
CWinTraits and CWinTraitsOR Encapsulate the traits (WS_ window styles) of an ATL window object.

Message Maps

One factor in the reluctance to invest the time in learning ATL windowing is a perception that ATL message maps are weird. OK, they’re different from the MFC message maps, but did you understand MFC message maps the first time you saw the macros? In fact, the ATL maps are surprisingly easy to grasp. To allow you to process window messages in a CWindowImpl-derived class, ATL inherits from the abstract base class CMessageMap. CMessageMap declares one pure virtual function, ProcessWindowMessage, which is implemented in your CWindowImpl-derived class via the BEGIN_MSG_MAP and END_MSG_MAP macros.

In addition to the familiar format of MFC message handlers, ATL message handler functions accept an additional argument of type BOOL&. This argument indicates whether a message has been processed, and it’s set to TRUE by default. A handler function can then set the argument to FALSE to indicate that it has not handled a message. In this case, ATL will continue to look for a handler function further in the message map. By setting this argument to FALSE, you can first perform some action in response to a message and then allow the default processing or another handler function to finish handling the message.

There are three groups of message map macros, as listed in the table below:

  • Message handlers for all messages
  • Command handlers for WM_COMMAND messages
  • Notification handlers for WM_NOTIFY messages
MESSAGE_HANDLER Maps a window message to a handler function.
COMMAND_HANDLER Maps a WM_COMMAND message to a handler function based on the notification code and the ID of the menuitem, control, or accelerator.
COMMAND_ID_HANDLER Maps a WM_COMMAND message to a handler function based on the ID of the menuitem, control, or accelerator.
COMMAND_CODE_HANDLER Maps a WM_COMMAND message to a handler function based on the notification code.
NOTIFY_HANDLER Maps a WM_NOTIFY message to a handler based on the notification code and the control identifier.
NOTIFY_ID_HANDLER Maps a WM_NOTIFY message to a handler based on the control identifier.
NOTIFY_CODE_HANDLER Maps a WM_NOTIFY message to a handler based on the notification code.

For example, if you have an ATL dialog class with child controls on the form, you might have a message map like the one shown below:

BEGIN_MSG_MAP(CMyDialog)
 MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
 COMMAND_HANDLER(IDC_EDIT1, EN_CHANGE, OnChangeEdit1)
 COMMAND_ID_HANDLER(IDOK, OnOK)
 COMMAND_CODE_HANDLER(EN_ERRSPACE, OnErrEdits)
 NOTIFY_HANDLER(IDC_LIST1, NM_CLICK, OnClickList1)
 NOTIFY_ID_HANDLER(IDC_LIST2, OnSomethingList2)
 NOTIFY_CODE_HANDLER(NM_DBLCLK, OnDblClkLists)
END_MSG_MAP()

The MFC architecture allows it to use two distinct message routing schemes: routing windows messages up through the hierarchy, and routing command messages across the doc-view classes. The first scheme is less appropriate in the ATL, which has a much looser hierarchy of partially implemented template classes. The second scheme is not appropriate because the ATL does not rigidly impose anything equivalent to the MFC doc-view architecture.

The ATL provides two ways to handle messages sent by different windows in a single message map: alternate message maps and chained message maps. A parent window can also handle messages sent to it by a child control by sending the message back as a reflected message.

Alternate Message Maps

Alternate message maps are primarily designed for use with the ATL class CContainedWindow. This class is written to route all of its messages to the message map in another class. This allows messages sent to a child window to be handled by its parent window.

The CContainedWindow constructor needs to be given the address of the class that contains the message map to be used, and the ID of the alternate message map within the message map (or zero for the default message map).

For example, when you create an ATL control based on a Windows control, the Object Wizard will generate a class for the control with an embedded CContainedWindow member to represent the child control. In effect, this contained window superclasses the particular Windows control you have chosen to base your ActiveX control on:

class ATL_NO_VTABLE CMyButton :
 public CComObjectRootEx<CComSingleThreadModel>,
 public CComCoClass<CMyButton, &CLSID_MyButton>,
 public CComControl<CMyButton>,
 //...
{
public:
 CContainedWindow m_ctlButton;
 CMyButton() : m_ctlButton(_T("Button"), this, 1) { }

BEGIN_MSG_MAP(CMyButton)
 MESSAGE_HANDLER(WM_CREATE, OnCreate)
 MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus)
 CHAIN_MSG_MAP(CComControl<CMyButton>)
 ALT_MSG_MAP(1)
END_MSG_MAP()
//...

Note that Button is the WNDCLASS style, not the caption. This pointer of the containing class is passed as the second parameter, and the value 1 is passed to the CContainedWindow constructor to identify the alternate message map.

If you then want to handle the WM_LBUTTONDOWN for the control, you would update the message map as shown below. In this way, the message would be routed to the parent window’s message map, and then routed to the alternate part of that message map:

BEGIN_MSG_MAP(CMyButton)
 MESSAGE_HANDLER(WM_CREATE, OnCreate)
 MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus)
 CHAIN_MSG_MAP(CComControl<CMyButton>)
ALT_MSG_MAP(1)
 MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown)
END_MSG_MAP()

So, alternate message maps are a simple strategy to allow you to consolidate message handlers within a single BEGIN_MSG_MAP/END_MSG_MAP macro pair.

Chained Message Maps

Chaining message maps routes the message through to the message map in another class or object. ATL supplies several map-chaining macros:

CHAIN_MSG_MAP(theBaseClass) Routs messages to the default message map of a base class.
CHAIN_MSG_MAP_ALT(theBaseClass, mapID) Routes messages to the alternate message map of a base class.
CHAIN_MSG_MAP_MEMBER(theMember) Routes messages to the default message map of the specified data member (derived from CMessageMap).
CHAIN_MSG_MAP_ALT_MEMBER(theMember, mapID) Routes messages to the alternate message map of the specified data member.

For example, when you create an ATL control based on a Windows control, the Object Wizard will generate code like this:

BEGIN_MSG_MAP(CMyButton)
 MESSAGE_HANDLER(WM_CREATE, OnCreate)
 MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus)
 CHAIN_MSG_MAP(CComControl<CMyButton>)
ALT_MSG_MAP(1)
END_MSG_MAP()

This specifies that WM_CREATE and WM_SETFOCUS messages will be handled in this class, but any other message will be routed to the message map in the CComControl<> base class. Also, if the handlers for WM_CREATE or WM_SETFOCUS set bHandled to false, these messages will then be passed on to the base class for further handling.

To route messages to a data member, you’d have to update the map like this:

BEGIN_MSG_MAP(CMyButton)
 MESSAGE_HANDLER(WM_CREATE, OnCreate)
 MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus)
 CHAIN_MSG_MAP(CComControl<CMyButton>)
ALT_MSG_MAP(1)
 CHAIN_MSG_MAP_MEMBER(m_ctlButton)
END_MSG_MAP()

This assumes that m_ctlButton is a member of the container window, and is an instance of a class derived from CContainedWindow where you have an entry in the message map for the messages you’re interested in:

class CMyButtonControl : public CContainedWindow
{
 //...
 BEGIN_MSG_MAP(CMyButtonControl)
  MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown)
 END_MSG_MAP()

So, chained message maps allow you to chain-route messages from one map to another — similar in concept to the message routing schemes adopted by the MFC.

Reflected Messages

A parent window can handle windows messages sent to it by a child control by sending the message back as a reflected message — this will be the original message plus a flag. When the control gets these messages, it can identify them as being reflected from the container and handle them appropriately. For example, a child control might want to handle WM_DRAWITEM messages. For this to work, REFLECT_NOTIFICATIONS must be present in the parent window’s message map:

BEGIN_MSG_MAP(CMyDialog)
 MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
 COMMAND_ID_HANDLER(IDOK, OnOk)
 NOTIFY_HANDLER(IDC_EDIT1, EN_CHANGE, OnChangeEdit1)
 REFLECT_NOTIFICATIONS()
END_MSG_MAP()

The REFLECT_NOTIFICATIONS macro expands to a call to CWindowImpl::ReflectNotifications, which has this signature:

template <class TBase>
LRESULT CWindowImplRoot<TBase>::ReflectNotifications(UINT uMsg,
 WPARAM wParam, LPARAM lParam, BOOL& bHandled);

The function extracts the window handle to the child control that sent the message from the wParam or lParam (depending on the type of message), and then sends the message on like this:

::SendMessage(hWndChild, OCM_ _BASE + uMsg, wParam, lParam);

The child window handles the reflected message using the standard MESSAGE_HANDLER macros and the predefined reflected message IDs defined in olectrl.h:

BEGIN_MSG_MAP(CMyContainedControl)
 MESSAGE_HANDLER(OCM_DRAWITEM, OnDrawItem)
 DEFAULT_REFLECTION_HANDLER()
END_MSG_MAP()

OCM_DRAWITEM, shown in this example, is defined as follows:

#define OCM_ _BASE          (WM_USER+0x1c00)
#define OCM_COMMAND         (OCM_ _BASE + WM_COMMAND)
//...
#define OCM_DRAWITEM        (OCM_ _BASE + WM_DRAWITEM)

The DEFAULT_REFLECTION_HANDLER macro converts the message back to the original message and passes it to DefWindowProc.

Recipe 1: ATL Window App

This is a very simple exercise, designed to demonstrate how easy it is to create a simple application using the ATL window classes. Here’s one I made earlier:

The ATL COM AppWizard is designed to provide a host for COM objects. If you want a non-COM application, the ATL COM AppWizard code is more than you need. So, to create an ATL application, you have two choices:

  • Create an ATL COM AppWizard EXE server, and accept the (possibly superfluous) overhead.
  • Create a Win32 Application, and manually add ATL support.

Just so you can see exactly what’s required, we’ll deliberately avoid any wizard-generated code, and follow the second route to achieve the minimum lightweight framework for our application.

  1. Create a new Win32 Application, selecting the Simple option, so that we get the stdafx.h and stdafx.cpp. (‘afx’, of course is a hangover from the MFC, but the name is irrelevant – what’s important is the PCH).
  2. ATL Support

  3. Modify stdafx.h to add the necessary ATL headers and an extern reference to the global CComModule object:
  4. #include <atlbase.h>
    extern CComModule _Module;
    #include <atlcom.h>
    #include <atlwin.h>
    
  5. Add an ATL object map to the main CPP file – it’ll be empty, but we need it for the CComModule object. Also declare the global CComModule object:
  6. CComModule _Module;
    
    BEGIN_OBJECT_MAP(ObjectMap)
    END_OBJECT_MAP()
    
  7. Add an IDL file with the same name as the project. This must have a library block. It doesn’t need to be built as part of the project (and you should set the Project|Settings to exclude it from the build), but it needs to exist for the wizards to work. And you can use any name you like for the library.:
  8. library SomethingOrOther
    {
    };
    

    Window

  9. There is no wizard support for creating classes specifically using the ATL window classes, so we’ll have to add a generic class with the New Class dialog and then modify it manually. Right-click the root node in classview, and select New Class. Use the class type: Generic Class, the name CMyWindow, and the base class CWindowImpl<CMyWindow>. You’ll get a complaint about these ATL window classes because the wizard will generate a new header for your new class but won’t #include the stdafx.h (where atlwin.h has been included) and won’t know about these classes. Fix this by #including the stdafx.h.
  10. In your new window class, declare a message map then save the file:
  11. BEGIN_MSG_MAP(CMyWindow)
    END_MSG_MAP()
    
  12. In classview, right-click your new window class to get a handler for WM_DESTROY. Code this to post a quit message:
  13. LRESULT OnDestroy(UINT uMsg, WPARAM wParam,
                      LPARAM lParam, BOOL& bHandled)
    {
     PostQuitMessage(0);
     return 0;
    }
    
  14. Similarly, get a handler for WM_PAINT, and code to print out a string. You can see straight away that – as usual with the ATL – we’re straight out to API code. And there’s no ATL class to wrap an HDC, although, there is in the WTL):
  15. LRESULT OnPaint(UINT uMsg, WPARAM wParam,
                    LPARAM lParam, BOOL& bHandled)
    {
     PAINTSTRUCT ps;
     HDC hDC = GetDC();
     BeginPaint(&ps);
     TextOut(hDC, 0, 0, _T("Hello world"), 11);
     EndPaint(&ps);
    
     return 0;
    }
    

    WinMain

  16. At the top and bottom of WinMain, code the usual CComModule::Init and Term calls:
  17. _Module.Init(NULL, hInstance);
    // ...
    _Module.Term();
    
  18. Between the Init and Term, declare an instance of your window class, and initialize it with a call to
    Create (don’t forget to #include the header). Then set up a message loop:
  19. CMyWindow wnd;
    wnd.Create(NULL, CWindow::rcDefault, _T("Hello"),
               WS_OVERLAPPEDWINDOW|WS_VISIBLE);
    
    MSG msg;
    while(GetMessage(&msg, NULL, 0, 0))
    {
     TranslateMessage(&msg);
     DispatchMessage(&msg);
    }
    
  20. Now, build and run your application. You should find that you’ll have a window just like the one shown above
    with the customary “Hello World” message in the client area.

Easy-peasy, huh? Now let’s take this a step further with another demo…

Recipe 2: ATL Frame-View App

In this project we’ll create an application modeled on the MFC SDI frame-view paradigm, but use the ATL window classes. The first version of this app will look almost the same as the previous SimpleWin, but then we’ll add a view, menus, and dialogs.

  1. Create a new ‘Simple’ Win32 Application. As before, modify stdafx.h to add the necessary ATL headers and an extern reference to the global CComModule object. Add an ATL object map to the main CPP file and declare the global CComModule object. Also add a skeleton IDL file with a library block.
  2. Mainframe Window

  3. Right-click the root node in classview, and select New Class. Use the class type: Generic Class, the name CMainFrame, and the base class CWindowImpl<CMainFrame, CWindow, CFrameWinTraits>. Note that CFrameWinTraits is a typedef (in atlwin.h) for a specialization of CWinTraits suitable for a main-frame window. In this new CMainFrame class, declare the WNDCLASS struct name, and a message map:
  4. DECLARE_WND_CLASS(_T("MyFrame"))
    
    BEGIN_MSG_MAP(CMainFrame)
    END_MSG_MAP()
    
  5. We have inherited the function OnFinalMessage – one of the few virtual functions in the ATL – and this will be called by ATL when a WM_NCDESTROY message is received. We need to override this to post a quit message:
  6. void OnFinalMessage(HWND /*hWnd*/)
    {
     ::PostQuitMessage(0);
    }
    
  7. Now add some code to WinMain. At the top and bottom call the usual CComModule initialization/termination routines:
  8. _Module.Init(NULL, hInstance, NULL);
    
    _Module.Term();
    
  9. #include the mainframe class header, declare an instance of the frame between the Init and Term, declare another instance of the frame, and initialize it with a call to Create. Then run a message loop:
  10. CMainFrame mf;
    mf.Create(GetDesktopWindow(), CWindow::rcDefault,
          _T("My App"), 0, 0, 0);
    mf.ShowWindow(SW_SHOWNORMAL);
    
    MSG msg;
    while (GetMessage(&msg, 0, 0, 0))
    {
     TranslateMessage(&msg);
     DispatchMessage(&msg);
    }
    
  11. Bake and serve.
  12. View Window

  13. Now we’ll add a view class. Right-click in classview to create another new class. Again, make it a generic class. Call it CViewWin, derive it from CWindowImpl<CViewWin, CWindow, CWinTraits >. Remember there is no predefined typedef for a CWinTraits specialization suitable for a view.
  14. #include the stdafx.h and declare the WNDCLASS and message map as before. Then add a CViewWin instance as a member in the CMainFrame class, and get a WM_CREATE handler in the frame: implement this to create the view:
  15. LRESULT OnCreate(UINT uMsg, WPARAM wParam,
                     LPARAM lParam, BOOL& bHandled)
    {
     m_wndView.Create(m_hWnd, CWindow::rcDefault,
                      _T("MyView"), 0, 0, 0);
     return 0;
    }
    
  16. Also get a WM_SIZE handler in the frame and implement to size the view. Return to the oven and serve again:
  17. LRESULT OnSize(UINT uMsg, WPARAM wParam,
                   LPARAM lParam, BOOL& bHandled)
    {
     RECT r;
     GetClientRect(&r);
    
     m_wndView.SetWindowPos(NULL, &r,
                            SWP_NOZORDER | SWP_NOACTIVATE );
    
     return 0;
    }
    

    User Interface

    Now, we’ll handle the WM_LBUTTONDOWN, WM_MOUSEMOVE and WM_LBUTTONUP messages to provide a very simple version of the Scribble program that allows a user to draw lines with the mouse Yes, I know, but it does provide a very manageable vehicle for exploring UI response and message handling:

  18. First, add two POINT data members to the view class, and initialize them both to -1,-1 in the constructor. We need to keep track of the starting and ending point of each line drawn — and -1,-1 of course would never come in as the coordinate values with a mouse message:
  19. m_startPoint.x = m_startPoint.y = -1;
    m_endPoint.x = m_endPoint.y = -1;
    
  20. Next, right-click to get handlers for the three mouse messages. Unlike the mouse message handlers in the MFC CWnd class, the ATL version doesn’t declare the lParam as a CPoint (nor even as a POINT), so you’ll have to extract the mouse coordinates yourself. First, the OnLButtonDown code it to log the incoming mouse coordinates as the start point of the line:
  21. LRESULT OnLButtonDown(UINT uMsg, WPARAM wParam,
                          LPARAM lParam, BOOL& bHandled)
    {
     m_startPoint.x = LOWORD(lParam);
     m_startPoint.y = HIWORD(lParam);
    
     return 0;
    }
    
  22. In the OnLButtonUP, reset the start point to -1:
  23. LRESULT OnLButtonUP(UINT uMsg, WPARAM wParam,
                        LPARAM lParam, BOOL& bHandled)
    {
     m_startPoint.x = m_startPoint.y = -1;
     return 0;
    }
    
  24. The OnMouseMove will need a little more work. First, set the line end point to the incoming mouse coordinates. Then get a DC, and use MoveToEx and LineTo to draw the line. Finally, update the start point with the endpoint so the next line will start at the current end point:
  25. LRESULT OnMouseMove(UINT uMsg, WPARAM wParam,
                        LPARAM lParam, BOOL& bHandled)
    {
     m_endPoint.x = LOWORD(lParam);
     m_endPoint.y = HIWORD(lParam);
    
     HDC hdc = GetDC();
     if (m_startPoint.x != -1 )
     {
      MoveToEx(hdc, m_startPoint.x, m_startPoint.y, NULL);
      LineTo(hdc, m_endPoint.x, m_endPoint.y);
      m_startPoint.x = m_endPoint.x;
      m_startPoint.y = m_endPoint.y;
     }
    
     return 0;
    }
    
  26. Bake and shake. You may ask why aren’t there any message crackers in the ATL? Well, of course, that’s one of the things that the WTL does address

Recipe 3: ATL Menus

Continue with the Frame-View project. We will add a simple menu to give the user a choice of pen colors.:

  1. Continue with the project. First, add a public COLORREF member variable to the view class, called m_color. Initialize this in the constructor to say black. Then, use this in the OnMouseMove handler to create a pen and select it into the DC, as indicated below. Select the original pen back into the DC afterwards, as normal:
  2. HPEN hp = CreatePen(PS_SOLID, 2, m_color);
    HPEN op = (HPEN)SelectObject(hdc, hp);
    
  3. Insert a menu resource. Add one top-level caption: Color and three menuitems for red, green, and blue.
  4. In WinMain, #include the resource header Just before the creation of the mainframe, load the menu, and pass the handle to the Create call:
  5. HMENU hMenu = LoadMenu(_Module.GetResourceInstance(),
                           MAKEINTRESOURCE(IDR_MENU1));
    mf.Create(GetDesktopWindow(), CWindow::rcDefault,
               _T("My App"), 0, 0, (UINT)hMenu);
    
  6. We’ll put the command handler for the menuitems in the main frame, so #include the resource header and manually update the message map with three new entries:
  7. BEGIN_MSG_MAP(CMainFrame)
     MESSAGE_HANDLER(WM_CREATE, OnCreate)
     MESSAGE_HANDLER(WM_SIZE, OnSize)
     COMMAND_ID_HANDLER(ID_COLOR_RED, OnColorRed)
     COMMAND_ID_HANDLER(ID_COLOR_GREEN, OnColorGreen)
     COMMAND_ID_HANDLER(ID_COLOR_BLUE, OnColorBlue)
    END_MSG_MAP()
    
  8. Set the three handlers to do the obvious work, and test again:
  9. LRESULT OnColorRed(WORD wNotifyCode, WORD wID,
                       HWND hWndCtl, BOOL& bHandled)
    {
     m_wndView.m_color = RGB(255,0,0);
     return 0;
    }
    

Recipe 4: ATL Dialogs

We’ll now add a simple dialog resource. Again, one of the features of the MFC is its rich support of child controls (CEdit, CComboBox, and others), which the ATL doesn’t have — although the WTL does. So how hard is it in ATL? Well, our dialog will feature a combobox, and we’ll deliberately not put the strings into the combo in the resource editor – just to show how to work with the controls in a dialog programmatically.

  1. Continue with the project. Add a new top-level caption to the menu: “View” and a menuitem on this: “Dialog”. We’ll put the command handler for the menuitem in the main-frame message map:
  2. COMMAND_ID_HANDLER(ID_VIEW_DIALOG, OnViewDialog)
    
  3. Now for our own dialog: for the first version, we’ll just use a CSimpleDialog directly. First, insert a new dialog resource, and paint it with a simple static box. Then change the menuitem command handler to use this. Build and test:
  4. LRESULT OnViewDialog(WORD wNotifyCode, WORD wID,
                         HWND hWndCtl, BOOL& bHandled)
    {
     CSimpleDialog<IDD_DIALOG1> dlg;
     dlg.DoModal();
    
     return 0;
    }
    
  5. If we want more sophisticated behaviour from our dialog, we’ll have to derive from CSimpleDialog. So, first go to the resource editor and add a drop-down combobox to the dialog.
  6. Then create a new class called CListDialog, derived from CSimpleDialog<IDD_DIALOG1>. Don’t forget to #include the stdafx.h. Add a message map to the new class, and an entry in the map to chain to the base-class message map:
  7. BEGIN_MSG_MAP(CListDialog)
     CHAIN_MSG_MAP(CSimpleDialog<IDD_DIALOG1>)
    END_MSG_MAP()
    
  8. Next, we’ll code the WM_INITDIALOG to add some strings to the combobox. First, remove the Sort style in the combobox. Then, right-click on the CListDialog class and select Add Windows Message Handler. Change the filter to Dialog. Add and Edit a handler for WM_INITDIALOG. Code as shown below – you’ll see that the crucial declaration of the combobox class object is very similar to what it would be with the MFC:
  9. LRESULT OnInitDialog(UINT uMsg, WPARAM wParam,
                         LPARAM lParam, BOOL& bHandled)
    {
     CWindow combo(GetDlgItem(IDC_COMBO1));
     combo.SendMessage(CB_ADDSTRING, 0, (LPARAM)"Red");
     combo.SendMessage(CB_ADDSTRING, 0, (LPARAM)"Green");
     combo.SendMessage(CB_ADDSTRING, 0, (LPARAM)"Blue");
    
     return CSimpleDialog<IDD_DIALOG1>::OnInitDialog(
      uMsg, wParam, lParam, bHandled);
    }
    
  10. Note: make sure the CHAIN_MSG_MAP macro is the last entry in the map. Change the menuitem handler in the frame to use this new CListDialog class. Build and test.
  11. OK, but what about DDX/DDV? I hear you say. Well, let’s code the IDOK pushbutton to retrieve the selected string from the list. First put the appropriate macro in the message map:
  12. COMMAND_ID_HANDLER(IDOK, OnOK)
    
  13. Next code the OnOK handler as shown below. We’ll store the text into a CComBSTR member in our dialog class called m_text:
  14. LRESULT OnOK(WORD, WORD wID, HWND, BOOL&)
    {
     CComBSTR text;
     GetDlgItemText(IDC_COMBO1, m_text.m_str);
     ::EndDialog(m_hWnd, wID);
    
     return 0;
    }
    
  15. Finally, update the menuitem handler in the frame to make use of the text extracted from the dialog, then build and test:
  16. LRESULT OnViewDialog(WORD wNotifyCode, WORD wID,
                         HWND hWndCtl, BOOL& bHandled)
    {
     // CSimpleDialog<IDD_DIALOG1> dlg;
     CListDialog dlg;
     if (IDOK == dlg.DoModal())
     {
      if (dlg.m_text == CComBSTR("Red"))
       m_wndView.m_color = RGB(255,0,0);
      else if (dlg.m_text == CComBSTR("Green"))
       m_wndView.m_color = RGB(0,255,0);
      else if (dlg.m_text == CComBSTR("Blue"))
       m_wndView.m_color = RGB(0,0,255);
     }
    
     return 0;
    }
    

If you want to extend this app with toolbars and statusbars, you can use the ATL CStatusBarCtrl and CToolBarCtrl classes – these are defined in atlcontrols.h, although Microsoft doesn’t officially support them. In the next article, I’ll consider the WTL — another officially unsupported Microsoft library. You’ll then be able to make intelligent comparisons between ATL and WTL front-end support, and informed decisions about ATL/WTL versus MFC.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read