C++ Q&A
Create Client Windows, Drag and Drop Between Listboxes
Paul DiLascia
Code download available at:
CQA0410.exe
(220 KB)
Browse the Code Online
Q How does the system recognize which part of a window is client or non-client when it sends paint messages? And how can I specify the client rectangle when I create a window using ::CreateWindow?
Q How does the system recognize which part of a window is client or non-client when it sends paint messages? And how can I specify the client rectangle when I create a window using ::CreateWindow?
Vipul Solanki
A You don't specify the client area when you create your window, you do it when your window receives a WM_NCCALCSIZE message. Windows
® sends this message whenever it wants to know the size of your window's client area. In MFC, you implement an OnNcCalcSize handler. The handler takes two parameters, converted from WPARAM and LPARAM:
void OnNcCalcSize(BOOL bCalcValidRects, NCCALCSIZE_PARAMS* lpncsp);
The flag tells your app whether to "calculate valid rectangles" (more on this later); the NCCALCSIZE_PARAMS struct holds an array of three rectangles, the first of which holds your window's client area. Here's the basic model for implementing OnNcCalcSize:
// got WM_NCCALCSIZE
void CMainFrame::OnNcCalcSize(...)
{
// do default thing (important!)
CFrameWnd::OnNcCalcSize(...);
CRect& rc = (CRect&)lpncsp->rgrc[0];
// adjust rc; eg, rc.DeflateRect(...);
}
A You don't specify the client area when you create your window, you do it when your window receives a WM_NCCALCSIZE message. Windows
® sends this message whenever it wants to know the size of your window's client area. In MFC, you implement an OnNcCalcSize handler. The handler takes two parameters, converted from WPARAM and LPARAM:
void OnNcCalcSize(BOOL bCalcValidRects, NCCALCSIZE_PARAMS* lpncsp);
The flag tells your app whether to "calculate valid rectangles" (more on this later); the NCCALCSIZE_PARAMS struct holds an array of three rectangles, the first of which holds your window's client area. Here's the basic model for implementing OnNcCalcSize:
// got WM_NCCALCSIZE
void CMainFrame::OnNcCalcSize(...)
{
// do default thing (important!)
CFrameWnd::OnNcCalcSize(...);
CRect& rc = (CRect&)lpncsp->rgrc[0];
// adjust rc; eg, rc.DeflateRect(...);
}
I wrote a little program, NCCalc, that shrinks the standard client area by seven pixels all around and paints this area the standard 3D face color (typically light gray). Figure 1 shows the source highlights. The important functions are OnNcCalcSize, which adjusts the client rectangle, and OnNcPaint, which paints the border. The paint code is straightforward, so I won't bother to explain. See the full source code on the MSDN®Magazine Web site for complete details.

