Wicked Code

Jeff Prosise

Jeff Prosise is the author of Programming Windows 95 with MFC (Microsoft Press, 1996). He also teaches Visual C++/MFC programming seminars. For more information, visit
http://www.solsem.com.

There’s an old axiom in the software industry that
programmers won’t switch tools unless there’s a
compelling reason for them to do so. Lately, more and more Windows® SDK programmers are migrating to Visual C++® and MFC for one reason and one reason only: MFC vastly simplifies the process of writing COM, OLE, and ActiveX™ applications. Writing COM, OLE, and ActiveX code without a good class library is about as much fun as a root canal. Maybe you enjoy having holes carved in your jawbone, but me, I’ll take the easy way out every time. I’d no sooner tackle ActiveX without MFC than say no when my dentist offers me the loopy gas.

MFC isn’t the best tool available for writing simple COM servers and clients, but it really shines when it comes to writing OLE and ActiveX document servers, Automation servers, and other applications that rely on the myriad protocols built on top of COM. MFC also makes it relatively easy to write applications that exchange data through the OLE clipboard and OLE drag and drop. Building a modern user interface means supporting drag and drop whenever and wherever possible. Even novice users find grabbing an object with the mouse and dragging it across the screen to be a clean, easy-to-execute, and for the most part intuitive operation.

This month, I’d like to share a fun little programming technique that will help you write better drag and drop code. The sample application I’ll present uses MFC, but the technique isn’t specific to MFC; it’s applicable to any language or development environment.

You’ve all seen drag and drop implementations that tack a little rectangle onto the cursor to represent whatever it is that’s being dragged. That’s good—for wimps. A really cool application draws an outline of the object (or objects) being dragged and moves the outline with the cursor. A good example of this behavior can be seen in the Microsoft Windows® 95 and Windows NT® 4.0 shells. Open a Windows Explorer window, group-select a bunch of files, and then drag the files to another folder. The shell paints a clear picture of what’s being dragged with a wireframe drag image silhouetting the files you selected (see Figure 1).

Figure 1 Dragging files in Windows 95

I’ve received a number of email messages in recent months asking how the shell draws the drag image. Common sense says that there’s more to it than simply changing the cursor image because a cursor image is limited to 32 ¥ 32 pixels. I can’t tell you exactly how the shell does it because I haven’t seen the source code, but I can tell you how I’ve achieved a similar effect in my own applications. The secret is to let the drop target (not the drop source, which is where most people assume the image originates) draw the drag image. How? By including information de­scribing how to draw the image in the OLE data object that links the drop source to the drop target. This information can take any form you’d like: a series of x-y coordinates describing the endpoints of lines, a list of private meta­commands, or even a handle to an enhanced metafile. When the drop target’s IDropTarget::OnDragEnter function is called, the drop target can extract the information from the data object and use it to draw a drag image.

In the sample application presented in the next section, the drop target draws a drag image by creating a temporary object just like the one that’s being dragged and passing a CDC pointer (MFC’s equivalent of a device context handle) to the object’s DrawDragImage function. In other words, the object draws its own drag image. It makes sense when you think about it, because who knows what the object looks like better than the object itself?

The WIDGET Application

WIDGET is an SDI doc/view application that lets you create widgets of various shapes and colors (see Figure 2). Widgets are created with commands in the application’s Insert menu. Once created, they can be moved and copied using drag and drop. When a widget is dragged, WIDGET draws a drag image depicting the widget’s shape, which could be a circle, a triangle, or a square. You can drop a widget inside the application that created it or in another instance of WIDGET. To demonstrate, start two copies of WIDGET and insert a widget or two in instance A. Then grab a widget in instance A and release it over instance B. As if by magic, the widget will disappear from A and appear in B. (To copy a widget, repeat this procedure with the Ctrl key held down.) One of the strengths of OLE’s Uniform Data Transfer (UDT) model is that it is not limited by process boundaries. OLE drag and drop is a subset of UDT.

Figure 2 Dragging a Widget

The code that makes all this work will seem pretty straight­forward if you’re at all familiar with OLE drag and drop as implemented by MFC. WIDGET is both an OLE drop source and an OLE drop target. When a widget is clicked with the left mouse button, the view’s OnLButton­Down handler initializes a global memory block (HGLOBAL) with data describing the widget. Then it creates a COle­DataSource object, transfers the HGLOBAL to the data source by calling COleDataSource::Cache­GlobalData, and calls COleDataSource::DoDragDrop to initiate a drag and drop data transfer. If DoDragDrop returns DROPEFFECT_
MOVE, indicating the widget was moved rather than copied, WIDGET deletes the widget from the document.

On the flip side of the data transfer, WIDGET’s view registers itself as a drop target by creating a COleDrop­Target object and calling COleDropTarget::Register. Calls to the drop target’s IDropTarget functions generate calls to the view’s OnDragEnter, OnDragOver, OnDragLeave, and OnDrop functions. WIDGET’s CWidgetView class implements OnDrop by calling COleDataObject::GetGlobalData to retrieve the HGLOBAL created by the drop source and calling the document’s AddWidget function to create a new widget from the data in the HGLOBAL.

