Programming Windows with MFC, Second Edition
The application shown in Figure 19-3 demonstrates one way to apply the concepts, principles, and code fragments presented in this chapter to the real world. Widget creates triangular "widgets" of various colors in response to commands on the Insert menu. You can transfer widgets to and from the OLE clipboard using the commands on the Edit menu. Before you can use the Cut and Copy commands, you must select a widget by clicking it. The widget will turn green to indicate that it is in a selected state. You can also move and copy widgets using OLE drag-and-drop. If you hold down the Ctrl key when a drop is performed, the widget is copied; otherwise, it's moved. For a graphical demonstration of OLE drag-and-drop in action, run two instances of Widget side by side and drag widgets back and forth between them.
Figure 19-3. The Widget window.
Figure 19-4 shows the pertinent parts of Widget's source code. WidgetView.cpp contains most of the good stuff, including the handlers for the Cut, Copy, and Paste commands. It also contains the overridden versions of OnDragEnter, OnDragOver, OnDragLeave, and OnDrop as well as the code that initiates a drag-and-drop data transfer when the left mouse button is clicked. (See OnLButtonDown.) Widgets are transferred through global memory. Widget registers a private clipboard format for widgets and uses it in calls to COleDataSource::CacheGlobalData and COleDataObject::GetGlobalData. The ID is stored in the application object and retrieved using CWidgetApp::GetClipboardFormat.
Figure 19-4. The Widget application.
Widget.h
// Widget.h : main header file for the WIDGET application // #if !defined(AFX_WIDGET_H__02909A45_3F5C_11D2_AC89_006008A8274D__INCLUDED_) #define AFX_WIDGET_H__02909A45_3F5C_11D2_AC89_006008A8274D__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 #ifndef __AFXWIN_H__ #error include `stdafx.h' before including this file for PCH #endif #include "resource.h" // main symbols /////////////////////////////////////////////////////////////////////////// // CWidgetApp: // See Widget.cpp for the implementation of this class // class CWidgetApp : public CWinApp { public: UINT GetClipboardFormat (); CWidgetApp(); // Overrides // ClassWizard generated virtual function overrides //AFX_VIRTUAL // Implementation //AFX_MSG DECLARE_MESSAGE_MAP() protected: UINT m_nFormat; }; /////////////////////////////////////////////////////////////////////////// // // Microsoft Visual C++ will insert additional declarations // immediately before the previous line. #endif // !defined(AFX_WIDGET_H__02909A45_3F5C_11D2_AC89_006008A8274D__INCLUDED_) |
Widget.cpp
// Widget.cpp : Defines the class behaviors for the application. // #include "stdafx.h" #include "Widget.h" #include "MainFrm.h" #include "WidgetDoc.h" #include "WidgetView.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif /////////////////////////////////////////////////////////////////////////// // CWidgetApp BEGIN_MESSAGE_MAP(CWidgetApp, CWinApp) //AFX_MSG_MAP // Standard file based document commands ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew) ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen) END_MESSAGE_MAP() /////////////////////////////////////////////////////////////////////////// // CWidgetApp construction CWidgetApp::CWidgetApp() { // TODO: add construction code here, // Place all significant initialization in InitInstance } /////////////////////////////////////////////////////////////////////////// // The one and only CWidgetApp object CWidgetApp theApp; /////////////////////////////////////////////////////////////////////////// // CWidgetApp initialization BOOL CWidgetApp::InitInstance() { if (!AfxOleInit ()) { AfxMessageBox (_T ("AfxOleInit failed")); return FALSE; } SetRegistryKey(_T("Local AppWizard-Generated Applications")); LoadStdProfileSettings(); // Load standard INI file // options (including MRU) // Register the application's document templates. Document templates // serve as the connection between documents, frame windows and views. CSingleDocTemplate* pDocTemplate; pDocTemplate = new CSingleDocTemplate( IDR_MAINFRAME, RUNTIME_CLASS(CWidgetDoc), RUNTIME_CLASS(CMainFrame), // main SDI frame window RUNTIME_CLASS(CWidgetView)); AddDocTemplate(pDocTemplate); // Enable DDE Execute open EnableShellOpen(); RegisterShellFileTypes(TRUE); // Parse command line for standard shell commands, DDE, file open CCommandLineInfo cmdInfo; ParseCommandLine(cmdInfo); // Dispatch commands specified on the command line if (!ProcessShellCommand(cmdInfo)) return FALSE; // The one and only window has been initialized, so show and update it. m_pMainWnd->ShowWindow(SW_SHOW); m_pMainWnd->UpdateWindow(); // Enable drag/drop open m_pMainWnd->DragAcceptFiles(); // // Register a private clipboard format for widgets. // m_nFormat = ::RegisterClipboardFormat (_T ("Widget")); return TRUE; } /////////////////////////////////////////////////////////////////////////// // CAboutDlg dialog used for App About class CAboutDlg : public CDialog { public: CAboutDlg(); // Dialog Data //{{AFX_DATA(CAboutDlg) enum { IDD = IDD_ABOUTBOX }; //}}AFX_DATA // ClassWizard generated virtual function overrides //AFX_VIRTUAL // Implementation protected: //AFX_MSG DECLARE_MESSAGE_MAP() }; CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD) { //AFX_DATA_INIT } void CAboutDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //AFX_DATA_MAP } BEGIN_MESSAGE_MAP(CAboutDlg, CDialog) //AFX_MSG_MAP END_MESSAGE_MAP() // App command to run the dialog void CWidgetApp::OnAppAbout() { CAboutDlg aboutDlg; aboutDlg.DoModal(); } /////////////////////////////////////////////////////////////////////////// // CWidgetApp message handlers UINT CWidgetApp::GetClipboardFormat() { return m_nFormat; } |
WidgetDoc.h
// WidgetDoc.h : interface of the CWidgetDoc class // /////////////////////////////////////////////////////////////////////////// #if !defined( AFX_WIDGETDOC_H__02909A4B_3F5C_11D2_AC89_006008A8274D__INCLUDED_) #define AFX_WIDGETDOC_H__02909A4B_3F5C_11D2_AC89_006008A8274D__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 #include "WidgetObj.h" typedef CTypedPtrArray<CObArray, CWidget*> CWidgetArray; 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 // Implementation public: BOOL RemoveWidget (int nIndex); int AddWidget (int x, int y, COLORREF color); CWidget* GetWidget (int nIndex); int GetWidgetCount (); virtual ~CWidgetDoc(); #ifdef _DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif protected: CWidgetArray m_arrWidgets; // Generated message map functions protected: //AFX_MSG DECLARE_MESSAGE_MAP() }; /////////////////////////////////////////////////////////////////////////// // // Microsoft Visual C++ will insert additional declarations // immediately before the previous line. #endif // !defined( // AFX_WIDGETDOC_H__02909A4B_3F5C_11D2_AC89_006008A8274D__INCLUDED_) |
WidgetDoc.cpp
CWidgetDoc::~CWidgetDoc() { } BOOL CWidgetDoc::OnNewDocument() { if (!CDocument::OnNewDocument()) return FALSE; return TRUE; } /////////////////////////////////////////////////////////////////////////// // CWidgetDoc serialization void CWidgetDoc::Serialize(CArchive& ar) { m_arrWidgets.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::DeleteContents() { int i = m_arrWidgets.GetSize (); while (i) delete m_arrWidgets[--i]; m_arrWidgets.RemoveAll (); CDocument::DeleteContents(); } int CWidgetDoc::GetWidgetCount() { return m_arrWidgets.GetSize (); } CWidget* CWidgetDoc::GetWidget(int nIndex) { if (nIndex >= m_arrWidgets.GetSize ()) return NULL; return (CWidget*) m_arrWidgets[nIndex]; } int CWidgetDoc::AddWidget(int x, int y, COLORREF color) { int nIndex = -1; CWidget* pWidget = NULL; try { pWidget = new CWidget (x, y, color); nIndex = m_arrWidgets.Add (pWidget); SetModifiedFlag (); } catch (CMemoryException* e) { AfxMessageBox (_T ("Out of memory")); if (pWidget != NULL) delete pWidget; e->Delete (); return -1; } return nIndex; } BOOL CWidgetDoc::RemoveWidget(int nIndex) { if (nIndex >= m_arrWidgets.GetSize ()) return FALSE; delete m_arrWidgets[nIndex]; m_arrWidgets.RemoveAt (nIndex); return TRUE; } void CWidgetDoc::OnInsertBlueWidget() { AddWidget (10, 10, RGB (0, 0, 255)); UpdateAllViews (NULL); } void CWidgetDoc::OnInsertRedWidget() { AddWidget (10, 10, RGB (255, 0, 0)); UpdateAllViews (NULL); } void CWidgetDoc::OnInsertYellowWidget() { AddWidget (10, 10, RGB (255, 255, 0)); UpdateAllViews (NULL); } |
WidgetView.h
// WidgetView.h : interface of the CWidgetView class // /////////////////////////////////////////////////////////////////////////// #if !defined( AFX_WIDGETVIEW_H__02909A4D_3F5C_11D2_AC89_006008A8274D__INCLUDED_) #define AFX_WIDGETVIEW_H__02909A4D_3F5C_11D2_AC89_006008A8274D__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 typedef struct tagWIDGETINFO { int x; // x coordinate of widget's upper left corner int y; // y coordinate of widget's upper left corner int cx; // Horizontal drag offset int cy; // Vertical drag offset COLORREF color; // The widget's color } WIDGETINFO; 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 // Implementation public: virtual ~CWidgetView(); #ifdef _DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif protected: CWidget* m_pTempWidget; CSize m_offset; CPoint m_pointLastImage; CPoint m_pointLastMsg; int m_nSel; COleDropTarget m_oleDropTarget; // Generated message map functions protected: //AFX_MSG DECLARE_MESSAGE_MAP() }; #ifndef _DEBUG // debug version in WidgetView.cpp inline CWidgetDoc* CWidgetView::GetDocument() { return (CWidgetDoc*)m_pDocument; } #endif /////////////////////////////////////////////////////////////////////////// // // Microsoft Visual C++ will insert additional declarations // immediately before the previous line. #endif // !defined( // AFX_WIDGETVIEW_H__02909A4D_3F5C_11D2_AC89_006008A8274D__INCLUDED_) |
WidgetView.cpp
// WidgetView.cpp : implementation of the CWidgetView class // #include "stdafx.h" #include "Widget.h" #include "WidgetDoc.h" #include "WidgetView.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 END_MESSAGE_MAP() /////////////////////////////////////////////////////////////////////////// // CWidgetView construction/destruction CWidgetView::CWidgetView() { } CWidgetView::~CWidgetView() { } BOOL CWidgetView::PreCreateWindow(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) { // // Draw all widgets. // for (int i=0; i<nCount; i++) pDoc->GetWidget (i)->Draw (pDC); // // Draw the selected widget if this view has the input focus. // if (m_nSel != -1 && CWnd::GetFocus () == this) pDoc->GetWidget (m_nSel)->DrawSelected (pDC); } } void CWidgetView::OnInitialUpdate() { CScrollView::OnInitialUpdate(); SetScrollSizes(MM_TEXT, CSize (1280, 1024)); m_pTempWidget = NULL; m_nSel = -1; } /////////////////////////////////////////////////////////////////////////// // 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 DROPEFFECT CWidgetView::OnDragEnter(COleDataObject* pDataObject, DWORD dwKeyState, CPoint point) { CScrollView::OnDragEnter(pDataObject, dwKeyState, point); // // If a widget is available from the drop source, create a temporary // widget for drag imaging. // UINT nFormat = ((CWidgetApp*) AfxGetApp ())->GetClipboardFormat (); HGLOBAL hData = pDataObject->GetGlobalData (nFormat); if (hData != NULL) { WIDGETINFO* pWidgetInfo = (WIDGETINFO*) ::GlobalLock (hData); int x = point.x - pWidgetInfo->cx; int y = point.y - pWidgetInfo->cy; m_offset.cx = pWidgetInfo->cx; m_offset.cy = pWidgetInfo->cy; COLORREF color = pWidgetInfo->color; ::GlobalUnlock (hData); ::GlobalFree (hData); m_pTempWidget = new CWidget (x, y, color); m_pointLastImage.x = m_pointLastImage.y = -32000; m_pointLastMsg = point; // // Return DROPEFFECT_COPY if the Ctrl key is down, or // DROPEFFECT_MOVE if it is not. // return (dwKeyState & MK_CONTROL) ? DROPEFFECT_COPY : DROPEFFECT_MOVE; } // // The cursor isn't carrying a widget. Indicate that the drop target // will not accept a drop. // m_pTempWidget = NULL; return DROPEFFECT_NONE; } 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_pTempWidget == NULL) return DROPEFFECT_NONE; // // Convert the drag point to logical coordinates. // CClientDC dc (this); OnPrepareDC (&dc); dc.DPtoLP (&point); // // If the cursor has moved, erase the old drag image and // draw a new one. // if (point != m_pointLastMsg) { CPoint pt (point.x - m_offset.cx, point.y - m_offset.cy); m_pTempWidget->DrawDragImage (&dc, m_pointLastImage); m_pTempWidget->DrawDragImage (&dc, pt); m_pointLastImage = pt; m_pointLastMsg = point; } // // Return DROPEFFECT_COPY if the Ctrl key is down, or DROPEFFECT_MOVE // if it is not. // return (dwKeyState & MK_CONTROL) ? DROPEFFECT_COPY : DROPEFFECT_MOVE; } void CWidgetView::OnDragLeave() { CScrollView::OnDragLeave(); // // Erase the last drag image and delete the temporary widget. // if (m_pTempWidget != NULL) { CClientDC dc (this); OnPrepareDC (&dc); m_pTempWidget->DrawDragImage (&dc, m_pointLastImage); delete m_pTempWidget; m_pTempWidget = NULL; } } BOOL CWidgetView::OnDrop(COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point) { CScrollView::OnDrop(pDataObject, dropEffect, point); // // Convert the drop point to logical coordinates. // CClientDC dc (this); OnPrepareDC (&dc); dc.DPtoLP (&point); // // Erase the last drag image and delete the temporary widget. // if (m_pTempWidget != NULL) { m_pTempWidget->DrawDragImage (&dc, m_pointLastImage); delete m_pTempWidget; m_pTempWidget = NULL; } // // Retrieve the HGLOBAL from the data object and create a widget. // UINT nFormat = ((CWidgetApp*) AfxGetApp ())->GetClipboardFormat (); HGLOBAL hData = pDataObject->GetGlobalData (nFormat); if (hData != NULL) { WIDGETINFO* pWidgetInfo = (WIDGETINFO*) ::GlobalLock (hData); int x = point.x - pWidgetInfo->cx; int y = point.y - pWidgetInfo->cy; COLORREF color = pWidgetInfo->color; ::GlobalUnlock (hData); ::GlobalFree (hData); CWidgetDoc* pDoc = GetDocument (); m_nSel = pDoc->AddWidget (x, y, color); pDoc->UpdateAllViews (NULL); return TRUE; } return FALSE; } int CWidgetView::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CScrollView::OnCreate(lpCreateStruct) == -1) return -1; m_oleDropTarget.Register (this); return 0; } void CWidgetView::OnLButtonDown(UINT nFlags, CPoint point) { CScrollView::OnLButtonDown(nFlags, 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 no widget was clicked, change the selection to NULL and exit. // if (!bHit) { m_nSel = -1; InvalidateRect (NULL, FALSE); return; } // // Select the widget that was clicked. // int nWidgetIndex = i + 1; if (m_nSel != nWidgetIndex) { m_nSel = nWidgetIndex; InvalidateRect (NULL, FALSE); UpdateWindow (); } // // Begin a drag-and-drop operation involving the selected widget. // HANDLE hData = ::GlobalAlloc (GMEM_MOVEABLE, sizeof (WIDGETINFO)); WIDGETINFO* pWidgetInfo = (WIDGETINFO*) ::GlobalLock (hData); CWidget* pWidget = pDoc->GetWidget (nWidgetIndex); ASSERT (pWidget != NULL); CRect rect = pWidget->GetRect (); pWidgetInfo->cx = point.x - rect.left; pWidgetInfo->cy = point.y - rect.top; pWidgetInfo->color = pWidget->GetColor (); ::GlobalUnlock (hData); COleDataSource ods; UINT nFormat = ((CWidgetApp*) AfxGetApp ())->GetClipboardFormat (); ods.CacheGlobalData (nFormat, hData); int nOldSel = m_nSel; DROPEFFECT de = ods.DoDragDrop (DROPEFFECT_COPY | DROPEFFECT_MOVE); if (de == DROPEFFECT_MOVE) { pDoc->RemoveWidget (nWidgetIndex); int nCount = pDoc->GetWidgetCount (); if (nOldSel == m_nSel || nCount == 0) m_nSel = -1; else if (m_nSel >= nCount) m_nSel = nCount - 1; pDoc->UpdateAllViews (NULL); } } } void CWidgetView::OnEditCut() { if (m_nSel != -1) { OnEditCopy (); OnEditDelete (); } } void CWidgetView::OnEditCopy() { if (m_nSel != -1) { // // Copy data describing the currently selected widget to a // global memory block. // HANDLE hData = ::GlobalAlloc (GMEM_MOVEABLE, sizeof (WIDGETINFO)); WIDGETINFO* pWidgetInfo = (WIDGETINFO*) ::GlobalLock (hData); CWidgetDoc* pDoc = GetDocument (); CWidget* pWidget = pDoc->GetWidget (m_nSel); ASSERT (pWidget != NULL); CRect rect = pWidget->GetRect (); pWidgetInfo->x = rect.left; pWidgetInfo->y = rect.top; pWidgetInfo->color = pWidget->GetColor (); ::GlobalUnlock (hData); // // Place the widget on the clipboard. // COleDataSource* pods = new COleDataSource; UINT nFormat = ((CWidgetApp*) AfxGetApp ())->GetClipboardFormat (); pods->CacheGlobalData (nFormat, hData); pods->SetClipboard (); } } void CWidgetView::OnEditPaste() { // // Create a COleDataObject and attach it to the clipboard. // COleDataObject odo; odo.AttachClipboard (); // // Retrieve the HGLOBAL from the clipboard and create a widget. // UINT nFormat = ((CWidgetApp*) AfxGetApp ())->GetClipboardFormat (); HGLOBAL hData = odo.GetGlobalData (nFormat); if (hData != NULL) { WIDGETINFO* pWidgetInfo = (WIDGETINFO*) ::GlobalLock (hData); int x = pWidgetInfo->x; int y = pWidgetInfo->y; COLORREF color = pWidgetInfo->color; ::GlobalUnlock (hData); ::GlobalFree (hData); CWidgetDoc* pDoc = GetDocument (); m_nSel = pDoc->AddWidget (x, y, color); pDoc->UpdateAllViews (NULL); } } void CWidgetView::OnEditDelete() { if (m_nSel != -1) { CWidgetDoc* pDoc = GetDocument (); pDoc->RemoveWidget (m_nSel); m_nSel = -1; pDoc->UpdateAllViews (NULL); } } void CWidgetView::OnUpdateEditCut(CCmdUI* pCmdUI) { pCmdUI->Enable (m_nSel != -1); } void CWidgetView::OnUpdateEditCopy(CCmdUI* pCmdUI) { pCmdUI->Enable (m_nSel != -1); } void CWidgetView::OnUpdateEditPaste(CCmdUI* pCmdUI) { UINT nFormat = ((CWidgetApp*) AfxGetApp ())->GetClipboardFormat (); pCmdUI->Enable (::IsClipboardFormatAvailable (nFormat)); } void CWidgetView::OnUpdateEditDelete(CCmdUI* pCmdUI) { pCmdUI->Enable (m_nSel != -1); } void CWidgetView::OnKillFocus(CWnd* pNewWnd) { CScrollView::OnKillFocus(pNewWnd); InvalidateRect (NULL, FALSE); } void CWidgetView::OnSetFocus(CWnd* pOldWnd) { CScrollView::OnSetFocus(pOldWnd); InvalidateRect (NULL, FALSE); } |
WidgetObj.h
#if !defined( AFX_WIDGETOBJ_H__02909A57_3F5C_11D2_AC89_006008A8274D__INCLUDED_) #define AFX_WIDGETOBJ_H__02909A57_3F5C_11D2_AC89_006008A8274D__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 // WidgetObj.h : header file // /////////////////////////////////////////////////////////////////////////// // CWidget command target class CWidget : public CObject { DECLARE_SERIAL(CWidget) // Attributes public: // Operations public: CWidget(); CWidget (int x, int y, COLORREF color); virtual ~CWidget(); void DrawSelected (CDC* pDC); BOOL PtInWidget (POINT point); virtual void DrawDragImage (CDC* pDC, POINT point); virtual void Draw (CDC* pDC); COLORREF GetColor (); CRect GetRect (); // Overrides // ClassWizard generated virtual function overrides //AFX_VIRTUAL virtual void Serialize (CArchive& ar); // Implementation protected: COLORREF m_color; CRect m_rect; }; /////////////////////////////////////////////////////////////////////////// // // Microsoft Visual C++ will insert additional declarations // immediately before the previous line. #endif // !defined( // AFX_WIDGETOBJ_H__02909A57_3F5C_11D2_AC89_006008A8274D__INCLUDED_) |
WidgetObj.cpp
// WidgetObj.cpp : implementation file // #include "stdafx.h" #include "Widget.h" #include "WidgetObj.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif /////////////////////////////////////////////////////////////////////////// // CWidget IMPLEMENT_SERIAL(CWidget, CObject, 1) CWidget::CWidget() { m_rect = CRect (0, 0, 90, 90); m_color = RGB (255, 0, 0); } CWidget::CWidget (int x, int y, COLORREF color) { m_rect = CRect (x, y, x + 90, y + 90); m_color = color; } CWidget::~CWidget() { } /////////////////////////////////////////////////////////////////////////// // CWidget message handlers CRect CWidget::GetRect() { return m_rect; } COLORREF CWidget::GetColor() { return m_color; } void CWidget::Serialize (CArchive& ar) { CObject::Serialize (ar); if (ar.IsStoring ()) ar << m_rect << m_color; else ar >> m_rect >> m_color; } void CWidget::Draw(CDC *pDC) { CBrush brush (m_color); 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.Width () / 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 CWidget::DrawSelected(CDC *pDC) { CBrush brush (RGB (0, 255, 0)); 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.Width () / 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 CWidget::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 CWidget::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)); } |
Widgets are represented by objects of the CWidget class, whose source code is found in WidgetObj.h and WidgetObj.cpp. To derive CWidget, I used ClassWizard to derive from CCmdTarget and then manually edited the source code to change the base class to CObject. I also changed the DYNCREATE macros inserted by ClassWizard into SERIAL macros and overrode CObject::Serialize to make CWidget a serializable class. These tweaks reduced the document's Serialize function to one simple statement:
m_arrWidgets.Serialize (ar); |
m_arrWidgets is the CWidgetDoc member variable that stores CWidget pointers. A CWidget object is created when a command is selected from the Insert menu, when a widget is pasted from the clipboard, and when a widget is dropped over the Widget window.
The CWidget class has a pair of member functions named Draw and DrawSelected that draw a widget to an output device. Draw draws the widget in the unselected state; DrawSelected draws it in the selected state. The view's OnDraw code is a simple loop that retrieves CWidget pointers from the document one by one and asks each widget to draw itself. If the view has the input focus and a widget is currently selected (that is, if CWidgetView::m_nSel != -1), the selected widget is drawn again after all the other widgets are drawn:
for (int i=0; i<nCount; i++) pDoc->GetWidget (i)->Draw (pDC); if (m_nSel != -1 && CWnd::GetFocus () == this) pDoc->GetWidget (m_nSel)->DrawSelected (pDC); |
Drawing the selected widget last ensures that it is always visible on top of the others.
Another CWidget drawing function, DrawDragImage, is used for drag imaging. As you drag a widget across the screen, notice the triangular outline that follows the cursor. That's drag imaging. The operating system shell uses drag images for a similar effect when file system objects are dragged. Because the drop source is responsible for displaying the cursor during a drag-and-drop operation, programmers often assume that the drop source draws drag images by making them part of the cursor. That's generally not true. What really happens is that the drop target (not the drop source) draws the drag image in OnDragOver. For this to work, the drop target has to know what kind of payload the cursor is carrying so that it can draw an outline on the screen.
Widget handles drag imaging by creating a temporary widget object in OnDragEnter, caching the pointer in CWidgetView::m_pTempWidget, and calling the object's DrawDragImage function each time OnDragOver is called. Actually, OnDragOver calls DrawDragImage twice: once to erase the old drag image and once to draw a new one. DrawDragImage does its drawing in the R2_NOT drawing mode, so drawing a drag image on top of an old one effectively erases the old drag image. The position of the previous drag image is stored in CWidgetView's m_pointLastImage data member. The temporary widget is deleted when OnDragLeave or OnDrop is called. This example demonstrates why overriding OnDragLeave is sometimes useful. In this case, OnDragEnter allocates a resource that must be freed even if a drop doesn't occur.
You can see drop target scrolling in action by dragging a widget and pausing within a few pixels of the view's lower or right border. After a brief delay, the view will begin scrolling and will continue scrolling until a drop occurs or the cursor moves away from the border. Drop target scrolling enables you to drop a widget in any part of the view without taking your finger off the mouse button to click a scroll bar. Again, drop target scrolling comes absolutely for free when the drop target is a CScrollView.
The AfxOleInit Function
When I used AppWizard to create the Widget project, I selected none of the OLE options in Step 3. When AppWizard is run this way, the generated source code doesn't include a call to the all-important AfxOleInit function, which initializes the OLE libraries. This function must be called before an MFC application touches COM or OLE in any way. Therefore, I added a call to AfxOleInit to the beginning of the application class's InitInstance function. This meant that I also had to add the statement
#include <afxole.h> |
to Stdafx.h. Otherwise, the call to AfxOleInit wouldn't have compiled.
I mention this because if you write an application that uses COM or OLE but you don't select one of the OLE options in AppWizard, you must add the AfxOleInit call and the statement that #includes Afxole.h manually. If you don't, your application will compile just fine, but calls to functions such as COleDataSource::DoDragDrop will fail. I once lost half a day of work wondering why my clipboard code wasn't working when, by all appearances, I had done everything right. Then I realized that I had forgotten to include these crucial statements in my source code. If you write an application and find that calls to DoDragDrop or other OLE functions mysteriously fail, make sure that AfxOleInit is called when the application starts up. You'll save yourself a lot of grief.