Figure 1 MainFrm
////////////////////////////////////////////////////////////////
// MSDN Magazine — October 2004
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual Studio .NET 2003 on Windows XP. Tab size=3.
//
#include "StdAfx.h"
#include "MainFrm.h"
•••
const int BORDERWIDTH = 7; // width of extra non-client area
//////////////////
// Paint client area: draw "Hello, world" text using tooltip bg color
//
void CMainFrame::OnPaint()
{
CPaintDC dc(this);
•••
CString s = _T("Hello, world");
dc.DrawText(s, &rc, DT_CENTER|DT_VCENTER|DT_SINGLELINE);
}
//////////////////
// Calculate non-client area:
// Use default non-client area minus BORDERWIDTH on all sides.
//
void CMainFrame::OnNcCalcSize(BOOL bCalcValidRects,
NCCALCSIZE_PARAMS* lpncsp)
{
TRACE(_T("CMainFrame:OnNcCalcSize:, bCalcValidRects=%d\n"),
bCalcValidRects);
// do default thing (important!)
CFrameWnd::OnNcCalcSize(bCalcValidRects, lpncsp);
CRect& rc = (CRect&)lpncsp->rgrc[0];
rc.DeflateRect(BORDERWIDTH,BORDERWIDTH);
}
/////////////////
// Paint non-client area: draw extra border using 3D face color.
//
void CMainFrame::OnNcPaint()
{
TRACE(_T("CMainFrame::OnNcPaint\n"));
CFrameWnd::OnNcPaint(); // do default thing (important!)
// get client rectangle
CRect rc;
GetClientRect(rc);
// convert to window coordinates
CRect rcWin;
GetWindowRect(rcWin); // window rect (in screen coords)..
ScreenToClient(rcWin); // ..convert to client coords...
rc -= CPoint(rcWin.left, rcWin.top);// ..convert to window coords
// rc now has client rect in window coords
CWindowDC dc(this);
dc.ExcludeClipRect(rc); // don't paint in client area
rc.InflateRect(BORDERWIDTH,BORDERWIDTH); // expand client rect by
// border width
// Now draw the rectangle. Since i've clipped the client area, this
// only draws the border (expanded rectangle minus client rectangle).
CBrush b;
b.CreateSolidBrush(GetSysColor(COLOR_3DFACE));
dc.FillRect(rc, &b);
}
If you override WM_NCCALCSIZE/OnNcCalcSize for a main window, make sure you call the base class (or default window proc) to perform default processing. That way, you'll start with the default client rectangle which you can then adjust. Likewise, you should call the base class/default procedure in your OnNcPaint/WM_NCPAINT handler. Otherwise Windows won't paint the borders, scroll bars, or any other standard non-client elements. If you're implementing your own window class, such as a custom toolbar or palette where you calculate the client rect and paint from scratch, calling the base class/default window proc may not be necessary. Either way, you're responsible for painting the entire non-client area when your window gets WM_NCPAINT.
Some of you may be wondering what the bCalcValidRects and the other rectangles in NCCALCSIZE_PARAMS are used for. If you read the documentation, you'll discover that the semantics of WM_NCCALCSIZE are rather complex. The documentation states that if bCalcValidRects is TRUE, "the application should indicate which part of the client area contains valid information. The system copies the valid information to the specified area within the new client area." In this scenario, "the second [rectangle] contains the coordinates of the window before it was moved or resized. The third [rectangle] contains the coordinates of the window's client area before the window was moved or resized." While all of this seems clear enough, I can't make total sense of it. Nor have I ever seen any app that uses these extra rectangles. Every app I've ever seen ignores bCalcValidRects and simply alters the first rectangle in NCCALCSIZE_PARAMS to set the client area.
I mention this because according to the documentation, if WPARAM/bCalcValidRects is FALSE, then LPARAM does not point to a NCCALCSIZE_PARAMS struct but rather to a single RECT, the client rectangle, whereas MFC casts LPARAM to NCCALCSIZE_PARAMS in all cases. This would seem to be a bug, albeit one that will never disrupt your program as long as you modify only the first rectangle in NCCALCSIZE_PARAMS. I ran some tests to determine that Windows sends WM_NCCALCSIZE with bCalcValidRects=FALSE only once, when the window is first created. Thereafter, Windows sends WM_NCCALCSIZE with bCalcValidRects=TRUE any time the window is resized. You must set the client rectangle in both cases so that your window will display in a proper way.
Alas, I'm afraid in the end I haven't done much to elucidate the purpose of bCalcValidRects. Nor were the Redmondtonians able to shed more light on this mysterious parameter, except to say that it's a performance holdover from the ancient days of Windows 3.1. In these heady times, you'll be fine as long as you make sure to always handle both cases (bCalcValidRects = TRUE and FALSE), and alter only the first rectangle.
Q I'm building a commercial baseball game app. In my UI, I want users to be able to drag items from one listbox to another. Is there a simple way to do this using MFC?
Q I'm building a commercial baseball game app. In my UI, I want users to be able to drag items from one listbox to another. Is there a simple way to do this using MFC?
Tom Tippett
A There's nothing built into MFC to handle this, but there are a couple of ways you can do it. COM has its own interfaces for doing general-purpose drag and drop between applications. This requires several interfaces: IDropTarget, IDropSource, and IDataObject, plus a function DoDragDrop that actually does the drag/drop. Using COM for drag and drop requires writing quite a bit of code and may be more than you're up for. Moreover, it may be overkill since the COM stuff is designed to interact with other programs in the most general manner. If all you want to do is drag items from one control to another control within your application, it's simpler and easier to implement your own mechanism from scratch.
A There's nothing built into MFC to handle this, but there are a couple of ways you can do it. COM has its own interfaces for doing general-purpose drag and drop between applications. This requires several interfaces: IDropTarget, IDropSource, and IDataObject, plus a function DoDragDrop that actually does the drag/drop. Using COM for drag and drop requires writing quite a bit of code and may be more than you're up for. Moreover, it may be overkill since the COM stuff is designed to interact with other programs in the most general manner. If all you want to do is drag items from one control to another control within your application, it's simpler and easier to implement your own mechanism from scratch.
I wrote a little class library with a class, CDragDropMgr, that lets you add drag/drop behavior among windows in your app. I also wrote a test program, DDTest, that shows how to use it (see Figure 2). Figure 3 shows it running. DDTest has two listboxes and an edit control. You can drag items from the first listbox to the second, or to the edit control. You can also drag items within the second listbox to rearrange them. DDTest uses CDragDropMgr to do the work. First I'll show you how to use CDragDropMgr; then I'll explain how it works.