When an object is transferred through OLE drag and drop, its type is identified with a clipboard format code. WIDGET uses ::RegisterClipboardFormat to register a private clipboard format for widgets. The format’s integer ID is stored in a public member variable named m_nFormat in the application object, and retrieved through the pointer returned by AfxGetApp.

You can see how all this is implemented by browsing the source code for WIDGET’s view class, which is reproduced along with other pertinent parts of the application’s source code in Figure 3. (The full source code and Visual C++ 5.0 project files may be downloaded from MSJ’s Web site at http://www.microsoft.com/msj and from the other sources listed on page 5.) However, what’s more interesting is how WIDGET draws drag images. As you drag a widget over a WIDGET window, notice that an outline of the widget travels with the cursor. The code responsible for drawing the drag image lies partly in the view class and partly in the classes that implement the widgets themselves.

Figure 3 WIDGET

WidgView.h

// WidgView.h : interface of the CWidgetView class

//

/////////////////////////////////////////////////////////////////////////////

#if !defined(AFX_WIDGVIEW_H__877329C1_C22E_11D0_B2D8_444553540000__INCLUDED_)

#define AFX_WIDGVIEW_H__877329C1_C22E_11D0_B2D8_444553540000__INCLUDED_

#if _MSC_VER >= 1000

#pragma once

#endif // _MSC_VER >= 1000

typedef struct tagWIDGETDATA {

int nType; // Widget type (0=Circle, 1=Triangle, 2=Square)

RECT rcItem; // Widget rectangle (logical coordinates)

POINT ptDrag; // Drag point (logical coordinates)

} WIDGETDATA;

class CWidgetView : public CScrollView

{

protected: // create from serialization only

CWidgetView();

DECLARE_DYNCREATE(CWidgetView)

// Attributes

public:

CWidgetDoc* GetDocument();

// Operations

public:

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CWidgetView)

public:

virtual void OnDraw(CDC* pDC); // overridden to draw this view

virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

virtual DROPEFFECT OnDragEnter(COleDataObject* pDataObject, DWORD dwKeyState,

CPoint point);

virtual DROPEFFECT OnDragOver(COleDataObject* pDataObject, DWORD dwKeyState,

CPoint point);

virtual void OnDragLeave();

virtual BOOL OnDrop(COleDataObject* pDataObject, DROPEFFECT dropEffect,

CPoint point);

protected:

virtual void OnInitialUpdate(); // called first time after construct

virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);

virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);

virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);

//}}AFX_VIRTUAL

// Implementation

public:

virtual ~CWidgetView();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:

// Generated message map functions

protected:

int m_nWidget;

COleDropTarget m_oleDropTarget;

CPoint m_ptPrevPos;

CPoint m_ptOldImage;

CSize m_sizeDelta;

CWidget* m_pWidget;

//{{AFX_MSG(CWidgetView)

afx_msg void OnLButtonDown(UINT nFlags, CPoint point);

afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

#ifndef _DEBUG // debug version in WidgView.cpp

inline CWidgetDoc* CWidgetView::GetDocument()

{ return (CWidgetDoc*)m_pDocument; }

#endif

/////////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}

// Microsoft Developer Studio will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_WIDGVIEW_H__877329C1_C22E_11D0_B2D8_444553540000__INCLUDED_)

WidgView.cpp

// WidgView.cpp : implementation of the CWidgetView class

//

#include "stdafx.h"

#include "Widget.h"

#include "WidgBase.h"

#include "Circle.h"

#include "Triangle.h"

#include "Square.h"

#include "WidgDoc.h"

#include "WidgView.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

/////////////////////////////////////////////////////////////////////////////

// CWidgetView

IMPLEMENT_DYNCREATE(CWidgetView, CScrollView)

BEGIN_MESSAGE_MAP(CWidgetView, CScrollView)

//{{AFX_MSG_MAP(CWidgetView)

ON_WM_LBUTTONDOWN()

ON_WM_CREATE()

//}}AFX_MSG_MAP

// Standard printing commands

ON_COMMAND(ID_FILE_PRINT, CScrollView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_DIRECT, CScrollView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_PREVIEW, CScrollView::OnFilePrintPreview)

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////

// CWidgetView construction/destruction

CWidgetView::CWidgetView()

{

}

CWidgetView::~CWidgetView()

{

}

BOOL CWidgetView::PreCreateWindow(CREATESTRUCT& cs)

{

// TODO: Modify the Window class or styles here by modifying

// the CREATESTRUCT cs

return CScrollView::PreCreateWindow(cs);

}

/////////////////////////////////////////////////////////////////////////////

// CWidgetView drawing

void CWidgetView::OnDraw(CDC* pDC)

{

CWidgetDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

int nCount = pDoc->GetWidgetCount ();

if (nCount) {

for (int i=0; i<nCount; i++)

pDoc->GetWidget (i)->Draw (pDC);

}

}

void CWidgetView::OnInitialUpdate()

