The MyWord Application

类别:VC语言 点击:0 评论:0 推荐:
Putting It All Together: The MyWord Application

The sample program shown in Figure 12-8 demonstrates many of the principles discussed in the preceding sections. MyWord is a miniature word processor built around a CRichEditView. MFC's CRichEditView class is like a CEditView on steroids; based on the rich text edit control supplied in the common controls library, it features superior text formatting capabilities and the ability to read and write rich text format (RTF) files with a simple function call. MyWord doesn't use all the features of a CRichEditView; in fact, it barely scratches the surface. (For a more in-depth look at CRichEditView, see the Wordpad sample program provided with MFC. The Wordpad files are the actual source code for the Wordpad applet that ships with Windows.) But MyWord packs a lot of punch for a program that's only a few hundred lines long, and it's a good starting point for writing CRichEditView-based applications of your own.

Figure 12-8. The MyWord window.

MyWord uses two toolbars and one status bar. The main toolbar includes buttons that serve as shortcuts for the New, Open, and Save items in the File menu and the Cut, Copy, Paste, and Undo items in the Edit menu. The other toolbar, which I'll refer to as the style bar, includes check push buttons for setting the character format (bold, italic, and underline), radio push buttons for setting the paragraph alignment (left aligned, centered, and right aligned), and combo boxes for selecting typefaces and font sizes. Both toolbars can be detached from the frame window, floated, and docked at other locations; and both can be resized while they're floating. Try it: Drag the main toolbar to the right side of the window, and dock it in a vertical position. Grab the style bar and release it in the center of the window so that it becomes a floating palette. Use the View menu to hide and display the toolbars and the status bar. You can also hide a toolbar by clicking the close button in the mini frame window it floats in when it's detached from the main frame window. To redisplay the toolbar, simply select the Toolbar or Style Bar command in the View menu.

The status bar at the bottom of MyWord's frame window displays help text for menu items and toolbar controls. It also includes Caps Lock and Num Lock indicators and a line number display that's continually updated as the caret moves through the document. The Caps Lock and Num Lock indicators were added using MFC's predefined ID_INDICATOR_CAPS and ID_INDICATOR_NUM IDs. The line number indicator is updated by an ON_UPDATE_COMMAND_UI handler that, when called, retrieves the current line number from the CRichEditView, formulates a text string containing the line number, and updates the status bar display with CCmdUI::SetText. The line number pane is sized to fit the dummy string "Line 00000," whose resource ID, ID_INDICATOR_LINE, is identical to the status bar pane's ID. The dummy string is never seen because the pane is updated with a real line number before the status bar appears on the screen.