Figure 2 DDTest
////////////////////////////////////////////////////////////////
// MSDN Magazine — October 2004
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual Studio .NET 2003 on Windows XP. Tab size=3.
//
#include "stdafx.h"
•••
//////////////////
// Generic MFC application class
//
class CMyApp : public CWinApp {
public:
CMyApp() { }
virtual BOOL InitInstance();
} theApp;
//////////////////
// Main dialog with two listboxes and edit control to drag/drop.
//
class CMyDlg : public CDialog {
protected:
CDragDropMgr m_ddm; // drag/drop manager
CListBox m_wndList1; // first (source) listbox
CListBox m_wndList2; // second (source/target) listbox
CEdit m_wndEdit1; // edit control (target)
•••
virtual BOOL PreTranslateMessage(MSG* pMsg);
virtual BOOL OnInitDialog();
int GetLBItemUnderPt(CListBox* pListBox, CPoint pt);
public:
CMyDlg(CWnd* pParent = NULL); // standard constructor
afx_msg void OnReset();
afx_msg LRESULT OnDragEnter(WPARAM wp, LPARAM lp);
afx_msg LRESULT OnDragDrop(WPARAM wp, LPARAM lp);
afx_msg LRESULT OnDragAbort(WPARAM wp, LPARAM lp);
DECLARE_MESSAGE_MAP()
};
BOOL CMyApp::InitInstance()
{
// InitCommonControls() is required on Windows XP if an application
// manifest specifies use of ComCtl32.dll version 6 or later to
// enable visual styles. Otherwise, any window creation will fail.
InitCommonControls();
CWinApp::InitInstance();
// Run modal dialog
CMyDlg dlg;
m_pMainWnd = &dlg;
dlg.DoModal();
// return FALSE to exit app
return FALSE;
}
BEGIN_MESSAGE_MAP(CMyDlg, CDialog)
ON_COMMAND(IDC_RESET, OnReset)
ON_REGISTERED_MESSAGE(WM_DD_DRAGENTER, OnDragEnter)
ON_REGISTERED_MESSAGE(WM_DD_DRAGDROP, OnDragDrop)
ON_REGISTERED_MESSAGE(WM_DD_DRAGABORT, OnDragAbort)
END_MESSAGE_MAP()
//////////////////
// Init dialog: subclass controls and install drag/drop manager
//
BOOL CMyDlg::OnInitDialog()
{
CDialog::OnInitDialog();
•••
OnReset();
// table describing which child windows are sources and targets
static DRAGDROPWND MyDragDropWindows[] = {
{ IDC_LIST1, DDW_SOURCE },
{ IDC_LIST2, DDW_SOURCE|DDW_TARGET },
{ IDC_EDIT1, DDW_TARGET },
{ 0, 0 },
};
m_ddm.Install(this, MyDragDropWindows);
return TRUE; // return TRUE unless you set the focus to a control
}
//////////////////
// Reset form/dialog contents to initial state. If edit control contains a
// three-letter word like "all," fill listbox #2 also.
//
void CMyDlg::OnReset()
{
static LPCTSTR MyItems[] = {
_T("Dopey"),
_T("Smokey"),
•••
_T("Zot"),
_T("This is a looong item at the end."),
NULL
};
// If contents of edit control are "all", copy all items.
CString s;
m_wndEdit1.GetWindowText(s);
BOOL bAll = s==_T("all");
m_wndEdit1.SetWindowText(NULL);
m_wndList1.ResetContent();
m_wndList2.ResetContent();
for (int i=0; MyItems[i]; i++) {
m_wndList1.AddString(MyItems[i]);
if (bAll)
m_wndList2.AddString(MyItems[i]);
}
}
//////////////////
// You must override PreTranslateMessage to pass input to drag/drop
// manager.
//
BOOL CMyDlg::PreTranslateMessage(MSG* pMsg)
{
return m_ddm.PreTranslateMessage(pMsg) ? TRUE :
CDialog::PreTranslateMessage(pMsg);
}
//////////////////
// Begin dragging: create new text data from list item under point.
//
LRESULT CMyDlg::OnDragEnter(WPARAM wp, LPARAM lp)
{
TRACE(_T(_T("CMyDlg::OnDragEnter, wp=%d\n")),wp);
DRAGDROPINFO& ddi = *(DRAGDROPINFO*)lp;
CListBox* plb = DYNAMIC_DOWNCAST(CListBox, GetDlgItem((int)wp));
ASSERT(plb!=NULL);
int item = GetLBItemUnderPt(plb, ddi.pt);
if (item>=0) {
CString text;
plb->GetText(item, text);
ddi.data = new CDragDropText(text);
return TRUE; // do drag/drop
}
return FALSE; // no item under mouse: nothing to drag
}
//////////////////
// User let go of the mouse: drop data into child control.
//
LRESULT CMyDlg::OnDragDrop(WPARAM wp, LPARAM lp)
{
TRACE(_T("CMyDlg::OnDragDrop\n"));
DRAGDROPINFO& ddi = *(DRAGDROPINFO*)lp;
LPCTSTR text = (LPCTSTR)ddi.data->OnGetData();
if (wp==IDC_LIST2) {
CListBox* plb = DYNAMIC_DOWNCAST(CListBox, GetDlgItem((int)wp));
ASSERT(plb!=NULL);
int iNew = GetLBItemUnderPt(plb, ddi.pt);
int iOld = plb->FindString(0, text);
if (iOld>=0) {
plb->DeleteString(iOld);
if (iOld < iNew)
iNew—;
}
if (iNew>=0)
plb->InsertString(iNew, text);
else
iNew = plb->AddString(text);
plb->SetCurSel(iNew);
} else if (wp==IDC_EDIT1) {
m_wndEdit1.SetWindowText(text);
} else {
ASSERT(FALSE);
}
return 0;
}
//////////////////
// Drag aborted (for example, user pressed Esc).
//
LRESULT CMyDlg::OnDragAbort(WPARAM wp, LPARAM lp)
{
TRACE(_T("CMyDlg::OnDragAbort\n"));
return 0;
}
//////////////////
// Helper to get the listbox item under the mouse.
// This may not be the selected item when dropping.
//
int CMyDlg::GetLBItemUnderPt(CListBox* pListBox, CPoint pt)
{
BOOL bOutside;
UINT item = pListBox->ItemFromPoint(pt, bOutside);
return item>=0 && !bOutside ? item : -1;
}
Figure 3 DDTest in Action
To use the drag/drop manager, you first instantiate a CDragDropMgr in your main window or dialog, then initialize it with a table, as shown in the following lines of code:
static DRAGDROPWND MyDragDropWindows[] = {
{ IDC_LIST1, DDW_SOURCE },
{ IDC_LIST2, DDW_SOURCE|DDW_TARGET },
{ IDC_EDIT1, DDW_TARGET },
{ 0, 0 },
};
m_ddm.Install(this, MyDragDropWindows);
Fans of my columns know that one of my top five programming mantras is that one table is worth a thousand lines of code. Tables are more concise, elegant, reliable, and maintainable than long sequences of procedural code. In this case, the table tells the drag/drop manager which child windows can be sources and/or targets for drag/drop. Each table entry holds a child window ID and a combination of flags DDW_SOURCE and DDW_TARGET. In DDTest, the first listbox is a source; the second listbox can be both a source and target, and the edit control is a target only. If you try this at home, don't forget the NULL entry at the end of your table!
Once you've initialized the drag/drop manager with your window table, next override your main window's PreTranslateMessage function to pass messages to the drag/drop manager:
BOOL CMyDlg::PreTranslateMessage(MSG* pMsg)
{
return m_ddm.PreTranslateMessage(pMsg) ? TRUE :
CDialog::PreTranslateMessage(pMsg);
}
With this bit of plumbing in place, the drag/drop manager detects when the user tries to drag from one window to another, and notifies your app when it's time to do something. There are four messages/notifications that CDragDropMgr can send: WM_DD_DRAGENTER, WM_DD_DRAGOVER, WM_DD_ DRAGDROP, and WM_DD_DRAGABORT. It's up to you to handle the notifications as they arrive. At a minimum, you must handle WM_DD_DRAGENTER to initiate dragging and WM_DD_DRAGDROP to perform the drop. The other two are optional. WM_DD_DRAGABORT is your chance to clean up when the user cancels; WM_DD_ DRAGOVER lets you do continual processing as the user moves the mouse while dragging. Simple apps like DDTest usually don't need to handle these messages.
When the drag/drop manager sends WM_DD_DRAGENTER, it passes a DRAGDROPINFO struct in LPARAM. Your mission is to set DRAGDROPINFO::data to point to an instance of CDragDropData containing the data you want to drag and then return TRUE. If dragging is not allowed (perhaps the user clicked on a dead area within the listbox), you should return FALSE without setting DRAGDROPINFO::data. CDragDropData is like COM's IDataObject, but much simpler: it holds the data to drop. CDragDropData has three virtual functions: OnGetDragSize to get the bounding rectangle of the drag image, OnDrawData to draw this image, and OnGetData to get the data itself. My drag/drop library provides a class called CDragDropText that implements these functions for text. It holds the text in a CString. OnGetData returns this string, OnGetDragSize calculates the text rectangle, and OnDrawData draws the text:
void CDragDropText::OnDrawData(CDC& dc, CRect& rc)
{
dc.DrawText(m_text, &rc, DT_LEFT|DT_END_ELLIPSIS);
}
If you want the text, the only function you ever need to call is OnGetData; the drag/drop manager calls OnGetDragSize and OnDrawData internally when it needs to.
How does all of this work in practice? When DDTest receives WM_DD_DRAGENTER, it calls an internal function called GetLBItemUnderPt to figure out which listbox item (if any) lies under the cursor. DDTest then creates a CDragDropText object with this item's text as data and sets the data pointer in DRAGDROPINFO to this object:
// in CMyDlg::OnDragEnter
DRAGDROPINFO& ddi = *(DRAGDROPINFO*)lp;
int item = GetLBItemUnderPt(...);
if (item>=0) {
CString text = // get item text
ddi.data = new CDragDropText(text);
return TRUE; // allow drag-drop
}
return FALSE; // nothing to drag
CDragDropMgr does the rest. It paints the text as the user drags it around the screen and changes the mouse cursor appropriately depending on whether the cursor is over a drop target or not.
When the user lets go of the mouse, CDragDropMgr sends your app a WM_DD_DRAGDROP message. This is your cue to drop the data. For DDTest, this means setting the text in the edit control if the mouse is over the edit control, or adding the text to the listbox if the mouse is over the listbox. In reality, DDTest is a little more complex because it lets users rearrange the items in the second listbox. DDTest has code to detect whether it needs to add the text or change its position in the listbox. I leave you to ponder these details alone; the basic outline for OnDragDrop is always the same:
// OnDragDrop handler
DRAGDROPINFO& ddi = *(DRAGDROPINFO*)lp;
void* data = ddi.data->OnGetData();
// do something with data
return 0;
So much for text. What if you want to drag some other kind of data? In that case, you'd have to extend my library by deriving from CDragDropData and overriding the three basic functions. For example, to drag an image you'd derive a class CDragDropImage where OnGetData returns a BITMAP or CBitmap, OnGetDragSize returns the dimensions of the bitmap, and OnDrawData calls BltBit or some other function to draw the bitmap.
I've shown you how to use CDragDropMgr, but how does it work? The basic idea is simple. CDragDropMgr::PreTranslateMessage looks for mouse messages sent to one of the drag source windows and sends the appropriate notifications to your main window. CDragDropMgr implements a typical finite state machine with three states: NONE, CAPTURED, and DRAGGING. When the user presses the mouse button, CDragDropMgr enters CAPTURED state. When the user moves the mouse, it goes into DRAGGING state. The details are straightforward.
CDragDropMgr uses PreTranslateMessage instead of subclassing the main window because it needs to intercept mouse messages sent to any one of many possible drag source windows as determined by the drag/drop window table. One of the beauties of MFC is the way it funnels all child window messages through a single virtual PreTranslateMessage method in the main window. This lets CDragDropMgr trap mouse messages sent to any one of many potential drag-source windows in a single function, thus avoiding the need to subclass each window. When CDragDropMgr::PreTranslateMessage sees WM_LBUTTONDOWN, it looks up the window handle (HWND) to see if it's listed as a source in the window table. If it is listed as a source, it initiates dragging; otherwise, it ignores the message.
The mechanics of dragging data are straightforward and a bit boring, so I'll spare you the details. The only thing to highlight is that CDragDropData uses CImageList to paint. If you implement your own drag/drop manager, I encourage you to do the same. CImageList has functions BeginDrag, DragEnter, DragMove, and EndDrag that make short shrift of drawing the bits. They use the proper raster operations to make the image semitransparent, erase it from its previous location, and so on.
These painting details are tedious without CImageList. With it, all CDragDropMgr has to do is draw the drag image once into the image list's bitmap. When the user initiates dragging, CDragDropMgr notifies the main app, which sets DRAGDROPINFO::data to an instance of CDragDropData, as described earlier. The drag/drop manager then calls CDragDropData:: CreateDragImage (see Figure 4), which creates an image list containing the image to draw. CreateDragImage calls the virtual functions CDragDropData::OnGetDragSize to get the size of the drag image and CDragDropData::OnDrawData to draw the data into the image list's bitmap. Once this is done, CDragDropMgr calls the image list functions to draw the image during dragging. For example, each time the user moves the mouse, CDragDropMgr calls CImageList::DragMove. What could be easier? The beauty is that this code is totally generic. To handle new data types, you only have to implement OnGetDragSize and OnDrawData.