{

CScrollView::OnInitialUpdate();

CSize sizeTotal;

sizeTotal.cx = sizeTotal.cy = 1024;

SetScrollSizes(MM_TEXT, sizeTotal);

m_nWidget = -1; // Index of the widget that's being dragged

m_pWidget = NULL; // Pointer to temporary widget used in drag imaging

}

/////////////////////////////////////////////////////////////////////////////

// CWidgetView printing

BOOL CWidgetView::OnPreparePrinting(CPrintInfo* pInfo)

{

// default preparation

return DoPreparePrinting(pInfo);

}

void CWidgetView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add extra initialization before printing

}

void CWidgetView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add cleanup after printing

}

/////////////////////////////////////////////////////////////////////////////

// CWidgetView diagnostics

#ifdef _DEBUG

void CWidgetView::AssertValid() const

{

CScrollView::AssertValid();

}

void CWidgetView::Dump(CDumpContext& dc) const

{

CScrollView::Dump(dc);

}

CWidgetDoc* CWidgetView::GetDocument() // non-debug version is inline

{

ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CWidgetDoc)));

return (CWidgetDoc*)m_pDocument;

}

#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////

// CWidgetView message handlers

void CWidgetView::OnLButtonDown(UINT nFlags, CPoint point)

{

CWidgetDoc* pDoc = GetDocument ();

int nCount = pDoc->GetWidgetCount ();

if (nCount) {

//

// Convert the click point to logical coordinates.

//

CClientDC dc (this);

OnPrepareDC (&dc);

dc.DPtoLP (&point);

//

// Find out whether a widget was clicked.

//

int i;

BOOL bHit = FALSE;

for (i=nCount - 1; i>=0 && !bHit; i--) {

CWidget* pWidget = pDoc->GetWidget (i);

if (pWidget->PtInWidget (point)) {

bHit = TRUE;

}

}

//

// If a widget was clicked, initiate a drag-drop operation.

//

if (bHit) {

m_nWidget = ++i;

HANDLE hData =

::GlobalAlloc (GMEM_MOVEABLE | GMEM_SHARE, sizeof (WIDGETDATA));

WIDGETDATA* pWidgetData = (WIDGETDATA*) ::GlobalLock (hData);

CWidget* pWidget = pDoc->GetWidget (m_nWidget);

pWidgetData->nType = pWidget->GetType ();

pWidget->GetItemRect (&pWidgetData->rcItem);

pWidgetData->ptDrag = point;

::GlobalUnlock (hData);

COleDataSource ods;

UINT nFormat = ((CWidgetApp*) AfxGetApp ())->m_nFormat;

ods.CacheGlobalData (nFormat, hData);

DROPEFFECT de = ods.DoDragDrop (DROPEFFECT_COPY | DROPEFFECT_MOVE);

if (de == DROPEFFECT_MOVE) {

pDoc->RemoveWidget (m_nWidget);

pDoc->UpdateAllViews (NULL);

}

m_nWidget = -1;

}

}

CScrollView::OnLButtonDown(nFlags, point);

}

DROPEFFECT CWidgetView::OnDragEnter(COleDataObject* pDataObject,

DWORD dwKeyState, CPoint point)

{

CScrollView::OnDragEnter(pDataObject, dwKeyState, point);

//

// Return now if the object being dragged is not a widget.

//

UINT nFormat = ((CWidgetApp*) AfxGetApp ())->m_nFormat;

if (!pDataObject->IsDataAvailable (nFormat))

return DROPEFFECT_NONE;

//

// Create a temporary widget for drag imaging.

//

HGLOBAL hData = pDataObject->GetGlobalData (nFormat);

WIDGETDATA* pWidgetData = (WIDGETDATA*) ::GlobalLock (hData);

int nType = pWidgetData->nType;

CRect rect = pWidgetData->rcItem;

CPoint pt = pWidgetData->ptDrag;

::GlobalUnlock (hData);

switch (nType) {

case 0: // Circle

m_pWidget = new CCircle (rect);

break;

case 1: // Triangle

m_pWidget = new CTriangle (rect);

break;

case 2: // Square

m_pWidget = new CSquare (rect);

break;

default: // Just in case

return DROPEFFECT_NONE;

}

//

// Begin dragging.

//

CClientDC dc (this);

OnPrepareDC (&dc);

dc.DPtoLP (&point);

m_sizeDelta.cx = pt.x - rect.left;

m_sizeDelta.cy = pt.y - rect.top;

CPoint ptDrag (point.x - m_sizeDelta.cx, point.y - m_sizeDelta.cy);

m_ptOldImage.x = m_ptOldImage.y = -32000;

m_ptPrevPos = point;

return (dwKeyState & MK_CONTROL) ? DROPEFFECT_COPY : DROPEFFECT_MOVE;

}

DROPEFFECT CWidgetView::OnDragOver(COleDataObject* pDataObject,

DWORD dwKeyState, CPoint point)

