Programming Windows with MFC, Second Edition
Let's close out this chapter by writing an application that uses owner-draw menus and context menus. Colors is a souped-up version of Shapes that features an owner-draw Color menu and a context menu from which the user can select both shapes and colors. The items in the context menu are functional duplicates of the items in the Shape and Color menus and even share command and update handlers. The context menu appears when the user clicks the shape in the middle of the window with the right mouse button, as seen in Figure 4-14.
Figure 4-14. The Colors window.
Colors' source code appears in Figure 4-15. To generate the source code, I used AppWizard to create a new project named Colors and then proceeded as if I were writing Shapes all over again by implementing OnPaint, adding the Shape menu, writing command and update handlers, and so on. I then added the Color menu. Even though the menu items are assigned text strings such as "&Red" and "&Blue," those strings are never seen because the menu is owner-draw. The code that converts the items in the menu into owner-draw items is found in InitInstance:
CMenu* pMenu = pFrame->GetMenu (); ASSERT (pMenu != NULL); for (int i=0; i<5; i++) pMenu->ModifyMenu (ID_COLOR_RED + i, MF_OWNERDRAW, ID_COLOR_RED + i); |
The first statement initializes pMenu with a pointer to a CMenu object representing the main menu. ModifyMenu is then called five times in succession to tag the items in the Color menu with the flag MF_OWNERDRAW.
Figure 4-15. The Colors program.
Colors.h
// Colors.h : main header file for the COLORS application // #if !defined(AFX_COLORS_H__1B036BE8_5C6F_11D2_8E53_006008A82731__INCLUDED_) #define AFX_COLORS_H__1B036BE8_5C6F_11D2_8E53_006008A82731__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 /////////////////////////////////////////////////////////////////////////// // CColorsApp: // See Colors.cpp for the implementation of this class // class CColorsApp : public CWinApp { public: CColorsApp(); // Overrides // ClassWizard generated virtual function overrides //AFX_VIRTUAL // Implementation public: //AFX_MSG DECLARE_MESSAGE_MAP() }; |
/////////////////////////////////////////////////////////////////////////// // // Microsoft Visual C++ will insert additional declarations immediately // before the previous line. #endif // !defined(AFX_COLORS_H__1B036BE8_5C6F_11D2_8E53_006008A82731__INCLUDED_) |
Colors.cpp
// Colors.cpp : Defines the class behaviors for the application. // #include "stdafx.h" #include "Colors.h" #include "MainFrm.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif /////////////////////////////////////////////////////////////////////////// // CColorsApp BEGIN_MESSAGE_MAP(CColorsApp, CWinApp) //AFX_MSG_MAP END_MESSAGE_MAP() /////////////////////////////////////////////////////////////////////////// // CColorsApp construction CColorsApp::CColorsApp() { } /////////////////////////////////////////////////////////////////////////// // The one and only CColorsApp object CColorsApp theApp; /////////////////////////////////////////////////////////////////////////// // CColorsApp initialization BOOL CColorsApp::InitInstance() { // Standard initialization // Change the registry key under which our settings are stored. SetRegistryKey(_T("Local AppWizard-Generated Applications")); CMainFrame* pFrame = new CMainFrame; m_pMainWnd = pFrame; // create and load the frame with its resources pFrame->LoadFrame(IDR_MAINFRAME, WS_OVERLAPPEDWINDOW ¦ FWS_ADDTOTITLE, NULL, NULL); pFrame->ShowWindow(SW_SHOW); pFrame->UpdateWindow(); // // Convert the items in the Color menu to owner-draw. // CMenu* pMenu = pFrame->GetMenu (); ASSERT (pMenu != NULL); for (int i=0; i<5; i++) pMenu->ModifyMenu (ID_COLOR_RED + i, MF_OWNERDRAW, ID_COLOR_RED + i); return TRUE; } /////////////////////////////////////////////////////////////////////////// // CColorsApp message handlers /////////////////////////////////////////////////////////////////////////// // 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 CColorsApp::OnAppAbout() { CAboutDlg aboutDlg; aboutDlg.DoModal(); } /////////////////////////////////////////////////////////////////////////// // CColorsApp message handlers |
MainFrm.h
// MainFrm.h : interface of the CMainFrame class // /////////////////////////////////////////////////////////////////////////// #if !defined(AFX_MAINFRM_H__1B036BEC_5C6F_11D2_8E53_006008A82731__INCLUDED_) #define AFX_MAINFRM_H__1B036BEC_5C6F_11D2_8E53_006008A82731__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 #include "ChildView.h" class CMainFrame : public CFrameWnd { public: CMainFrame(); protected: DECLARE_DYNAMIC(CMainFrame) // Attributes public: // Operations public: // Overrides // ClassWizard generated virtual function overrides //AFX_VIRTUAL // Implementation public: virtual ~CMainFrame(); #ifdef _DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif CChildView m_wndView; // Generated message map functions protected: //AFX_MSG afx_msg void OnMeasureItem (int nIDCtl, LPMEASUREITEMSTRUCT lpmis); afx_msg void OnDrawItem (int nIDCtl, LPDRAWITEMSTRUCT lpdis); DECLARE_MESSAGE_MAP() }; /////////////////////////////////////////////////////////////////////////// // // Microsoft Visual C++ will insert additional declarations immediately // before the previous line. #endif // !defined(AFX_MAINFRM_H__1B036BEC_5C6F_11D2_8E53_006008A82731__INCLUDED_) |
MainFrm.cpp
// MainFrm.cpp : implementation of the CMainFrame class // #include "stdafx.h" #include "Colors.h" #include "MainFrm.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif /////////////////////////////////////////////////////////////////////////// // CMainFrame IMPLEMENT_DYNAMIC(CMainFrame, CFrameWnd) BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) //AFX_MSG_MAP ON_WM_MEASUREITEM () ON_WM_DRAWITEM () END_MESSAGE_MAP() /////////////////////////////////////////////////////////////////////////// // CMainFrame construction/destruction CMainFrame::CMainFrame() { } CMainFrame::~CMainFrame() { } BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) { if( !CFrameWnd::PreCreateWindow(cs) ) return FALSE; cs.dwExStyle &= ~WS_EX_CLIENTEDGE; cs.lpszClass = AfxRegisterWndClass(0); return TRUE; } /////////////////////////////////////////////////////////////////////////// // CMainFrame diagnostics #ifdef _DEBUG void CMainFrame::AssertValid() const { CFrameWnd::AssertValid(); } void CMainFrame::Dump(CDumpContext& dc) const { CFrameWnd::Dump(dc); } #endif //_DEBUG /////////////////////////////////////////////////////////////////////////// // CMainFrame message handlers void CMainFrame::OnSetFocus(CWnd* pOldWnd) { // forward focus to the view window m_wndView.SetFocus(); } BOOL CMainFrame::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) { // let the view have first crack at the command if (m_wndView.OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) return TRUE; // otherwise, do default handling return CFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo); } int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CFrameWnd::OnCreate(lpCreateStruct) == -1) return -1; if (!m_wndView.Create(NULL, NULL, AFX_WS_DEFAULT_VIEW, CRect(0, 0, 0, 0), this, AFX_IDW_PANE_FIRST, NULL)) { TRACE0("Failed to create view window\n"); return -1; } return 0; } void CMainFrame::OnMeasureItem (int nIDCtl, LPMEASUREITEMSTRUCT lpmis) { lpmis->itemWidth = ::GetSystemMetrics (SM_CYMENU) * 4; lpmis->itemHeight = ::GetSystemMetrics (SM_CYMENU); } void CMainFrame::OnDrawItem (int nIDCtl, LPDRAWITEMSTRUCT lpdis) { BITMAP bm; CBitmap bitmap; bitmap.LoadOEMBitmap (OBM_CHECK); bitmap.GetObject (sizeof (bm), &bm); CDC dc; dc.Attach (lpdis->hDC); CBrush* pBrush = new CBrush (::GetSysColor ((lpdis->itemState & ODS_SELECTED) ? COLOR_HIGHLIGHT : COLOR_MENU)); dc.FrameRect (&(lpdis->rcItem), pBrush); delete pBrush; if (lpdis->itemState & ODS_CHECKED) { CDC dcMem; dcMem.CreateCompatibleDC (&dc); CBitmap* pOldBitmap = dcMem.SelectObject (&bitmap); dc.BitBlt (lpdis->rcItem.left + 4, lpdis->rcItem.top + (((lpdis->rcItem.bottom - lpdis->rcItem.top) - bm.bmHeight) / 2), bm.bmWidth, bm.bmHeight, &dcMem, 0, 0, SRCCOPY); dcMem.SelectObject (pOldBitmap); } UINT itemID = lpdis->itemID & 0xFFFF; // Fix for Win95 bug. pBrush = new CBrush (m_wndView.m_clrColors[itemID - ID_COLOR_RED]); CRect rect = lpdis->rcItem; rect.DeflateRect (6, 4); rect.left += bm.bmWidth; dc.FillRect (rect, pBrush); delete pBrush; dc.Detach (); } |
ChildView.h
// ChildView.h : interface of the CChildView class // /////////////////////////////////////////////////////////////////////////// #if !defined(AFX_CHILDVIEW_H__1B036BEE_5C6F_11D2_8E53_006008A82731__INCLUDED_) #define AFX_CHILDVIEW_H__1B036BEE_5C6F_11D2_8E53_006008A82731__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 /////////////////////////////////////////////////////////////////////////// // CChildView window class CChildView : public CWnd { // Construction public: CChildView(); // Attributes public: static const COLORREF m_clrColors[5]; // Operations public: // Overrides // ClassWizard generated virtual function overrides //AFX_VIRTUAL // Implementation public: virtual ~CChildView(); // Generated message map functions protected: int m_nColor; int m_nShape; //AFX_MSG afx_msg void OnColor (UINT nID); afx_msg void OnUpdateColor (CCmdUI* pCmdUI); DECLARE_MESSAGE_MAP() }; /////////////////////////////////////////////////////////////////////////// // // Microsoft Visual C++ will insert additional declarations immediately // before the previous line. #endif //!defined(AFX_CHILDVIEW_H__1B036BEE_5C6F_11D2_8E53_006008A82731__INCLUDED_) |
ChildView.cpp
// ChildView.cpp : implementation of the CChildView class // #include "stdafx.h" #include "Colors.h" #include "ChildView.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif /////////////////////////////////////////////////////////////////////////// // CChildView CChildView::CChildView() { m_nShape = 1; // Triangle m_nColor = 0; // Red } CChildView::~CChildView() { } BEGIN_MESSAGE_MAP(CChildView,CWnd ) //AFX_MSG_MAP ON_COMMAND_RANGE (ID_COLOR_RED, ID_COLOR_BLUE, OnColor) ON_UPDATE_COMMAND_UI_RANGE (ID_COLOR_RED, ID_COLOR_BLUE, OnUpdateColor) END_MESSAGE_MAP() const COLORREF CChildView::m_clrColors[5] = { RGB ( 255, 0, 0), // Red RGB ( 255, 255, 0), // Yellow RGB ( 0, 255, 0), // Green RGB ( 0, 255, 255), // Cyan RGB ( 0, 0, 255) // Blue }; /////////////////////////////////////////////////////////////////////////// // CChildView message handlers BOOL CChildView::PreCreateWindow(CREATESTRUCT& cs) { if (!CWnd::PreCreateWindow(cs)) return FALSE; cs.dwExStyle ¦= WS_EX_CLIENTEDGE; cs.style &= ~WS_BORDER; cs.lpszClass = AfxRegisterWndClass(CS_HREDRAW¦CS_VREDRAW¦CS_DBLCLKS, ::LoadCursor(NULL, IDC_ARROW), HBRUSH(COLOR_WINDOW+1), NULL); return TRUE; } void CChildView::OnPaint() { CPoint points[3]; CPaintDC dc(this); CRect rcClient; GetClientRect (&rcClient); int cx = rcClient.Width () / 2; int cy = rcClient.Height () / 2; CRect rcShape (cx - 45, cy - 45, cx + 45, cy + 45); CBrush brush (m_clrColors[m_nColor]); CBrush* pOldBrush = dc.SelectObject (&brush); switch (m_nShape) { case 0: // Circle dc.Ellipse (rcShape); break; case 1: // Triangle points[0].x = cx - 45; points[0].y = cy + 45; points[1].x = cx; points[1].y = cy - 45; points[2].x = cx + 45; points[2].y = cy + 45; dc.Polygon (points, 3); break; case 2: // Square dc.Rectangle (rcShape); break; } dc.SelectObject (pOldBrush); } void CChildView::OnShapeCircle() { m_nShape = 0; Invalidate (); } void CChildView::OnShapeTriangle() { m_nShape = 1; Invalidate (); } void CChildView::OnShapeSquare() { m_nShape = 2; Invalidate (); } void CChildView::OnUpdateShapeCircle(CCmdUI* pCmdUI) { pCmdUI->SetCheck (m_nShape == 0); } void CChildView::OnUpdateShapeTriangle(CCmdUI* pCmdUI) { pCmdUI->SetCheck (m_nShape == 1); } void CChildView::OnUpdateShapeSquare(CCmdUI* pCmdUI) { pCmdUI->SetCheck (m_nShape == 2); } void CChildView::OnColor (UINT nID) { m_nColor = nID - ID_COLOR_RED; Invalidate (); } void CChildView::OnUpdateColor (CCmdUI* pCmdUI) { pCmdUI->SetCheck ((int) pCmdUI->m_nID - ID_COLOR_RED == m_nColor); } void CChildView::OnContextMenu(CWnd* pWnd, CPoint point) { CRect rcClient; GetClientRect (&rcClient); int cx = rcClient.Width () / 2; int cy = rcClient.Height () / 2; CRect rcShape (cx - 45, cy - 45, cx + 45, cy + 45); CPoint pos = point; ScreenToClient (&pos); CPoint points[3]; BOOL bShapeClicked = FALSE; int dx, dy; // // Hit test the shape. // switch (m_nShape) { case 0: // Circle dx = pos.x - cx; dy = pos.y - cy; if ((dx * dx) + (dy * dy) <= (45 * 45)) bShapeClicked = TRUE; break; case 1: // Triangle if (rcShape.PtInRect (pos)) { dx = min (pos.x - rcShape.left, rcShape.right - pos.x); if ((rcShape.bottom - pos.y) < (2 * dx)) bShapeClicked = TRUE; } break; case 2: // Square if (rcShape.PtInRect (pos)) bShapeClicked = TRUE; break; } // // Display a context menu if the shape was clicked. // if (bShapeClicked) { CMenu menu; menu.LoadMenu (IDR_CONTEXTMENU); CMenu* pContextMenu = menu.GetSubMenu (0); for (int i=0; i<5; i++) pContextMenu->ModifyMenu (ID_COLOR_RED + i, MF_BYCOMMAND ¦ MF_OWNERDRAW, ID_COLOR_RED + i); pContextMenu->TrackPopupMenu (TPM_LEFTALIGN ¦ TPM_LEFTBUTTON ¦ TPM_RIGHTBUTTON, point.x, point.y, AfxGetMainWnd ()); return; } // // Call the base class if the shape was not clicked. // CWnd::OnContextMenu (pWnd, point); } |
Resource.h
// // Microsoft Developer Studio generated include file. // Used by Colors.rc // #define IDD_ABOUTBOX 100 #define IDR_MAINFRAME 128 #define IDR_COLORSTYPE 129 #define IDR_CONTEXTMENU 130 #define ID_SHAPE_CIRCLE 32771 #define ID_SHAPE_TRIANGLE 32772 #define ID_SHAPE_SQUARE 32773 #define ID_COLOR_RED 32774 #define ID_COLOR_YELLOW 32775 #define ID_COLOR_GREEN 32776 #define ID_COLOR_CYAN 32777 #define ID_COLOR_BLUE 32778 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 131 #define _APS_NEXT_COMMAND_VALUE 32779 #define _APS_NEXT_CONTROL_VALUE 1000 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif |
Colors.rc
//Microsoft Developer Studio generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS /////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h" /////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS /////////////////////////////////////////////////////////////////////////// // English (U.S.) resources #if !defined(AFX_RESOURCE_DLL) ¦¦ defined(AFX_TARG_ENU) #ifdef _WIN32 LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #pragma code_page(1252) #endif //_WIN32 #ifdef APSTUDIO_INVOKED /////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END 2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END 3 TEXTINCLUDE DISCARDABLE BEGIN "#define _AFX_NO_SPLITTER_RESOURCES\r\n" "#define _AFX_NO_OLE_RESOURCES\r\n" "#define _AFX_NO_TRACKER_RESOURCES\r\n" "#define _AFX_NO_PROPERTY_RESOURCES\r\n" "\r\n" "#if !defined(AFX_RESOURCE_DLL) ¦¦ defined(AFX_TARG_ENU)\r\n" "#ifdef _WIN32\r\n" "LANGUAGE 9, 1\r\n" "#pragma code_page(1252)\r\n" "#endif //_WIN32\r\n" "#include ""res\\Colors.rc2" " // non-Microsoft Visual C++ edited resources\r\n" "#include ""afxres.rc"" // Standard components\r\n" "#endif\r\n" "\0" END #endif // APSTUDIO_INVOKED /////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDR_MAINFRAME ICON DISCARDABLE "res\\Colors.ico" /////////////////////////////////////////////////////////////////////////// // // Menu // IDR_MAINFRAME MENU PRELOAD DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "E&xit", ID_APP_EXIT END POPUP "&Shape" BEGIN MENUITEM "&Circle\tF7", ID_SHAPE_CIRCLE MENUITEM "&Triangle\tF8", ID_SHAPE_TRIANGLE MENUITEM "&Square\tF9", ID_SHAPE_SQUARE END POPUP "&Color" BEGIN MENUITEM "&Red", ID_COLOR_RED MENUITEM "&Yellow", ID_COLOR_YELLOW MENUITEM "&Green", ID_COLOR_GREEN MENUITEM "&Cyan", ID_COLOR_CYAN MENUITEM "&Blue", ID_COLOR_BLUE END END IDR_CONTEXTMENU MENU DISCARDABLE BEGIN POPUP "Top" BEGIN MENUITEM "&Circle\tF7", ID_SHAPE_CIRCLE MENUITEM "&Triangle\tF8", ID_SHAPE_TRIANGLE MENUITEM "&Square\tF9", ID_SHAPE_SQUARE MENUITEM SEPARATOR MENUITEM "&Red", ID_COLOR_RED MENUITEM "&Yellow", ID_COLOR_YELLOW MENUITEM "&Green", ID_COLOR_GREEN MENUITEM "&Cyan", ID_COLOR_CYAN MENUITEM "&Blue", ID_COLOR_BLUE END END /////////////////////////////////////////////////////////////////////////// // // Accelerator // IDR_MAINFRAME ACCELERATORS PRELOAD MOVEABLE PURE BEGIN "B", ID_COLOR_BLUE, VIRTKEY, CONTROL, NOINVERT "C", ID_COLOR_CYAN, VIRTKEY, CONTROL, NOINVERT "G", ID_COLOR_GREEN, VIRTKEY, CONTROL, NOINVERT "R", ID_COLOR_RED, VIRTKEY, CONTROL, NOINVERT VK_F7, ID_SHAPE_CIRCLE, VIRTKEY, NOINVERT VK_F8, ID_SHAPE_TRIANGLE, VIRTKEY, NOINVERT VK_F9, ID_SHAPE_SQUARE, VIRTKEY, NOINVERT "Y", ID_COLOR_YELLOW, VIRTKEY, CONTROL, NOINVERT END /////////////////////////////////////////////////////////////////////////// // // Dialog // IDD_ABOUTBOX DIALOG DISCARDABLE 0, 0, 235, 55 STYLE DS_MODALFRAME ¦ WS_POPUP ¦ WS_CAPTION ¦ WS_SYSMENU CAPTION "About Colors" FONT 8, "MS Sans Serif" BEGIN ICON IDR_MAINFRAME,IDC_STATIC,11,17,20,20 LTEXT "Colors Version 1.0",IDC_STATIC,40,10,119,8,SS_NOPREFIX LTEXT "Copyright (C) 1998",IDC_STATIC,40,25,119,8 DEFPUSHBUTTON "OK",IDOK,178,7,50,14,WS_GROUP END #ifndef _MAC /////////////////////////////////////////////////////////////////////////// // // Version // VS_VERSION_INFO VERSIONINFO FILEVERSION 1,0,0,1 PRODUCTVERSION 1,0,0,1 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L #else FILEFLAGS 0x0L #endif FILEOS 0x4L FILETYPE 0x1L FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904B0" BEGIN VALUE "CompanyName", "\0" VALUE "FileDescription", "Colors MFC Application\0" VALUE "FileVersion", "1, 0, 0, 1\0" VALUE "InternalName", "Colors\0" VALUE "LegalCopyright", "Copyright (C) 1998\0" VALUE "LegalTrademarks", "\0" VALUE "OriginalFilename", "Colors.EXE\0" VALUE "ProductName", "Colors Application\0" VALUE "ProductVersion", "1, 0, 0, 1\0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1200 END END #endif // !_MAC /////////////////////////////////////////////////////////////////////////// // // DESIGNINFO // #ifdef APSTUDIO_INVOKED GUIDELINES DESIGNINFO DISCARDABLE BEGIN IDD_ABOUTBOX, DIALOG BEGIN LEFTMARGIN, 7 RIGHTMARGIN, 228 TOPMARGIN, 7 BOTTOMMARGIN, 48 END END #endif // APSTUDIO_INVOKED /////////////////////////////////////////////////////////////////////////// // // String Table // STRINGTABLE PRELOAD DISCARDABLE BEGIN IDR_MAINFRAME "Colors" END STRINGTABLE PRELOAD DISCARDABLE BEGIN AFX_IDS_APP_TITLE "Colors" AFX_IDS_IDLEMESSAGE "Ready" END STRINGTABLE DISCARDABLE BEGIN ID_INDICATOR_EXT "EXT" ID_INDICATOR_CAPS "CAP" ID_INDICATOR_NUM "NUM" ID_INDICATOR_SCRL "SCRL" ID_INDICATOR_OVR "OVR" ID_INDICATOR_REC "REC" END STRINGTABLE DISCARDABLE BEGIN ID_APP_ABOUT "Display program information, version number and copyright\nAbout" ID_APP_EXIT "Quit the application; prompts to save documents\nExit" END STRINGTABLE DISCARDABLE BEGIN ID_NEXT_PANE "Switch to the next window pane\nNext Pane" ID_PREV_PANE "Switch back to the previous window pane\nPrevious Pane" END STRINGTABLE DISCARDABLE BEGIN ID_WINDOW_SPLIT "Split the active window into panes\nSplit" END STRINGTABLE DISCARDABLE BEGIN ID_EDIT_CLEAR "Erase the selection\nErase" ID_EDIT_CLEAR_ALL "Erase everything\nErase All" ID_EDIT_COPY "Copy the selection and put it on the Clipboard\nCopy" ID_EDIT_CUT "Cut the selection and put it on the Clipboard\nCut" ID_EDIT_FIND "Find the specified text\nFind" ID_EDIT_PASTE "Insert Clipboard contents\nPaste" ID_EDIT_REPEAT "Repeat the last action\nRepeat" ID_EDIT_REPLACE "Replace specific text with different text\nReplace" ID_EDIT_SELECT_ALL "Select the entire document\nSelect All" ID_EDIT_UNDO "Undo the last action\nUndo" ID_EDIT_REDO "Redo the previously undone action\nRedo" END STRINGTABLE DISCARDABLE BEGIN AFX_IDS_SCSIZE "Change the window size" AFX_IDS_SCMOVE "Change the window position" AFX_IDS_SCMINIMIZE "Reduce the window to an icon" AFX_IDS_SCMAXIMIZE "Enlarge the window to full size" AFX_IDS_SCNEXTWINDOW "Switch to the next document window" AFX_IDS_SCPREVWINDOW "Switch to the previous document window" AFX_IDS_SCCLOSE "Close the active window and prompts to save the documents" END STRINGTABLE DISCARDABLE BEGIN AFX_IDS_SCRESTORE "Restore the window to normal size" AFX_IDS_SCTASKLIST "Activate Task List" END #endif // English (U.S.) resources /////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED /////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // #define _AFX_NO_SPLITTER_RESOURCES #define _AFX_NO_OLE_RESOURCES #define _AFX_NO_TRACKER_RESOURCES #define _AFX_NO_PROPERTY_RESOURCES #if !defined(AFX_RESOURCE_DLL) ¦¦ defined(AFX_TARG_ENU) #ifdef _WIN32 LANGUAGE 9, 1 #pragma code_page(1252) #endif //_WIN32 #include "res\Colors.rc2" // non-Microsoft Visual C++ edited resources #include "afxres.rc" // Standard components #endif /////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED |
Because the frame window is the menu's owner, the frame window receives the WM_MEASUREITEM and WM_DRAWITEM messages that the owner-draw items generate. Therefore, the message handlers appear in the frame window class. CMainFrame::OnMeasureItem contains just two statements: one specifying the height of each menu item (the SM_CYMENU value returned by ::GetSystemMetrics), the other specifying the width (SM_CYMENU*4). CMainFrame::OnDrawItem is a bit more complicated because it's responsible for doing the actual drawing. After doing some preliminary work involving a CBitmap object that we'll discuss in a moment, OnDrawItem constructs an empty CDC object and attaches to it the device context handle provided in the DRAWITEMSTRUCT structure using CDC::Attach:
CDC dc; dc.Attach (lpdis->hDC); |
This converts dc into a valid device context object that wraps the Windows-provided device context. That device context should be returned to Windows in the same state in which it was received. Objects selected into the device context should be selected back out, and any changes made to the state of the device context (for example, to the background mode or the text color) should be undone before OnDrawItem ends.
Next, OnDrawItem creates a brush whose color is either COLOR_MENU or COLOR_HIGHLIGHT, depending on whether the ODS_SELECTED bit in the itemState field is set. Then it outlines the menu item with a rectangle by calling CDC::FrameRect with a pointer to the brush:
CBrush* pBrush = new CBrush (::GetSysColor ((lpdis->itemState & ODS_SELECTED) ? COLOR_HIGHLIGHT : COLOR_MENU)); dc.FrameRect (&(lpdis->rcItem), pBrush); delete pBrush; |
COLOR_MENU is the default menu background color; COLOR_HIGHLIGHT is the color of a menu's highlight bar. CDC::FrameRect uses the specified brush to draw a rectangle with lines 1 pixel wide. The code above draws a rectangle around the menu item in the background color if the item isn't selected or in the highlight color if it is. This is the rectangle you see when you pull down the Color menu and move the mouse up and down. Drawing the rectangle in the background color if the ODS_SELECTED bit is clear erases the selection rectangle when the highlight passes from one item to another.
OnDrawItem's next task is to draw a check mark next to the menu item if the ODS_CHECKED bit is set. Unfortunately, drawing check marks is a detail you have to take care of yourself when you use owner-draw menus. More unfortunate still, neither MFC nor the Windows API has a DrawCheckMark function that would make drawing a check mark easy. The alternative is to create a bitmap depicting the check mark and use CDC::BitBlt to "blit" the check mark to the screen. Blitting is discussed in detail in Chapter 15, but even without that background preparation, the OnDrawItem code that draws a check mark if ODS_CHECKED is set is relatively easy to understand:
CDC dcMem; dcMem.CreateCompatibleDC (&dc); CBitmap* pOldBitmap = dcMem.SelectObject (&bitmap); dc.BitBlt (lpdis->rcItem.left + 4, lpdis->rcItem.top + (((lpdis->rcItem.bottom - lpdis->rcItem.top) - bm.bmHeight) / 2), bm.bmWidth, bm.bmHeight, &dcMem, 0, 0, SRCCOPY); dcMem.SelectObject (pOldBitmap); |
dcMem represents a memory device context (DC)—a virtual display surface in memory that can be drawn to as if it were a screen or other output device. CreateCompatibleDC creates a memory DC. Windows doesn't let you blit bitmaps directly to a display surface, so instead you must select the bitmap into a memory DC and copy it to the screen DC. In this example, BitBlt copies the bitmap from the memory DC to a location near the left end of the rectangle described by lpdis->rcItem in the screen DC. When BitBlt returns, the bitmap is selected out of the memory DC in preparation for the memory DC to be destroyed when dcMem goes out of scope.
Where does the bitmap come from? The first four statements in OnDrawItem create an empty CBitmap object, initialize it with the bitmap that Windows uses to draw menu check marks, and copy information about the bitmap (including its width and height) to a BITMAP structure:
BITMAP bm; CBitmap bitmap; bitmap.LoadOEMBitmap (OBM_CHECK); bitmap.GetObject (sizeof (bm), &bm); |
OBM_CHECK is the bitmap ID; CBitmap::LoadOEMBitmap copies the bitmap to a CBitmap object. CBitmap::GetObject copies information about the bitmap to a BITMAP structure, and the width and height values stored in the structure's bmWidth and bmHeight fields are used in the call to BitBlt. bmWidth is used again toward the end of OnDrawItem to indent the left end of each color swatch by an amount that equals the width of the check mark. For OBM_CHECK to be recognized, the statement
#define OEMRESOURCE |
must appear before the statement that includes Afxwin.h. In Colors, you'll find the #define in StdAfx.h.
After the selection rectangle is drawn or erased and the check mark is drawn if the ODS_CHECKED bit is set, OnDrawItem draws the colored rectangle representing the menu item itself. To do so, it creates a solid brush, creates a CRect object from the rcItem structure passed in DRAWITEMSTRUCT, shrinks the rectangle a few pixels, and paints the rectangle using CDC::FillRect:
UINT itemID = lpdis->itemID & 0xFFFF; // Fix for Win95 bug. pBrush = new CBrush (m_wndView.m_clrColors[itemID - ID_COLOR_RED]); CRect rect = lpdis->rcItem; rect.DeflateRect (6, 4); rect.left += bm.bmWidth; dc.FillRect (rect, pBrush); delete pBrush; |
CDC::FillRect is yet another CDC rectangle function. It fills the interior of the rectangle with a specified brush rather than with the brush selected into the device context, and it doesn't outline the rectangle with the current pen. Using FillRect rather than Rectangle prevents us from having to select a pen and a brush into the device context and select them back out again when we're done. The color of the brush passed to FillRect is determined by subtracting ID_COLOR_RED from the menu item ID supplied in lpdis->itemID and using the result as an index into the view object's m_clrColors array.
Speaking of lpdis->itemID: Observe that the code fragment in the previous paragraph ANDs the item ID with 0xFFFF. This is done to work around a bug in Windows 95. If you assign an owner-draw menu item an ID equal to 0x8000 or higher, Windows 95 unwittingly sign-extends the value when passing it between the 16-bit and 32-bit halves of USER. The result? The command ID 0x8000 becomes 0xFFFF8000, 0x8001 becomes 0xFFFF8001, and so on, and OnDrawItem won't recognize its own command IDs unless it masks off the upper 16 bits. Using ID values lower than 0x8000 fixes this problem by eliminating the 1 in the upper bit, but it just so happens that when you allow Visual C++ to pick your command IDs, it uses values greater than 0x8000. Rather than manually change the IDs, I chose to strip the bits instead. This problem doesn't exist in Windows NT and is fixed in Windows 98.
OnDrawItem's final act is to detach dc from the device context handle obtained from DRAWITEMSTRUCT. This final step is important because it prevents dc's destructor from deleting the device context when OnDrawItem ends. Normally you want a device context to be deleted when a message handler returns, but because this device context was borrowed from Windows, only Windows should delete it. CDC::Detach disassociates a CDC object and its device context so that the object can safely go out of scope.
The Context Menu
Colors' context menu comes from the menu resource IDR_CONTEXTMENU. The menu resource is defined as follows in Colors.rc:
IDR_CONTEXTMENU MENU DISCARDABLE BEGIN POPUP "Top" BEGIN MENUITEM "&Circle\tF7", ID_SHAPE_CIRCLE MENUITEM "&Triangle\tF8", ID_SHAPE_TRIANGLE MENUITEM "&Square\tF9", ID_SHAPE_SQUARE MENUITEM SEPARATOR MENUITEM "&Red", ID_COLOR_RED MENUITEM "&Yellow", ID_COLOR_YELLOW MENUITEM "&Green", ID_COLOR_GREEN MENUITEM "&Cyan", ID_COLOR_CYAN MENUITEM "&Blue", ID_COLOR_BLUE END END |
I created it by inserting a new menu resource into the application with Visual C++'s Insert-Resource command. I added items using the menu editor.
When the right mouse button is clicked in the view, the context menu is loaded and displayed by CChildView::OnContextMenu. Before loading the menu, OnContextMenu hit-tests the shape in the window and passes the WM_CONTEXTMENU message to the base class if the click occurred outside the shape. If it determines that the click occurred over the circle, the triangle, or the square, OnContextMenu loads the menu and converts the items in it to owner-draw items before calling TrackPopupMenu:
if (bShapeClicked) { CMenu menu; menu.LoadMenu (IDR_CONTEXTMENU); CMenu* pContextMenu = menu.GetSubMenu (0); for (int i=0; i<5; i++) pContextMenu->ModifyMenu (ID_COLOR_RED + i, MF_BYCOMMAND ¦ MF_OWNERDRAW, ID_COLOR_RED + i); pContextMenu->TrackPopupMenu (TPM_LEFTALIGN ¦ TPM_LEFTBUTTON ¦ TPM_RIGHTBUTTON, point.x, point.y, AfxGetMainWnd ()); return; } |
The owner-draw conversion must be performed each time the menu is loaded because when menu goes out of scope, the menu is destroyed and the modifications made to it are lost.
The colors in the Color menu and the context menu are linked to the command handler OnColor and the update handler OnUpdateColor by the following entries in CChildView's message map:
ON_COMMAND_RANGE (ID_COLOR_RED, ID_COLOR_BLUE, OnColor) ON_UPDATE_COMMAND_UI_RANGE (ID_COLOR_RED, ID_COLOR_BLUE, OnUpdateColor) |
I added these entries to the source code manually because ClassWizard doesn't support either ON_COMMAND_RANGE or ON_UPDATE_COMMAND_UI_RANGE. ClassWizard's lack of support for these macros is one very important reason why MFC programmers shouldn't become too reliant on code-generating wizards. The wizards are useful, but they support only a subset of MFC's functionality. I could have used ClassWizard to write separate command and update handlers for every command, but hand-coding RANGE macros into the message map is more efficient because it reduces what would have been 10 separate command and update handlers to just 2. Note that entries added to a message map manually should be added outside the AFX_MSG_MAP comments generated by AppWizard. The portion of the message map that lies between these comments belongs to ClassWizard.
For these RANGE macros to work, the items in the Color menu must be assigned contiguous command IDs, with ID_COLOR_RED and ID_COLOR_BLUE bracketing the low and high ends of the range, respectively. To ensure that these conditions are met, you should either specify the command IDs explicitly when creating the menu items in the menu editor or edit them after the fact. You can specify a numeric command ID when creating or editing a menu item by appending "= value" to the command ID typed into the Menu Item Properties dialog box's ID combo box, as shown in Figure 4-16. Or you can edit Resource.h instead. I used the values 32774 through 32778 for ID_COLOR_RED through ID_COLOR_BLUE.
Figure 4-16. Assigning a numeric value to a menu item ID.
On Your Own
Here's an exercise you can try on your own. Go to ResourceView and edit the icon resource IDR_MAINFRAME. This resource, which was created by AppWizard, defines the application icon. The icon contains two images: a large (32 by 32) image and a small (16 by 16) image. You should edit both of them before you ship an application so that your application will have a unique icon. You can pick the one you want to edit by selecting Standard or Small from the icon editor's Device drop-down list. You can see the large icon in the operating system shell if you navigate to the folder containing Colors.exe and select Large Icons as the view type. If you have Small Icons, List, or Details selected instead, you'll see the small icon. The small icon also appears in the frame window's title bar, thanks to some code in CFrameWnd::LoadFrame that loads the icon resource and associates it with the window.