Figure 4 DragDrop
////////////////////////////////////////////////////////////////
// MSDN Magazine — October 2004
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual Studio .NET 2003 on Windows XP. Tab size=3.
//
•••
//////////////////
// Create the drag image: create an image list and call virtual draw
// function to draw the data into the image list. Will then use this
// during dragging.
//
CImageList* CDragDropData::CreateDragImage(CWnd* pWnd, CRect& rc)
{
const COLORREF BGCOLOR = GetSysColor(COLOR_3DLIGHT);
// create memory dc compatible with source window
CWindowDC dcWin(pWnd);
CDC dcMem;
dcMem.CreateCompatibleDC(&dcWin);
// use same font as source window
CFont* pFont = pWnd->GetFont();
CFont* pOldFont = dcMem.SelectObject(pFont);
// get size of drag image
CSize sz = OnGetDragSize(dcMem); // call virtual fn to get size
rc = CRect(CPoint(0,0), sz);
// create image list: create bitmap and draw into it
m_bitmap.CreateCompatibleBitmap(&dcWin, sz.cx, sz.cy);
CBitmap* pOldBitmap = dcMem.SelectObject(&m_bitmap);
CBrush brush;
brush.CreateSolidBrush(GetSysColor(COLOR_HIGHLIGHT));
dcMem.FillRect(&rc,&brush);
dcMem.SetBkMode(TRANSPARENT);
dcMem.SetTextColor(GetSysColor(COLOR_WINDOWTEXT));
OnDrawData(dcMem, rc); // call virtual fn to draw
dcMem.SelectObject(pOldFont);
dcMem.SelectObject(pOldBitmap);
// create image list and add bitmap to it
CImageList *pil = new CImageList();
pil->Create(sz.cx, sz.cy, ILC_COLOR24|ILC_MASK, 0, 1);
pil->Add(&m_bitmap, BGCOLOR);
return pil;
}
Figure 2 and Figure 4 show the important highlights from DDTest. As always, you can download the full source. Happy programming!
Send your questions and comments for Paul to cppqa@microsoft.com.
Paul DiLascia is a freelance writer, consultant, and Web/UI designer-at-large. He is the author of
Windows++:
Writing Reusable Windows Code in C++ (Addison-Wesley, 1992). Paul can be reached at
http://www.dilascia.com.