{

CScrollView::OnDragOver(pDataObject, dwKeyState, point);

//

// Return now if the object being dragged is not a widget.

//

if (m_pWidget == NULL)

return DROPEFFECT_NONE;

//

// Erase the old drag image and draw a new one if the cursor has moved.

//

CClientDC dc (this);

OnPrepareDC (&dc);

dc.DPtoLP (&point);

if (point != m_ptPrevPos) {

m_pWidget->DrawDragImage (&dc, m_ptOldImage);

CPoint ptDrag (point.x - m_sizeDelta.cx, point.y - m_sizeDelta.cy);

m_pWidget->DrawDragImage (&dc, ptDrag);

m_ptOldImage = ptDrag;

m_ptPrevPos = point;

}

return (dwKeyState & MK_CONTROL) ? DROPEFFECT_COPY : DROPEFFECT_MOVE;

}

void CWidgetView::OnDragLeave()

{

CScrollView::OnDragLeave();

//

// Erase the old drag image and delete the temporary widget.

//

if (m_pWidget != NULL) {

CClientDC dc (this);

OnPrepareDC (&dc);

m_pWidget->DrawDragImage (&dc, m_ptOldImage);

delete m_pWidget;

m_pWidget = NULL;

}

}

BOOL CWidgetView::OnDrop(COleDataObject* pDataObject, DROPEFFECT dropEffect,

CPoint point)

{

CScrollView::OnDrop(pDataObject, dropEffect, point);

//

// Convert point to logical coordinates.

//

CClientDC dc (this);

OnPrepareDC (&dc);

dc.DPtoLP (&point);

//

// Erase the old drag image and delete the temporary widget.

//

if (m_pWidget != NULL) {

m_pWidget->DrawDragImage (&dc, m_ptOldImage);

delete m_pWidget;

m_pWidget = NULL;

}

//

// Retrieve the HGLOBAL from the data object.

//

UINT nFormat = ((CWidgetApp*) AfxGetApp ())->m_nFormat;

HGLOBAL hData = pDataObject->GetGlobalData (nFormat);

if (hData == NULL)

return FALSE;

//

// Create a widget from the data in the HGLOBAL.

//

WIDGETDATA* pWidgetData = (WIDGETDATA*) ::GlobalLock (hData);

int nType = pWidgetData->nType;

CRect rect = pWidgetData->rcItem;

::GlobalUnlock (hData);

CRect rcItem;

rcItem.left = point.x - m_sizeDelta.cx;

rcItem.top = point.y - m_sizeDelta.cy;

rcItem.right = rcItem.left + rect.Width ();

rcItem.bottom = rcItem.top + rect.Height ();

CWidgetDoc* pDoc = GetDocument ();

pDoc->AddWidget (nType, &rcItem);

pDoc->UpdateAllViews (NULL);

return TRUE;

}

int CWidgetView::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

if (CScrollView::OnCreate(lpCreateStruct) == -1)

return -1;

m_oleDropTarget.Register (this);

return 0;

}

WidgDoc.h

// WidgDoc.h : interface of the CWidgetDoc class

//

/////////////////////////////////////////////////////////////////////////////

#if !defined(AFX_WIDGDOC_H__877329BF_C22E_11D0_B2D8_444553540000__INCLUDED_)

#define AFX_WIDGDOC_H__877329BF_C22E_11D0_B2D8_444553540000__INCLUDED_

#if _MSC_VER >= 1000

#pragma once

#endif // _MSC_VER >= 1000

class CWidgetDoc : public CDocument

{

protected: // create from serialization only

CWidgetDoc();

DECLARE_DYNCREATE(CWidgetDoc)

// Attributes

public:

// Operations

public:

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CWidgetDoc)

public:

virtual BOOL OnNewDocument();

virtual void Serialize(CArchive& ar);

virtual void DeleteContents();

//}}AFX_VIRTUAL

// Implementation

public:

BOOL RemoveWidget (int nIndex);

int AddWidget (int nType, LPCRECT pRect);

CWidget* GetWidget (int nIndex);

int GetWidgetCount ();

virtual ~CWidgetDoc();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:

// Generated message map functions

protected:

CObArray m_aWidgets;

//{{AFX_MSG(CWidgetDoc)

afx_msg void OnInsertTriangle();

afx_msg void OnInsertCircle();

afx_msg void OnInsertSquare();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

/////////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}

// Microsoft Developer Studio will insert additional declarations immediately

// before the previous line.

#endif // !defined(AFX_WIDGDOC_H__877329BF_C22E_11D0_B2D8_444553540000__INCLUDED_)

WidgDoc.cpp

// WidgDoc.cpp : implementation of the CWidgetDoc class

//

#include "stdafx.h"

#include "Widget.h"

#include "WidgBase.h"

#include "Circle.h"

#include "Triangle.h"

#include "Square.h"

#include "WidgDoc.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

/////////////////////////////////////////////////////////////////////////////

// CWidgetDoc

IMPLEMENT_DYNCREATE(CWidgetDoc, CDocument)

BEGIN_MESSAGE_MAP(CWidgetDoc, CDocument)

//{{AFX_MSG_MAP(CWidgetDoc)