I used AppWizard to begin MyWord. I checked the Docking Toolbar and Initial Status Bar options in AppWizard's Step 4 dialog box, and in Step 6, I selected CRichEditView as the base class for the view. I next derived a class named CStyleBar to represent the style bar, added a CStyleBar data member to the frame window class, and modified the frame window's OnCreate function to create the style bar. (I used ClassWizard to perform the class derivation, but because ClassWizard doesn't support CToolBar as a base class, I derived CStyleBar from CCmdTarget and then manually patched up the code to change the base class to CToolBar.) I used Visual C++'s Insert-Resource command to create the toolbar resource from which the style bar is created, and I added buttons in the toolbar editor. Finishing MyWord was a matter of writing the command handlers, update handlers, and ordinary class member functions that form the core of the application.

The source code for MyWord's frame window, document, view, and style bar classes is listed in Figure 12-9. Take a moment to look it over to see how the toolbars and status bar are handled. Then go to "The Main Toolbar" to read about pertinent parts of the source code in greater detail.

Figure 12-9. The MyWord application.


// MainFrm.h : interface of the CMainFrame class // /////////////////////////////////////////////////////////////////////////// #if !defined( AFX_MAINFRM_H__C85C9089_A154_11D2_8E53_006008A82731__INCLUDED_) #define AFX_MAINFRM_H__C85C9089_A154_11D2_8E53_006008A82731__INCLUDED_ #include "StyleBar.h" // Added by ClassView #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 class CMainFrame : public CFrameWnd { protected: // create from serialization only CMainFrame(); DECLARE_DYNCREATE(CMainFrame) // Attributes public: // Operations public: // Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CMainFrame) virtual BOOL PreCreateWindow(CREATESTRUCT& cs); //}}AFX_VIRTUAL // Implementation public: virtual ~CMainFrame(); #ifdef _DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif protected: // control bar embedded members CStyleBar m_wndStyleBar; CStatusBar m_wndStatusBar; CToolBar m_wndToolBar; // Generated message map functions protected: BOOL CreateToolBar (); BOOL CreateStyleBar (); BOOL CreateStatusBar (); //{{AFX_MSG(CMainFrame) afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); afx_msg void OnClose(); //}}AFX_MSG DECLARE_MESSAGE_MAP() }; /////////////////////////////////////////////////////////////////////////// //{{AFX_INSERT_LOCATION}} // Microsoft Visual C++ will insert additional declarations // immediately before the previous line. #endif // !defined(AFX_MAINFRM_H__C85C9089_A154_11D2_8E53_006008A82731__INCLUDED_)


// MainFrm.cpp : implementation of the CMainFrame class // #include "stdafx.h" #include "MyWord.h" #include "MainFrm.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif /////////////////////////////////////////////////////////////////////////// // CMainFrame IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd) BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) //{{AFX_MSG_MAP(CMainFrame) ON_WM_CREATE() ON_WM_CLOSE() //}}AFX_MSG_MAP ON_COMMAND_EX (IDW_STYLE_BAR, OnBarCheck) ON_UPDATE_COMMAND_UI (IDW_STYLE_BAR, OnUpdateControlBarMenu) END_MESSAGE_MAP() /////////////////////////////////////////////////////////////////////////// // CMainFrame construction/destruction CMainFrame::CMainFrame() { } CMainFrame::~CMainFrame() { } int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CFrameWnd::OnCreate(lpCreateStruct) == -1) return -1; // // Tell the frame window to permit docking. // EnableDocking (CBRS_ALIGN_ANY); // // Create the toolbar, style bar, and status bar. // if (!CreateToolBar () || !CreateStyleBar () || !CreateStatusBar ()) return -1; // // Load the saved bar state (if any). // LoadBarState (_T ("MainBarState")); return 0; } BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) { if( !CFrameWnd::PreCreateWindow(cs) ) return FALSE; 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::OnClose() { SaveBarState (_T ("MainBarState")); CFrameWnd::OnClose(); } BOOL CMainFrame::CreateToolBar() { if (!m_wndToolBar.Create (this) || !m_wndToolBar.LoadToolBar (IDR_MAINFRAME)) return FALSE; m_wndToolBar.SetBarStyle (m_wndToolBar.GetBarStyle () | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC); m_wndToolBar.SetWindowText (_T ("Main")); m_wndToolBar.EnableDocking (CBRS_ALIGN_ANY); DockControlBar (&m_wndToolBar); return TRUE; } BOOL CMainFrame::CreateStyleBar() { if (!m_wndStyleBar.Create (this, WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC, IDW_STYLE_BAR)) return FALSE; m_wndStyleBar.SetWindowText (_T ("Styles")); m_wndStyleBar.EnableDocking (CBRS_ALIGN_TOP | CBRS_ALIGN_BOTTOM); DockControlBar (&m_wndStyleBar); return TRUE; } BOOL CMainFrame::CreateStatusBar() { static UINT nIndicators[] = { ID_SEPARATOR, ID_INDICATOR_LINE, ID_INDICATOR_CAPS, ID_INDICATOR_NUM }; if (!m_wndStatusBar.Create (this)) return FALSE; m_wndStatusBar.SetIndicators (nIndicators, 4); return TRUE; }


// MyWordDoc.h : interface of the CMyWordDoc class // /////////////////////////////////////////////////////////////////////////// #if !defined( AFX_MYWORDDOC_H__C85C908B_A154_11D2_8E53_006008A82731__INCLUDED_) #define AFX_MYWORDDOC_H__C85C908B_A154_11D2_8E53_006008A82731__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 class CMyWordDoc : public CRichEditDoc { protected: // create from serialization only CMyWordDoc(); DECLARE_DYNCREATE(CMyWordDoc) // Attributes public: // Operations public: // Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CMyWordDoc) public: virtual BOOL OnNewDocument(); virtual void Serialize(CArchive& ar); //}}AFX_VIRTUAL virtual CRichEditCntrItem* CreateClientItem(REOBJECT* preo) const; // Implementation public: virtual ~CMyWordDoc(); #ifdef _DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif protected: // Generated message map functions protected: //{{AFX_MSG(CMyWordDoc) // NOTE - the ClassWizard will add and remove member functions here. // DO NOT EDIT what you see in these blocks of generated code ! //}}AFX_MSG DECLARE_MESSAGE_MAP() }; /////////////////////////////////////////////////////////////////////////// //{{AFX_INSERT_LOCATION}} // Microsoft Visual C++ will insert additional declarations // immediately before the previous line. #endif // !defined( // AFX_MYWORDDOC_H__C85C908B_A154_11D2_8E53_006008A82731__INCLUDED_)


// MyWordDoc.cpp : implementation of the CMyWordDoc class // #include "stdafx.h" #include "MyWord.h" #include "MyWordDoc.h" #include "CntrItem.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif /////////////////////////////////////////////////////////////////////////// // CMyWordDoc IMPLEMENT_DYNCREATE(CMyWordDoc, CRichEditDoc) BEGIN_MESSAGE_MAP(CMyWordDoc, CRichEditDoc) //{{AFX_MSG_MAP(CMyWordDoc) // NOTE - the ClassWizard will add and remove mapping macros here. // DO NOT EDIT what you see in these blocks of generated code! //}}AFX_MSG_MAP // Enable default OLE container implementation ON_UPDATE_COMMAND_UI(ID_OLE_EDIT_LINKS, CRichEditDoc::OnUpdateEditLinksMenu) ON_COMMAND(ID_OLE_EDIT_LINKS, CRichEditDoc::OnEditLinks) ON_UPDATE_COMMAND_UI_RANGE(ID_OLE_VERB_FIRST, ID_OLE_VERB_LAST, CRichEditDoc::OnUpdateObjectVerbMenu) END_MESSAGE_MAP() /////////////////////////////////////////////////////////////////////////// // CMyWordDoc construction/destruction CMyWordDoc::CMyWordDoc() { } CMyWordDoc::~CMyWordDoc() { } BOOL CMyWordDoc::OnNewDocument() { if (!CRichEditDoc::OnNewDocument()) return FALSE; return TRUE; } CRichEditCntrItem* CMyWordDoc::CreateClientItem(REOBJECT* preo) const { return new CMyWordCntrItem(preo, (CMyWordDoc*) this); } /////////////////////////////////////////////////////////////////////////// // CMyWordDoc serialization void CMyWordDoc::Serialize(CArchive& ar) { CRichEditDoc::Serialize(ar); } /////////////////////////////////////////////////////////////////////////// // CMyWordDoc diagnostics #ifdef _DEBUG void CMyWordDoc::AssertValid() const { CRichEditDoc::AssertValid(); } void CMyWordDoc::Dump(CDumpContext& dc) const { CRichEditDoc::Dump(dc); } #endif //_DEBUG /////////////////////////////////////////////////////////////////////////// // CMyWordDoc commands


// MyWordView.h : interface of the CMyWordView class // /////////////////////////////////////////////////////////////////////////// #if !defined( AFX_MYWORDVIEW_H__C85C908D_A154_11D2_8E53_006008A82731__INCLUDED_) #define AFX_MYWORDVIEW_H__C85C908D_A154_11D2_8E53_006008A82731__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 class CMyWordCntrItem; class CMyWordView : public CRichEditView { protected: // create from serialization only CMyWordView(); DECLARE_DYNCREATE(CMyWordView) // Attributes public: CMyWordDoc* GetDocument(); // Operations public: void GetFontInfo (LPTSTR pszFaceName, int& nSize); void ChangeFont (LPCTSTR pszFaceName); void ChangeFontSize (int nSize); // Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CMyWordView) public: virtual BOOL PreCreateWindow(CREATESTRUCT& cs); protected: virtual void OnInitialUpdate(); // called first time after construct //}}AFX_VIRTUAL // Implementation public: virtual ~CMyWordView(); #ifdef _DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif protected: // Generated message map functions protected: //{{AFX_MSG(CMyWordView) afx_msg void OnDestroy(); afx_msg void OnCharBold(); afx_msg void OnCharItalic(); afx_msg void OnCharUnderline(); afx_msg void OnParaLeft(); afx_msg void OnParaCenter(); afx_msg void OnParaRight(); afx_msg void OnUpdateCharBold(CCmdUI* pCmdUI); afx_msg void OnUpdateCharItalic(CCmdUI* pCmdUI); afx_msg void OnUpdateCharUnderline(CCmdUI* pCmdUI); afx_msg void OnUpdateParaLeft(CCmdUI* pCmdUI); afx_msg void OnUpdateParaCenter(CCmdUI* pCmdUI); afx_msg void OnUpdateParaRight(CCmdUI* pCmdUI); //}}AFX_MSG afx_msg void OnUpdateLineNumber (CCmdUI* pCmdUI); DECLARE_MESSAGE_MAP() }; #ifndef _DEBUG // debug version in MyWordView.cpp inline CMyWordDoc* CMyWordView::GetDocument() { return (CMyWordDoc*)m_pDocument; } #endif /////////////////////////////////////////////////////////////////////////// //{{AFX_INSERT_LOCATION}} // Microsoft Visual C++ will insert additional declarations // immediately before the previous line. #endif // !defined( // AFX_MYWORDVIEW_H__C85C908D_A154_11D2_8E53_006008A82731__INCLUDED_)


// MyWordView.cpp : implementation of the CMyWordView class // #include "stdafx.h" #include "MyWord.h" #include "MyWordDoc.h" #include "CntrItem.h" #include "MyWordView.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif /////////////////////////////////////////////////////////////////////////// // CMyWordView IMPLEMENT_DYNCREATE(CMyWordView, CRichEditView) BEGIN_MESSAGE_MAP(CMyWordView, CRichEditView) //{{AFX_MSG_MAP(CMyWordView) ON_WM_DESTROY() ON_COMMAND(ID_CHAR_BOLD, OnCharBold) ON_COMMAND(ID_CHAR_ITALIC, OnCharItalic) ON_COMMAND(ID_CHAR_UNDERLINE, OnCharUnderline) ON_COMMAND(ID_PARA_LEFT, OnParaLeft) ON_COMMAND(ID_PARA_CENTER, OnParaCenter) ON_COMMAND(ID_PARA_RIGHT, OnParaRight) ON_UPDATE_COMMAND_UI(ID_CHAR_BOLD, OnUpdateCharBold) ON_UPDATE_COMMAND_UI(ID_CHAR_ITALIC, OnUpdateCharItalic) ON_UPDATE_COMMAND_UI(ID_CHAR_UNDERLINE, OnUpdateCharUnderline) ON_UPDATE_COMMAND_UI(ID_PARA_LEFT, OnUpdateParaLeft) ON_UPDATE_COMMAND_UI(ID_PARA_CENTER, OnUpdateParaCenter) ON_UPDATE_COMMAND_UI(ID_PARA_RIGHT, OnUpdateParaRight) //}}AFX_MSG_MAP ON_UPDATE_COMMAND_UI(ID_INDICATOR_LINE, OnUpdateLineNumber) END_MESSAGE_MAP() /////////////////////////////////////////////////////////////////////////// // CMyWordView construction/destruction CMyWordView::CMyWordView() { } CMyWordView::~CMyWordView() { } BOOL CMyWordView::PreCreateWindow(CREATESTRUCT& cs) { return CRichEditView::PreCreateWindow(cs); } void CMyWordView::OnInitialUpdate() { CRichEditView::OnInitialUpdate(); CHARFORMAT cf; cf.cbSize = sizeof (CHARFORMAT); cf.dwMask = CFM_BOLD | CFM_ITALIC | CFM_UNDERLINE | CFM_PROTECTED | CFM_STRIKEOUT | CFM_FACE | CFM_SIZE; cf.dwEffects = 0; cf.yHeight = 240; // 240 twips == 12 points ::lstrcpy (cf.szFaceName, _T ("Times New Roman")); SetCharFormat (cf); } void CMyWordView::OnDestroy() { // Deactivate the item on destruction; this is important // when a splitter view is being used. CRichEditView::OnDestroy(); COleClientItem* pActiveItem = GetDocument()->GetInPlaceActiveItem(this); if (pActiveItem != NULL && pActiveItem->GetActiveView() == this) { pActiveItem->Deactivate(); ASSERT(GetDocument()->GetInPlaceActiveItem(this) == NULL); } } /////////////////////////////////////////////////////////////////////////// // CMyWordView diagnostics #ifdef _DEBUG void CMyWordView::AssertValid() const { CRichEditView::AssertValid(); } void CMyWordView::Dump(CDumpContext& dc) const { CRichEditView::Dump(dc); } CMyWordDoc* CMyWordView::GetDocument() // non-debug version is inline { ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CMyWordDoc))); return (CMyWordDoc*)m_pDocument; } #endif //_DEBUG /////////////////////////////////////////////////////////////////////////// // CMyWordView message handlers void CMyWordView::OnCharBold() { CHARFORMAT cf; cf = GetCharFormatSelection (); if (!(cf.dwMask & CFM_BOLD) || !(cf.dwEffects & CFE_BOLD)) cf.dwEffects = CFE_BOLD; else cf.dwEffects = 0; cf.dwMask = CFM_BOLD; SetCharFormat (cf); } void CMyWordView::OnCharItalic() { CHARFORMAT cf; cf = GetCharFormatSelection (); if (!(cf.dwMask & CFM_ITALIC) || !(cf.dwEffects & CFE_ITALIC)) cf.dwEffects = CFE_ITALIC; else cf.dwEffects = 0; cf.dwMask = CFM_ITALIC; SetCharFormat (cf); } void CMyWordView::OnCharUnderline() { CHARFORMAT cf; cf = GetCharFormatSelection (); if (!(cf.dwMask & CFM_UNDERLINE) || !(cf.dwEffects & CFE_UNDERLINE)) cf.dwEffects = CFE_UNDERLINE; else cf.dwEffects = 0; cf.dwMask = CFM_UNDERLINE; SetCharFormat (cf); } void CMyWordView::OnParaLeft() { OnParaAlign (PFA_LEFT); } void CMyWordView::OnParaCenter() { OnParaAlign (PFA_CENTER); } void CMyWordView::OnParaRight() { OnParaAlign (PFA_RIGHT); } void CMyWordView::OnUpdateCharBold(CCmdUI* pCmdUI) { OnUpdateCharEffect (pCmdUI, CFM_BOLD, CFE_BOLD); } void CMyWordView::OnUpdateCharItalic(CCmdUI* pCmdUI) { OnUpdateCharEffect (pCmdUI, CFM_ITALIC, CFE_ITALIC); } void CMyWordView::OnUpdateCharUnderline(CCmdUI* pCmdUI) { OnUpdateCharEffect (pCmdUI, CFM_UNDERLINE, CFE_UNDERLINE); } void CMyWordView::OnUpdateParaLeft(CCmdUI* pCmdUI) { OnUpdateParaAlign (pCmdUI, PFA_LEFT); } void CMyWordView::OnUpdateParaCenter(CCmdUI* pCmdUI) { OnUpdateParaAlign (pCmdUI, PFA_CENTER); } void CMyWordView::OnUpdateParaRight(CCmdUI* pCmdUI) { OnUpdateParaAlign (pCmdUI, PFA_RIGHT); } void CMyWordView::OnUpdateLineNumber(CCmdUI* pCmdUI) { int nLine = GetRichEditCtrl ().LineFromChar (-1) + 1; CString string; string.Format (_T ("Line %d"), nLine); pCmdUI->Enable (TRUE); pCmdUI->SetText (string); } void CMyWordView::ChangeFont(LPCTSTR pszFaceName) { CHARFORMAT cf; cf.cbSize = sizeof (CHARFORMAT); cf.dwMask = CFM_FACE; ::lstrcpy (cf.szFaceName, pszFaceName); SetCharFormat (cf); } void CMyWordView::ChangeFontSize(int nSize) { CHARFORMAT cf; cf.cbSize = sizeof (CHARFORMAT); cf.dwMask = CFM_SIZE; cf.yHeight = nSize; SetCharFormat (cf); } void CMyWordView::GetFontInfo(LPTSTR pszFaceName, int& nSize) { CHARFORMAT cf = GetCharFormatSelection (); ::lstrcpy (pszFaceName, cf.dwMask & CFM_FACE ? cf.szFaceName : _T ("")); nSize = cf.dwMask & CFM_SIZE ? cf.yHeight : -1; }


#if !defined( AFX_STYLEBAR_H__C85C9099_A154_11D2_8E53_006008A82731__INCLUDED_) #define AFX_STYLEBAR_H__C85C9099_A154_11D2_8E53_006008A82731__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 // StyleBar.h : header file // /////////////////////////////////////////////////////////////////////////// // CStyleBar command target class CStyleBar : public CToolBar { // Attributes public: // Operations public: static int CALLBACK EnumFontNameProc (ENUMLOGFONT* lpelf, NEWTEXTMETRIC* lpntm, int nFontType, LPARAM lParam); // Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CStyleBar) //}}AFX_VIRTUAL virtual void OnUpdateCmdUI (CFrameWnd* pTarget, BOOL bDisableIfNoHndler); // Implementation protected: void InitTypefaceList (CDC* pDC); CFont m_font; CComboBox m_wndFontNames; CComboBox m_wndFontSizes; // Generated message map functions //{{AFX_MSG(CStyleBar) afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); //}}AFX_MSG afx_msg void OnSelectFont (); afx_msg void OnSelectSize (); afx_msg void OnCloseUp (); DECLARE_MESSAGE_MAP() }; /////////////////////////////////////////////////////////////////////////// //{{AFX_INSERT_LOCATION}} // Microsoft Visual C++ will insert additional declarations // immediately before the previous line. #endif // !defined( // AFX_STYLEBAR_H__C85C9099_A154_11D2_8E53_006008A82731__INCLUDED_)


// StyleBar.cpp : implementation file // #include "stdafx.h" #include "MyWord.h" #include "MyWordDoc.h" #include "MyWordView.h" #include "StyleBar.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif /////////////////////////////////////////////////////////////////////////// // CStyleBar BEGIN_MESSAGE_MAP(CStyleBar, CToolBar) //{{AFX_MSG_MAP(CStyleBar) ON_WM_CREATE() //}}AFX_MSG_MAP ON_CBN_SELENDOK (IDC_FONTNAMES, OnSelectFont) ON_CBN_SELENDOK (IDC_FONTSIZES, OnSelectSize) ON_CBN_CLOSEUP (IDC_FONTNAMES, OnCloseUp) ON_CBN_CLOSEUP (IDC_FONTSIZES, OnCloseUp) END_MESSAGE_MAP() /////////////////////////////////////////////////////////////////////////// // CStyleBar message handlers int CStyleBar::OnCreate(LPCREATESTRUCT lpCreateStruct) { static int nFontSizes[] = { 8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 32, 36, 48, 72 }; if (CToolBar::OnCreate(lpCreateStruct) == -1) return -1; // // Load the toolbar. // if (!LoadToolBar (IDR_STYLE_BAR)) return -1; // // Create an 8-point MS Sans Serif font for the combo boxes. // CClientDC dc (this); m_font.CreatePointFont (80, _T ("MS Sans Serif")); CFont* pOldFont = dc.SelectObject (&m_font); TEXTMETRIC tm; dc.GetTextMetrics (&tm); int cxChar = tm.tmAveCharWidth; int cyChar = tm.tmHeight + tm.tmExternalLeading; dc.SelectObject (pOldFont); // // Add the font name combo box to the toolbar. // SetButtonInfo (8, IDC_FONTNAMES, TBBS_SEPARATOR, cxChar * 32); CRect rect; GetItemRect (8, &rect); rect.bottom = + (cyChar * 16); if (!m_wndFontNames.Create (WS_CHILD | WS_VISIBLE | WS_VSCROLL | CBS_DROPDOWNLIST | CBS_SORT, rect, this, IDC_FONTNAMES)) return -1; m_wndFontNames.SetFont (&m_font); InitTypefaceList (&dc); // // Add the font size combo box to the toolbar. // SetButtonInfo (10, IDC_FONTSIZES, TBBS_SEPARATOR, cxChar * 12); GetItemRect (10, &rect); rect.bottom = + (cyChar * 14); if (!m_wndFontSizes.Create (WS_CHILD | WS_VISIBLE | WS_VSCROLL | CBS_DROPDOWNLIST, rect, this, IDC_FONTSIZES)) return -1; m_wndFontSizes.SetFont (&m_font); CString string; int nCount = sizeof (nFontSizes) / sizeof (int); for (int i=0; i<nCount; i++) { string.Format (_T ("%d"), nFontSizes[i]); m_wndFontSizes.AddString (string); } return 0; } void CStyleBar::OnSelectFont () { TCHAR szFaceName[LF_FACESIZE]; int nIndex = m_wndFontNames.GetCurSel (); m_wndFontNames.GetLBText (nIndex, szFaceName); CMyWordView* pView = (CMyWordView*) ((CFrameWnd*) AfxGetMainWnd ())->GetActiveView (); pView->ChangeFont (szFaceName); } void CStyleBar::OnSelectSize () { TCHAR szSize[8]; int nIndex = m_wndFontSizes.GetCurSel (); m_wndFontSizes.GetLBText (nIndex, szSize); int nSize = atoi (szSize) * 20; // Need twips CMyWordView* pView = (CMyWordView*) ((CFrameWnd*) AfxGetMainWnd ())->GetActiveView (); pView->ChangeFontSize (nSize); } void CStyleBar::OnCloseUp () { ((CFrameWnd*) AfxGetMainWnd ())->GetActiveView ()->SetFocus (); } void CStyleBar::InitTypefaceList (CDC* pDC) { ::EnumFontFamilies (pDC->m_hDC, NULL, (FONTENUMPROC) EnumFontNameProc, (LPARAM) this); } int CALLBACK CStyleBar::EnumFontNameProc (ENUMLOGFONT* lpelf, NEWTEXTMETRIC* lpntm, int nFontType, LPARAM lParam) { CStyleBar* pWnd = (CStyleBar*) lParam; if (nFontType & TRUETYPE_FONTTYPE) pWnd->m_wndFontNames.AddString (lpelf->elfLogFont.lfFaceName); return 1; } void CStyleBar::OnUpdateCmdUI (CFrameWnd* pTarget, BOOL bDisableIfNoHndler) { CToolBar::OnUpdateCmdUI (pTarget, bDisableIfNoHndler); CWnd* pWnd = GetFocus (); if ((pWnd == &m_wndFontNames) || (pWnd == &m_wndFontSizes)) return; // // Get the font name and size. // int nTwips; TCHAR szFaceName[LF_FACESIZE]; CMyWordView* pView = (CMyWordView*) ((CFrameWnd*) AfxGetMainWnd ())->GetActiveView (); pView->GetFontInfo (szFaceName, nTwips); // // Update the font name combo box. // TCHAR szSelection[LF_FACESIZE]; m_wndFontNames.GetWindowText (szSelection, sizeof (szSelection) / sizeof (TCHAR)); if (::lstrcmp (szFaceName, szSelection) != 0) { if (szFaceName[0] == 0) m_wndFontNames.SetCurSel (-1); else { if (m_wndFontNames.SelectString (-1, szFaceName) == CB_ERR) m_wndFontNames.SetCurSel (-1); } } // // Update the font size combo box. // TCHAR szSize[4]; m_wndFontSizes.GetWindowText (szSize, sizeof (szSize) / sizeof (TCHAR)); int nSizeFromComboBox = atoi (szSize); int nSizeFromView = nTwips / 20; if (nSizeFromComboBox != nSizeFromView) { if (nTwips == -1) m_wndFontSizes.SetCurSel (-1); else { CString string; string.Format (_T ("%d"), nSizeFromView); if (m_wndFontSizes.SelectString (-1, string) == CB_ERR) m_wndFontSizes.SetCurSel (-1); } } }

The Main Toolbar

MyWord's main toolbar is a standard CToolBar that's created along with the style bar and status bar in CMainFrame::OnCreate. After the main toolbar is created, the styles CBRS_TOOLTIPS, CBRS_FLYBY, and CBRS_SIZE_DYNAMIC are added and CToolBar::EnableDocking is called with a CBRS_ALIGN_ANY parameter so that the toolbar can be docked to any side of the frame window. DockControlBar is called to dock the toolbar in its default location at the top of the window so that it can be detached and floated. The call to LoadBarState in CMainFrame::OnCreate restores the toolbar to its previous location if the application has been run before.

Handlers for all the buttons on the main toolbar—and for all the items in MyWord's menus, for that matter—are provided by the framework. As usual, CWinApp provides handlers for the New, Open, and Exit commands in the File menu, and CDocument handles the Save and Save As commands. CRichEditView provides handlers for the items in the Edit menu (all prewired into the message map, of course), and CFrameWnd handles the commands in the View menu. CRichEditView also provides update handlers for Edit commands, which explains why the Cut, Copy, Paste, and Undo buttons in the toolbar are automatically enabled and disabled in response to actions performed by the user. To see what I mean, type a line or two of text and highlight a few characters to form a selection. The Cut and Copy buttons will light up when the first character is selected and blink out again when the selection is canceled. Updates are automatic because of the following entries in CRichEditView's message map:


Scan the CRichEditView message map in the MFC source code file Viewrich.cpp to see the full range of commands for which CRichEditView provides default command and update handlers.

The Style Bar

MyWord's style bar is an instance of the CToolBar-derived class CStyleBar. The style bar is constructed when the frame window is constructed and created in CMainFrame::OnCreate, but it also contains its own OnCreate handler that creates and initializes the font name and font size combo boxes. Other CStyleBar member functions include OnSelectFont, which applies typefaces selected from the font name combo box; OnSelectSize, which applies sizes selected from the font size combo box; OnCloseUp, which restores the input focus to the view when either combo box's drop-down list box is closed; InitTypefaceList and EnumFontNameProc, which work together to enumerate fonts and add their names to the font name combo box; and OnUpdateCmdUI, which updates the combo boxes so that the font name and the font size shown in the style bar are consistent with the character at the caret or the characters in a selection.

MyWord's view class provides command and update handlers for the buttons in the style bar. Clicking the Bold button, for example, activates CMyWordView::OnCharBold, which is implemented as follows:

void CMyWordView::OnCharBold () { CHARFORMAT cf; cf = GetCharFormatSelection (); if (!(cf.dwMask & CFM_BOLD) ¦¦ !(cf.dwEffects & CFE_BOLD)) cf.dwEffects = CFE_BOLD; else cf.dwEffects = 0; cf.dwMask = CFM_BOLD; SetCharFormat (cf); }

GetCharFormatSelection is a CRichEditView function that returns a CHARFORMAT structure containing information about the text that is currently selected in the view or, if there is no selection, about the default character format. SetCharFormat is another CRichEditView function that applies the text attributes described in a CHARFORMAT structure to the selected text. If no text is currently selected, SetCharFormat sets the view's default character format.

Boldface text is toggled on or off by setting the CFM_BOLD bit in the dwMask field of the CHARFORMAT structure passed to SetCharFormat and either setting or clearing the CFE_BOLD bit in the structure's dwEffects field. To determine the proper setting for the CFE_BOLD flag, OnCharBold inspects both the CFM_BOLD and CFE_BOLD flags in the CHARFORMAT structure returned by GetCharFormatSelection. The CFM_BOLD flag is clear if the current selection includes a mix of bold and nonbold text. If CFM_BOLD is set, either the selection consists entirely of bold or nonbold text or no text is currently selected. In either case, the CFE_BOLD flag indicates whether the selected (or default) text is bold or nonbold. OnCharBold can be called in five possible scenarios. The following table describes each set of circumstances and documents the result. The view's OnCharItalic and OnCharUnderline handlers use similar logic to toggle italic and underline on and off.

Circumstances Under Which OnCharBold Is Called dwMask & CFM_BOLD dwEffects &CFE_BOLD Action Taken by OnCharBold One or more characters are selected; the selection contains a mix of bold and nonbold text. 0 Undefined Makes all characters in the selection bold. One or more characters are selected; the selection consists entirely of bold text. Nonzero Nonzero Makes all characters in the selection nonbold. One or more characters are selected; the selection consists entirely of nonbold text. Nonzero 0 Makes all characters in the selection bold. No text is selected; the default character format is bold. Nonzero Nonzero Sets the default character format to nonbold. No text is selected; the default character format is nonbold. Nonzero 0 Sets the default character format to bold.

The handlers for the paragraph alignment buttons are simpler because their actions don't depend on the current paragraph alignment. CRichEditView provides a convenient OnParaAlign function for setting the paragraph alignment to left, right, or centered. (Unfortunately, neither a CRichEditView nor the rich edit control that is the foundation for a CRichEditView supports fully justified text that extends the width between both margins.) The statement

OnParaAlign (PFA_LEFT);

in OnParaLeft selects left-aligned text. If no text is selected in the view, OnParaAlign reformats the paragraph that contains the caret. If text is selected, all paragraphs touched by the selection are transformed so that the text in them is left aligned.

Each button in the style bar is mapped to an update handler that calls either CRichEditView::OnUpdateCharEffect or CRichEditView::OnUpdateParaAlign. In addition to checking and unchecking the buttons as appropriate, these CRichEditView functions also set a button to the indeterminate state when a selection includes a mix of character formats or paragraph alignments. For a simple demonstration, try this test. First enter some text if you haven't already. Then highlight some characters, click the Italic button to italicize the selection, and select a range of characters that includes both italicized and nonitalicized text. Because OnUpdateCharItalic calls OnUpdateCharEffect, the Italic button will become half-grayed, indicating that the selection contains a mix of character formats. And because each style bar button is assigned an update handler, the buttons behave like check push buttons and radio push buttons even though none is assigned the TBBS_CHECKBOX or TBBS_CHECKGROUP style.

When a font name or a font size is selected from the combo boxes, the style bar retrieves the font name or font size and calls a public member function of the view class to implement the change. Selecting a font name activates CStyleBar::OnSelectFont, which passes the new typeface name to the view through CMyWordView::ChangeFont. ChangeFont, in turn, changes the font in the view by setting the CFM_FACE flag in a CHARFORMAT structure's dwMask field, copying the typeface name to the structure's szFaceName field and calling SetCharFormat:

void CMyWordView::ChangeFont (LPCTSTR pszFaceName) { CHARFORMAT cf; cf.cbSize = sizeof (CHARFORMAT); cf.dwMask = CFM_FACE; ::lstrcpy (cf.szFaceName, pszFaceName); SetCharFormat (cf); }

CStyleBar::OnSelectSize uses a similar procedure to change the font size through the view's ChangeFontSize member function. Font sizes passed to CRichEditViews are expressed in twips, where 1 twip equals 1/20 of a point. Therefore, OnSelectSize multiplies the point size retrieved from the combo box by 20 to convert points to twips before calling ChangeFontSize.

Which brings up a question: Because the command message generated when an item is selected from a combo box is subject to command routing, why doesn't MyWord let the view handle combo box notifications directly? Actually, that would be ideal. But it would also pose a problem. Because the combo boxes are protected members of the style bar class, the view would have no way of retrieving the selected item from the combo box. We could fix that by making the combo boxes public data members and the style bar a public data member of the frame window class, but protected data members provide stricter encapsulation. Letting the style bar handle combo box notifications and pass the information to the view through public member functions allows the style bar to hide its data yet still communicate style changes to the view.

So that the items selected in the combo boxes will match the character format in the view as the caret is moved through the document and selections are made, CStyleBar overrides the OnUpdateCmdUI function it inherits from CToolBar and updates the combo boxes based on information obtained from the view. After verifying that neither of the combo boxes has the input focus so that the combo boxes won't flicker if OnUpdateCmdUI is called while a drop-down list box is displayed, OnUpdateCmdUI calls CMyWordView::GetFontInfo to get the current font name and size. If the font name obtained from the view doesn't match the font name selected in the font name combo box, OnUpdateCmdUI changes the combo box selection. Similarly, the selection is updated in the font size combo box if the size shown in the combo box doesn't match the size reported by GetFontInfo. Leaving the current selection intact if it hasn't changed prevents the combo boxes from flickering as a result of repeated (and unnecessary) updates. The update handler is also smart enough to blank the combo box selection if the font name or font size obtained from GetFontInfo doesn't match any of the items in the combo box or if the text selected in the view contains a mixture of typefaces or font sizes.

One thing CStyleBar doesn't do is update the list of typefaces in the font name combo box if the pool of installed fonts changes while MyWord is running. When fonts are added or deleted, Windows sends all top-level windows a WM_FONTCHANGE message notifying them of the change. To respond to changes in font availability while an application is running, include an ON_WM_FONTCHANGE entry in the frame window's message map and an OnFontChange handler to go with it. The message-map entry and handler must be members of the frame window class because WM_FONTCHANGE messages are not routed, whereas command messages are.

To simplify the logic for updating the selection in the font size combo box, MyWord's style bar lists TrueType fonts only. If the font name combo box included raster fonts as well, the font size combo box would need to be reinitialized each time the selection changed in the font name combo box because raster fonts come in a limited number of sizes. Limiting the user's choice of fonts to TrueType only makes the point sizes listed in the font size combo box independent of the typeface selected in the font name combo box because TrueType fonts can be accurately rendered in any point size from 1 through 999.

More About CRichEditView

Most of MyWord's functionality comes from CRichEditView, which is built around the powerful rich text edit control provided in the common controls library. MFC's CRichEditView class doesn't act alone in encapsulating the features of a rich text edit control; help comes from CRichEditDoc and CRichEditCntrItem. CRichEditDoc represents the data stored in a CRichEditView, which can include linked and embedded OLE objects, and CRichEditCntrItem represents OLE objects contained in a CRichEditView.

When you derive a view class from CRichEditView, you must also derive a document class from CRichEditDoc and override CRichEditDoc::CreateClientItem, which is pure virtual. MyWord's CMyWordDoc document class implements CreateClientItem by creating a CRichEditCntrItem object and returning a pointer:

CRichEditCntrItem* CMyWordDoc::CreateClientItem (REOBJECT* preo) const { return new CMyWordCntrItem (preo, (CMyWordDoc*) this); }

This simple override enables the Paste and Paste Special commands in the Edit menu to paste OLE items into the document. For a demonstration, copy a picture created with the Windows Paint applet to the clipboard and paste it into a MyWord document. Then double-click the embedded image in MyWord, and Paint will merge its menus and toolbars with MyWord's menus and toolbars so that you can edit the picture in place. If the document is saved, the embedded Paint object is saved, too, so that it will come back up just as you left it when you reload the document.

In case you hadn't noticed, MyWord is fully capable of saving the documents you create and loading them back in. It can even read RTF files created by other word processors and serialize OLE objects. Yet CMyWordDoc::Serialize contains just one statement:


You won't find any other serialization code in CMyWordDoc because CRichEditDoc can handle serialization on its own. CRichEditDoc::Serialize streams data to and from a CRichEditView by calling the view's Serialize function, which in turn relies on the streaming capabilities built into a rich text edit control. (For more information, see the documentation for the EM_STREAMIN and EM_STREAMOUT messages that can be sent to a rich text edit control and the equivalent StreamIn and StreamOut function members of MFC's CRichEditCtrl class.) It's relatively easy to write an SDK application that saves and loads documents in a rich text edit control, but it's downright simple to do it in MFC because CRichEditDoc and CRichEditView work together with other components of the framework to handle all phases of the serialization process for you.

By default, CRichEditDoc serializes documents in rich text format. You can instruct a CRichEditDoc to write text files that lack formatting information and OLE objects by setting the CRichEditDoc data member m_bRTF equal to FALSE before storing a document. By the same token, you can read files in plain text format by setting m_bRTF to FALSE before dearchiving a document. It wouldn't be hard to give MyWord the ability to read and write text files as well as rich text format files, but you'd have to add some logic to the front end of the deserialization process to identify the type of file that's about to be read. CRichEditDoc won't load a text file if m_bRTF is TRUE, and if it reads a rich text format document with m_bRTF equal to FALSE, it converts RTF formatting commands to ordinary text. A full treatment of CRichEditDoc serialization options is beyond the scope of this book, but if you're interested in learning more, a good place to start is the Wordpad source code provided with MFC.