ON_COMMAND(ID_INSERT_TRIANGLE, OnInsertTriangle)

ON_COMMAND(ID_INSERT_CIRCLE, OnInsertCircle)

ON_COMMAND(ID_INSERT_SQUARE, OnInsertSquare)

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////

// CWidgetDoc construction/destruction

CWidgetDoc::CWidgetDoc()

{

// TODO: add one-time construction code here

}

CWidgetDoc::~CWidgetDoc()

{

}

BOOL CWidgetDoc::OnNewDocument()

{

if (!CDocument::OnNewDocument())

return FALSE;

m_aWidgets.SetSize (0, 16);

return TRUE;

}

/////////////////////////////////////////////////////////////////////////////

// CWidgetDoc serialization

void CWidgetDoc::Serialize(CArchive& ar)

{

m_aWidgets.Serialize (ar);

}

/////////////////////////////////////////////////////////////////////////////

// CWidgetDoc diagnostics

#ifdef _DEBUG

void CWidgetDoc::AssertValid() const

{

CDocument::AssertValid();

}

void CWidgetDoc::Dump(CDumpContext& dc) const

{

CDocument::Dump(dc);

}

#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////

// CWidgetDoc commands

void CWidgetDoc::OnInsertCircle()

{

AddWidget (0, CRect (10, 10, 100, 100));

UpdateAllViews (NULL);

}

void CWidgetDoc::OnInsertTriangle()

{

AddWidget (1, CRect (10, 10, 100, 100));

UpdateAllViews (NULL);

}

void CWidgetDoc::OnInsertSquare()

{

AddWidget (2, CRect (10, 10, 100, 100));

UpdateAllViews (NULL);

}

void CWidgetDoc::DeleteContents()

{

int nCount = m_aWidgets.GetSize ();

while (nCount--)

delete m_aWidgets[nCount];

m_aWidgets.RemoveAll ();

CDocument::DeleteContents();

}

int CWidgetDoc::GetWidgetCount()

{

return m_aWidgets.GetSize ();

}

CWidget* CWidgetDoc::GetWidget(int nIndex)

{

if (nIndex > (m_aWidgets.GetSize () - 1))

return NULL;

return (CWidget*) m_aWidgets[nIndex];

}

int CWidgetDoc::AddWidget(int nType, LPCRECT pRect)

{

int nIndex;

CWidget* pWidget;

try {

switch (nType) {

case 0: // Circle widget

pWidget = new CCircle (pRect);

break;

case 1: // Triangle widget

pWidget = new CTriangle (pRect);

break;

case 2: // Square widget

pWidget = new CSquare (pRect);

break;

}

nIndex = m_aWidgets.Add (pWidget);

}

catch (CMemoryException* e) {

AfxMessageBox ("Out of memory");

if (pWidget != NULL)

delete pWidget;

e->Delete ();

return -1;

}

SetModifiedFlag ();

return nIndex;

}

BOOL CWidgetDoc::RemoveWidget(int nIndex)

{

if (nIndex >= m_aWidgets.GetSize ())

return FALSE;

delete m_aWidgets[nIndex];

m_aWidgets.RemoveAt (nIndex);

return TRUE;

}

Circle.h

#if !defined(AFX_CIRCLE_H__877329CB_C22E_11D0_B2D8_444553540001__INCLUDED_)

#define AFX_CIRCLE_H__877329CB_C22E_11D0_B2D8_444553540001__INCLUDED_

#if _MSC_VER >= 1000

#pragma once

#endif // _MSC_VER >= 1000

// Circle.h : header file

//

/////////////////////////////////////////////////////////////////////////////

// CCircle

class CCircle : public CWidget

{

DECLARE_SERIAL(CCircle)

CCircle(); // protected constructor used by dynamic creation

// Attributes

public:

// Operations

public:

virtual ~CCircle();

virtual int GetType ();

CCircle (LPCRECT pRect);

CCircle (int left, int top, int right, int bottom);

virtual BOOL PtInWidget (POINT point);

virtual void DrawDragImage (CDC* pDC, POINT point);

virtual void Draw (CDC* pDC);

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CCircle)

//}}AFX_VIRTUAL

virtual void Serialize (CArchive& ar);

// Implementation

protected:

// Generated message map functions

//{{AFX_MSG(CCircle)

// NOTE - the ClassWizard will add and remove member functions here.

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

/////////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}

// Microsoft Developer Studio will insert additional declarations immediately

// before the previous line.

#endif // !defined(AFX_CIRCLE_H__877329CB_C22E_11D0_B2D8_444553540001__INCLUDED_)

Circle.cpp

// Circle.cpp : implementation file

//

#include "stdafx.h"

#include "Widget.h"

#include "WidgBase.h"

#include "Circle.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

/////////////////////////////////////////////////////////////////////////////

// CCircle

IMPLEMENT_SERIAL(CCircle, CWidget, 1)

CCircle::CCircle()

{

}

CCircle::CCircle (LPCRECT pRect)

{

m_rect = *pRect;

}

CCircle::CCircle (int left, int top, int right, int bottom)

{

m_rect.SetRect (left, top, right, bottom);

}

CCircle::~CCircle()

{

}

BEGIN_MESSAGE_MAP(CCircle, CWidget)

//{{AFX_MSG_MAP(CCircle)

// NOTE - the ClassWizard will add and remove mapping macros here.

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////

// CCircle message handlers

void CCircle::Draw(CDC * pDC)

{

CBrush brush (RGB (255, 255, 0)); // Yellow

CBrush* pOldBrush = pDC->SelectObject (&brush);

pDC->Ellipse (m_rect);

pDC->SelectObject (pOldBrush);

}

void CCircle::DrawDragImage(CDC * pDC, POINT point)

{

int nOldMode = pDC->SetROP2 (R2_NOT);

CBrush* pOldBrush = (CBrush*) pDC->SelectStockObject (NULL_BRUSH);

pDC->Ellipse (point.x, point.y,

point.x + m_rect.Width (),

point.y + m_rect.Height ());

pDC->SelectObject (pOldBrush);

pDC->SetROP2 (nOldMode);

}

BOOL CCircle::PtInWidget(POINT point)

{

if (!m_rect.PtInRect (point))

return FALSE;

CPoint ptCenter;

ptCenter.x = m_rect.left + (m_rect.Width () / 2);

ptCenter.y = m_rect.top + (m_rect.Height () / 2);

int cx = point.x - ptCenter.x;

int cy = point.y - ptCenter.y;

if ((cx * cx) + (cy * cy) <

(m_rect.Width () / 2) * (m_rect.Width () / 2))

return TRUE;

return FALSE;

}

int CCircle::GetType()

{

return 0; // Circle

}

void CCircle::Serialize (CArchive& ar)

{

CWidget::Serialize (ar);

if (ar.IsStoring ())

ar << m_rect;

else

ar >> m_rect;

}

Triangle.h

#if !defined(AFX_TRIANGLE_H__877329CB_C22E_11D0_B2D8_444553540000__INCLUDED_)

#define AFX_TRIANGLE_H__877329CB_C22E_11D0_B2D8_444553540000__INCLUDED_

#if _MSC_VER >= 1000

#pragma once

#endif // _MSC_VER >= 1000

// Triangle.h : header file

//

/////////////////////////////////////////////////////////////////////////////

// CTriangle

class CTriangle : public CWidget

{

DECLARE_SERIAL(CTriangle)

CTriangle(); // protected constructor used by dynamic creation

// Attributes

public:

// Operations

public:

virtual ~CTriangle();

virtual int GetType ();

CTriangle (LPCRECT pRect);

CTriangle (int left, int top, int right, int bottom);

virtual BOOL PtInWidget (POINT point);

virtual void DrawDragImage (CDC* pDC, POINT point);

virtual void Draw (CDC* pDC);

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CTriangle)

//}}AFX_VIRTUAL

virtual void Serialize (CArchive& ar);

// Implementation

protected:

// Generated message map functions

//{{AFX_MSG(CTriangle)

// NOTE - the ClassWizard will add and remove member functions here.

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

/////////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}

// Microsoft Developer Studio will insert additional declarations immediately

// before the previous line.

#endif // !defined(AFX_TRIANGLE_H__877329CB_C22E_11D0_B2D8_444553540000__INCLUDED_)

Triangle.cpp

// Triangle.cpp : implementation file

//

#include "stdafx.h"

#include "Widget.h"

#include "WidgBase.h"

#include "Triangle.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

/////////////////////////////////////////////////////////////////////////////

// CTriangle

IMPLEMENT_SERIAL(CTriangle, CWidget, 1)

CTriangle::CTriangle()

{

}

CTriangle::CTriangle (LPCRECT pRect)

{

m_rect = *pRect;

}

CTriangle::CTriangle (int left, int top, int right, int bottom)

{

m_rect.SetRect (left, top, right, bottom);

}

CTriangle::~CTriangle()

{

}

BEGIN_MESSAGE_MAP(CTriangle, CWidget)

//{{AFX_MSG_MAP(CTriangle)

// NOTE - the ClassWizard will add and remove mapping macros here.

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////

// CTriangle message handlers

void CTriangle::Draw(CDC * pDC)

{

CBrush brush (RGB (0, 0, 255)); // Blue

CBrush* pOldBrush = pDC->SelectObject (&brush);

CPoint points[3];

points[0].x = m_rect.left;

points[0].y = m_rect.bottom;

points[1].x = m_rect.left + ((m_rect.right - m_rect.left) / 2);

points[1].y = m_rect.top;

points[2].x = m_rect.right;

points[2].y = m_rect.bottom;

pDC->Polygon (points, 3);

pDC->SelectObject (pOldBrush);

}

void CTriangle::DrawDragImage(CDC * pDC, POINT point)

{

int nOldMode = pDC->SetROP2 (R2_NOT);

CBrush* pOldBrush = (CBrush*) pDC->SelectStockObject (NULL_BRUSH);

CPoint points[3];

points[0].x = point.x;

points[0].y = point.y + m_rect.Height ();

points[1].x = point.x + (m_rect.Width () / 2);

points[1].y = point.y;

points[2].x = point.x + m_rect.Width ();

points[2].y = point.y + m_rect.Height ();

pDC->Polygon (points, 3);

pDC->SelectObject (pOldBrush);

pDC->SetROP2 (nOldMode);

}

BOOL CTriangle::PtInWidget(POINT point)

{

if (!m_rect.PtInRect (point))

return FALSE;

int cx = min (point.x - m_rect.left, m_rect.right - point.x);

return ((m_rect.bottom - point.y) <= (2 * cx));

}

int CTriangle::GetType()

{

return 1; // Triangle

}

void CTriangle::Serialize (CArchive& ar)

{

CWidget::Serialize (ar);

if (ar.IsStoring ())

ar << m_rect;

else

ar >> m_rect;

}

Square.h

#if !defined(AFX_SQUARE_H__877329CB_C22E_11D0_B2D8_444553540002__INCLUDED_)

#define AFX_SQUARE_H__877329CB_C22E_11D0_B2D8_444553540002__INCLUDED_

#if _MSC_VER >= 1000

#pragma once

#endif // _MSC_VER >= 1000

// Square.h : header file

//

/////////////////////////////////////////////////////////////////////////////

// CSquare

class CSquare : public CWidget

{

DECLARE_SERIAL(CSquare)

CSquare(); // protected constructor used by dynamic creation

// Attributes

public:

// Operations

public:

virtual ~CSquare();

virtual int GetType ();

CSquare (LPCRECT pRect);

CSquare (int left, int top, int right, int bottom);

virtual BOOL PtInWidget (POINT point);

virtual void DrawDragImage (CDC* pDC, POINT point);

virtual void Draw (CDC* pDC);

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CSquare)

//}}AFX_VIRTUAL

virtual void Serialize (CArchive& ar);

// Implementation

protected:

// Generated message map functions

//{{AFX_MSG(CSquare)

// NOTE - the ClassWizard will add and remove member functions here.

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

/////////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}

// Microsoft Developer Studio will insert additional declarations immediately

// before the previous line.

#endif // !defined(AFX_SQUARE_H__877329CB_C22E_11D0_B2D8_444553540002__INCLUDED_)

Square.cpp

// Square.cpp : implementation file

//

#include "stdafx.h"

#include "Widget.h"

#include "WidgBase.h"

#include "Square.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

/////////////////////////////////////////////////////////////////////////////

// CSquare

IMPLEMENT_SERIAL(CSquare, CWidget, 1)

CSquare::CSquare()

{

}

CSquare::CSquare (LPCRECT pRect)

{

m_rect = *pRect;

}

CSquare::CSquare (int left, int top, int right, int bottom)

{

m_rect.SetRect (left, top, right, bottom);

}

CSquare::~CSquare()

{

}

BEGIN_MESSAGE_MAP(CSquare, CWidget)

//{{AFX_MSG_MAP(CSquare)

// NOTE - the ClassWizard will add and remove mapping macros here.

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////

// CSquare message handlers

void CSquare::Draw(CDC * pDC)

{

CBrush brush (RGB (255, 0, 0)); // Red

CBrush* pOldBrush = pDC->SelectObject (&brush);

pDC->Rectangle (m_rect);

pDC->SelectObject (pOldBrush);

}

void CSquare::DrawDragImage(CDC * pDC, POINT point)

{

int nOldMode = pDC->SetROP2 (R2_NOT);

CBrush* pOldBrush = (CBrush*) pDC->SelectStockObject (NULL_BRUSH);

pDC->Rectangle (point.x, point.y,

point.x + m_rect.Width (),

point.y + m_rect.Height ());

pDC->SelectObject (pOldBrush);

pDC->SetROP2 (nOldMode);

}

BOOL CSquare::PtInWidget(POINT point)

{

return m_rect.PtInRect (point);

}

int CSquare::GetType()

{

return 2; // Square

}

void CSquare::Serialize (CArchive& ar)

{

CWidget::Serialize (ar);

if (ar.IsStoring ())

ar << m_rect;

else

ar >> m_rect;

}

WidgBase.h

#if !defined(AFX_WIDGBASE_H__877329CA_C22E_11D0_B2D8_444553540000__INCLUDED_)

#define AFX_WIDGBASE_H__877329CA_C22E_11D0_B2D8_444553540000__INCLUDED_

#if _MSC_VER >= 1000

#pragma once

#endif // _MSC_VER >= 1000

// WidgBase.h : header file

//

/////////////////////////////////////////////////////////////////////////////

// CWidget command target

class CWidget : public CCmdTarget

{

DECLARE_DYNCREATE(CWidget)

CWidget(); // protected constructor used by dynamic creation

// Attributes

public:

// Operations

public:

virtual void GetItemRect (LPRECT pRect);

virtual ~CWidget();

virtual int GetType ();

virtual BOOL PtInWidget (POINT point);

virtual void DrawDragImage (CDC* pDC, POINT point);

virtual void Draw (CDC* pDC);

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CWidget)

//}}AFX_VIRTUAL

// Implementation

protected:

CRect m_rect;

// Generated message map functions

//{{AFX_MSG(CWidget)

// NOTE - the ClassWizard will add and remove member functions here.

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

/////////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}

// Microsoft Developer Studio will insert additional declarations immediately

// before the previous line.

#endif // !defined(AFX_WIDGBASE_H__877329CA_C22E_11D0_B2D8_444553540000__INCLUDED_)

WidgBase.cpp

// WidgBase.cpp : implementation file

//

#include "stdafx.h"

#include "Widget.h"

#include "WidgBase.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

/////////////////////////////////////////////////////////////////////////////

// CWidget

IMPLEMENT_DYNCREATE(CWidget, CCmdTarget)

CWidget::CWidget()

{

}

CWidget::~CWidget()

{

}

BEGIN_MESSAGE_MAP(CWidget, CCmdTarget)

//{{AFX_MSG_MAP(CWidget)

// NOTE - the ClassWizard will add and remove mapping macros here.

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////

// CWidget message handlers

void CWidget::Draw(CDC * pDC)

{

ASSERT (FALSE); // Override this function in a derived class

}

void CWidget::DrawDragImage(CDC * pDC, POINT point)

{

ASSERT (FALSE); // Override this function in a derived class

}

BOOL CWidget::PtInWidget(POINT point)

{

ASSERT (FALSE); // Override this function in a derived class

return FALSE;

}

int CWidget::GetType()

{

ASSERT (FALSE); // Override this function in a derived class

return -1;

}

void CWidget::GetItemRect(LPRECT pRect)

{

*pRect = m_rect;

}

A good place to begin examining the code is with the data structure WIDGET uses to describe a widget object:

typedef struct tagWIDGETDATA {

int nType;

RECT rcItem;

POINT ptDrag;

} WIDGETDATA;

The type field holds 0, 1, or 2, identifying the widget as a circle, triangle, or square. rcItem holds the coordinates of the widget’s bounding rectangle, and ptDrag holds the coor­dinates of the drag point—the point at which the widget is grabbed to begin a drag and drop operation. The difference between the drag point and the upper-left corner of the bounding rectangle is used as an offset from the current cursor location whenever WIDGET draws a drag image.

When a widget is clicked, the OnLButtonDown code in the view allocates a global memory block and fills it with a WIDGETDATA structure describing the widget. When a drop occurs, the view’s OnDrop handler uses the infor­mation in the structure to create a new widget. However, before the drop occurs, the view’s OnDragEnter function, which is called when the cursor enters the window during a drag operation, uses the information in the WIDGETDATA structure to create a temporary widget. The temporary widget’s address is tucked away in the view’s m_pWidget data member, as shown in Figure 4.

Figure 4 Saving a Widget's Address

HGLOBAL hData = pDataObject->GetGlobalData (nFormat);

WIDGETDATA* pWidgetData = (WIDGETDATA*) ::GlobalLock (hData);

int nType = pWidgetData->nType;

CRect rect = pWidgetData->rcItem;

CPoint pt = pWidgetData->ptDrag;

::GlobalUnlock (hData);

switch (nType) {

case 0: // Circle

m_pWidget = new CCircle (rect);

break;

case 1: // Triangle

m_pWidget = new CTriangle (rect);

break;

case 2: // Square

m_pWidget = new CSquare (rect);

break;

default: // Just in case

return DROPEFFECT_NONE;

}

Why create a temporary widget when the cursor enters the window? Because widget objects have DrawDragImage functions that draw widget outlines by inverting pixels on the screen. As the cursor is moved over the view during a drag and drop operation, the view’s OnDragOver function is called repeatedly. OnDragOver calls the temporary widget’s DrawDragImage function twice—once to erase the previous drag image and once to draw the image in the new location. Thus, the drag image follows the cursor around the window, and it’s the widget itself—a copy of it, anyway—that does the actual drawing. Calls to Draw­DragImage, of course, go through the pointer stored in m_p­Widget. The temporary widget is deleted when the view’s OnDragLeave function is called, indicating that the cursor left the window.

That’s really all there is to it. The key (again) is that the drop target, not the drop source, does the drag imaging. Since the drop target gets called each time the cursor moves over its window, it’s a simple matter for it to grab a screen DC and update a drag image. All it needs to know is what to draw and, as WIDGET demonstrates, that information can be provided either directly or indirectly by the drop source. I suspect that, if you could peel the cover off the Microsoft Windows 95 and Windows NT 4.0 shell and look at the source code, you’d find that it does something very similar to what I’ve done here.

Your Needs, Your Ideas

Are there tough Win32-based programming questions you’d like to see answered in this column? If so, mail them to me at the address listed below. I regret that time doesn’t permit me to respond individually to all questions, but rest assured that I’ll read each and every one and consider all for inclusion in a future installment of Wicked Code. u

To obtain complete source code listings, see page 5.

Have a tricky issue dealing with Windows? Send your questions via email to Jeff Prosise: 72241.44@compuserve.com