DeskPad

Introduction

A rich edit control is a text-based object that can handle text and paragraph formatting. To create such an application, you can use the CRichEditView class. This class is equipped with properties and methods to perform the most regularly operations on such an application. In most cases, the class can handle the necessary operations. Nevertheless, in this exercise, we will take care of them using another technique.

Application's Startup

Probably the fastest way to create a rich edit application is by using the MFC AppWizard and selecting the base class as CRichEditView. The application created like that is not equipped with the necessary main menu nor even the context menu. Also, there is no toolbar to support regular operations. Therefore, after creating the application with the wizard, you would need to create menu items and other convenient objects to support rich edit operations.

Practical Learning: Creating the Application

  1. Start Microsoft Visual C++
  2. Press Ctrl + N to access File -> New
  3. When the New dialog box shows up, click the Projects tab, and choose MFC AppWizard (exe)
  4. In the Project name edit box, type DeskPad as the name of the project
  5. In the Location box, specify the directory where you want to create your application and press Enter (or click OK)
  6. The MFC AppWizard - Step 1 shows up: accept all default and press Enter (or click Next)
  7. Accept the defaults in the next 3 pages by clicking Next in the MFC Appwizard - Steps 2, 3, 4, and 5 of 6
  8. In Step 6, make the changes according to the following table: 
     
    Class Name New Class Name Header File Implementation File Base Class
    CDeskPadView CExoView ExoView.h ExoView.cpp CRichEditView
    CExoApp CExoApp No Change No Change No Change
    CDeskPadDoc CExoDoc ExoDoc.h ExoDoc.cpp No Change
  9. Click the Finish button. After you have read the Microsoft Visual C++ message requiring OLE container support for the project, press Enter (or click OK)
  10. The New Project Information appears. Examine it to verify that it contains all the features you requested AppWizard to create. Then press Enter (or click OK)
  11. Test the appication

The Default Font

A rich edit application created with AppWizard displays its text is a default font. If you plan to let users change or specify a font of their choice, you would need to provide this functionality or at least change the default font.

To change the default font, you can use one of the first events that fire when the application starts. Based on the concepts of the Document/View architecture, you can use the CDocument::OnNewDocument() event or you can use the CRichEditView::OnInitialUpdate() event. In the event, you can declare and initialize a CHARFORMAT variable with the necessary values on its member variables.

As its name indicates, the CHARFORMAT structure is used to specify some features about the characters, text, or selection that you want to modify in your document. Before defining the initial character features, we will create and reserve a control class that will communicate our set characteristics to the rest of the application. To accomplish that, we will use the CRichEditCtrl class.

The CHARFORMAT structure is full of features. You usually don't need all of them all the time. For example, in our document, we will be using some font styles (bold, italic, underline, strikeout). We will also use, change, or manipulate various font sizes. That's what the dwMask of the structure is used for. While we are there, we can specify the initial font size and face (name). I chose Times New Roman for its regularity in Microsoft Windows.

Once you have defined your initial font features, you call the CRichEditCtrl::SetDefaultCharFormat() function and provide the newly defined structure as its argument.

 

 

 

Practical Learning: Changing the Default Font

  1. From the WorkSpace, click the ClassView tab. Expand the DeskPad classes. Expand CExoView
  2. Double-click the OnInitialUpdate() function and change it as follows:
     
    void CExoView::OnInitialUpdate()
    {
    	CRichEditView::OnInitialUpdate();
     
        CRichEditCtrl& rCtrl = GetRichEditCtrl(); 
        CHARFORMAT cfm; 
        cfm.cbSize = sizeof(CHARFORMAT); 
        cfm.dwMask = CFM_FACE | CFM_SIZE | CFM_BOLD | 
    
        CFM_ITALIC | CFM_UNDERLINE | CFM_STRIKEOUT | CFM_PROTECTED; 
        cfm.dwEffects = 0; 
        cfm.yHeight = 240; :
        ::lstrcpy(cfm.szFaceName, "Times New Roman");
    	
        rCtrl.SetDefaultCharFormat(cfm); 
    
        SetMargins(CRect(720, 720, 720, 720));
    }
  3. To give your users the ability to use the CFontDialog to format characters, from the WorkSpace, click the ResourceView tab. Expand the DeskPad resources and expand the Menu folder. Double-click the IDR_DESKPATYPE menu
  4. Drag the Empty box next to the Help menu to between View and Window, then type F&ormat
  5. Click the empty submenu under Format and type &Font...
  6. Change its Prompt to Change the font of the selected text\nFont
  7. Test the application

Character Formatting

Practical Learning: Formatting Characters

  1. From the WorkSpace, in the ResourceView tab, right-click the Toolbar folder and choose Insert Toolbar
  2. A new toolbar, called IDR_TOOLBAR1, appears. Right-click it and choose Properties
  3. Click the pushpin on the Properties dialog box to keep it always on top of Visual C++
  4. Change the identification of IDR_TOOLBAR1 to IDR_FORMATBAR
  5. Create some buttons on it identified respectively as: IDC_CHAR_BOLD, IDC_CHAR_ITALIC, IDC_CHAR_UNDERLINE, IDC_CHAR_STRIKEOUT, separator, IDC_CHAR_COLOR, IDC_INSERT_BULLET, separator, IDC_PARA_LEFT, IDC_PARA_CENTER, and IDC_PARA_RIGHT
     
  6. Double-click the IDR_DESKPATYPE Menu to access its menu
  7. Under the view menu, add a sub item with the caption &Format Bar
  8. Also, under the Format menu, add a few submenus for some of the existing toolbar buttons
  9. Double-click the IDR_FORMATBAR toolbar to give it focus
  10. To create the toolbar, click Ctrl + W to access the ClassWizard
  11. You might receive a message indicating that IDR_FORMATBAR is a new resource and needs a class. If you receive that warning, click the Create a new class radio button. If the IDR_FORMATBAR didn't have the focus when you accessed the ClassWizard, then you might not receive that message; in this case, click the Add Class... button and choose to create a new class. Specify the name of the new class as CFormatBar, based on CToolBatCtrl
  12. Press Enter two times or until you get back to the WorkSpace where you will click the ClassView tab
  13. Double-click the CFormatBar class to access its header file and change its base class to CToolBar
  14. In the WorkSpace, expand the CFormatBar class and double-click its constructor. On the BEGIN_MESSAGE_MAP line, make the following change: 
    BEGIN_MESSAGE_MAP(CFormatBar, CToolBar)
  15. In the CMainFrame header file, declare the CFormatBar: 
     
    protected:  // control bar embedded members
    	CStatusBar  m_wndStatusBar;
    	CToolBar    m_wndToolBar;
    	CFormatBar m_wndFormatBar;
  16. On top of that same file, include the formatbar header: 
    #include "FormatBar.h"
  17. In the MainFrm.cpp, implement the formatbar following the example of the standard toolbar. When you finish, your CFrameWnd::OnCreate() function might look like this: 
     
    int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
    {
    	if (CMDIFrameWnd::OnCreate(lpCreateStruct) == -1)
    		return -1;
    	
    	if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT,
    		WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER |
    		CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
    		!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
    	{
    		TRACE0("Failed to create the Standard toolbar\n");
    		return -1;      // fail to create
    	}
    
    	m_wndToolBar.SetWindowText("Standard");
    
    	if (!m_wndFormatBar.CreateEx(this, TBSTYLE_FLAT,
    		WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | 
    		CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) || 
    		!m_wndFormatBar.LoadToolBar(IDR_FORMATBAR)) 
    	{
    		TRACE0("Failed to create the Formatting toolbar\n");
    		return -1; // fail to create 
    	}
    	
    	m_wndFormatBar.SetWindowText("Formatting"); 
    	
    	if (!m_wndStatusBar.Create(this) ||
    		!m_wndStatusBar.SetIndicators(indicators,
    		  sizeof(indicators)/sizeof(UINT)))
    	{
    		TRACE0("Failed to create status bar\n");
    		return -1;      // fail to create
    	}
    
    	m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
    	m_wndFormatBar.EnableDocking(CBRS_ALIGN_ANY);
    	EnableDocking(CBRS_ALIGN_ANY);
    	DockControlBar(&m_wndToolBar);
    	DockControlBar(&m_wndFormatBar); 
    
    	return 0;
    }
  18. If you build and execute your app now, you should have two toolbars. The new one (CFormatBar) is completely grayed because none of its buttons has received any instruction about its behavior. The worst thing is that since this toolbar is also detachable/dockable, if you hide or disable it, you will not be able to get it back. Let's modify that.
    In your main frame class declare a function that will get the state of the toolbar: 
     
    // Implementation
    public:
    	virtual ~CMainFrame();
    	CFormatBar *GetFormatBar() { return &m_wndFormatBar; }
  19. Press Ctrl + W to access the ClassWizard. In the Class name combo box, choose CMainFrame
  20. In the Object IDs list box, choose ID_VIEW_FORMATBAR. In the Messages box, double-click COMMAND
  21. Change the name of the function to OnViewFormatBar and click OK
  22. Double-click UPDATE_COMMAND_UI
  23. Change the name of the function to OnUpdateViewFormatBar and click OK
  24. Click Edit Code and implement these two functions as follows: 
     
    void CMainFrame::OnViewFormatBar() 
    {
        // TODO: Add your command handler code here
        CMainFrame* pFrame = (CMainFrame *)GetTopLevelFrame();
        CControlBar* pBar = pFrame->GetFormatBar(); 
    
        if (pBar)
            pFrame->ShowControlBar(pBar, (pBar->GetStyle() & WS_VISIBLE) == 0, FALSE); 
    }
    
    void CMainFrame::OnUpdateViewFormatBar(CCmdUI* pCmdUI) 
    {
        // TODO: Add your command update UI handler code here
        CMainFrame* pFrame = (CMainFrame *)GetTopLevelFrame();
        CControlBar* pBar = pFrame->GetFormatBar(); 
    
        if (pBar != NULL)
        {
            pCmdUI->SetCheck((pBar->GetStyle() & WS_VISIBLE) != 0); return; 
        }
    	
        pCmdUI->ContinueRouting();
    }
  25. To evaluate what we have done so far, you can build and execute your app now

Paragraph Formatting

While CHARFORMAT is a structure used to format characters and text in a CRichEditView application, the PARAFORMAT structure is used to perform paragraph formatting in the same application. You are encouraged to consult their documentations in the MSDN (CD-ROM or web site) which are fairly large. By their definitions, they have all the attributes necessary to do their respective formatting, but you still need the aid of the CRichEditCtrl class to control the setting of the attributes you specify for a particular situation. To start, keep in mind that there are practically three behaviors a toolbar button has:

  • You press a button and it performs an operation but it doesn't change its state, it stays up, an example is the ID_FILE_NEW
  • You press a button and it stays down throughout its operation until you depress it, this behavior is the same as a check box, an example is a bold, italic, or underline buttons
  • A few buttons stay in a group like radio buttons. Only one in the group can be and stay pressed at a time. An example is the group of Left, Center, and Right buttons.

So, we need to treat some buttons as standalone and others as a group. A button that behaves as a check box has a Boolean attribute because its state always tells the user that it is pressed or it is not, and it only controls its own behavior. We will treat the other three buttons as a group (and that's what they are).

To manually configure the buttons on the toolbar, whenever the user clicks a Boolean-based button, we need to reverse its state, if it is down, we want it up, and vice versa. And we will implement the same logic for the other format character buttons: 
 

void CExoView::OnBold() 
{ 
bBold = !bBold; 
} 
void CExoView::OnCharItalic() 
{

bItalic = !bItalic; 
} 
void CExoView::OnCharUnderline() 
{

bUnderline = !bUnderline; 
} 
void CExoView::OnCharStrikeout() 
{

bStrikeout = !Strikeout; 
}

If the user clicks a button in a group, we just want to keep that particular button as pressed even if it is already: 
 

void CExoView::OnLeft() 
{ 
m_Justify = LEFT; 
} 
d) Use the same logics for the Center and the Right justified buttons. 
void CExoView::OnParaCenter() 
{ 
m_Justify = CENTER; 
} 
void CExoView::OnParaRight() 
{

m_Justify = RIGHT; 
}

For the character buttons, as you already know, the character formatting buttons behave like check boxes. So, we will call the CCmdUI::SetCheck() function for each button and use its Boolean attribute as its argument: 
 

void CExoView::OnUpdateBold(CCmdUI* pCmdUI) 
{ 
pCmdUI->SetCheck( bBold ); 
} 
void CExoView::OnUpdateCharItalic(CCmdUI* pCmdUI) 
{

pCmdUI->SetCheck( bItalic ); 
} 
void CExoView::OnUpdateCharUnderline(CCmdUI* pCmdUI) 
{

pCmdUI->SetCheck( bUnderline ); 
} 
void CExoView::OnUpdateCharStrikeout(CCmdUI* pCmdUI) 
{

pCmdUI->SetCheck( bStrikeout ); 
}

You already know that the paragraph formatting buttons act like radio buttons. So, for the paragraph formatting buttons, we will call the CCmdUI::SetRadio() function and set the enumerated value as the argument: 
 

void CExoView::OnUpdateLeft(CCmdUI* pCmdUI) 
{ 
pCmdUI->SetRadio( m_Justify == LEFT ); 
} 

When you finish, your buttons should express their Boolean features. Of course, you know that the ID_CHAR_COLOR and the ID_INSERT_BULLET buttons don't use/need any of those behaviors

Over all, this formatting follows this approach:

  • Create a CRichEditCtrl object: 
    CRichEditCtrl& rCtrl = GetRichEditCtrl();
  • declare a CHARFORMAT structure:
    CHARFORMAT cf;
  • Before doing anything you need to get the current selection: 
    rCtrl.GetSelectionCharFormat(cf);
  • Now, assign the attributes you want for this particular button: 
    cf.cbSize = sizeof(cf); 
    cf.dwMask = CFM_BOLD; 
    cf.dwEffects ^= CFE_BOLD;
  • Finally, assign those attributes to the current selection: 
    rCtrl.SetSelectionCharFormat(cf); 
  • Keep the boolean argument at the end of the function

 

Practical Learning: Formatting Paragraphs

  1. In your ExoView.h file, declare the following three variable: 
     
    // Operations
    public:
    	BOOL bBold; 
    	BOOL bItalic; 
    	BOOL bUnderline;
    	BOOL bStrikeout;
    	BOOL bList;
  2. Since we will use the three paragraph formatting buttons as a group, we need to enumerate them. In the public implementation section of the CExoView class, type: 
     
    // Implementation
    public:
    	virtual ~CExoView();
    	enum JUST { LEFT = 0, CENTER, RIGHT } m_Justify;
  3. When the application starts, we want the text (or no text) to be left aligned. But we don't want any of the font styles to apply yet. Initialize or set their default attributes in the CPadView constructor: 
     
    CExoView::CExoView()
    {
    	m_Justify		= LEFT;
    	bBold		= FALSE;
    	bItalic		= FALSE;
    	bUnderline	= FALSE;
    	bStrikeout	= FALSE;
    	bList		= FALSE; 
    }
  4. In ClassWizard, from the Messages tab, make sure the class is CExoView and locate all the buttons we will use, double-click their COMMAND messages followed by their UPDATE_COMMAND_UI messages, accept the suggested function names. The buttons are as follows (and I suggest you follow this order, first the Command message, then the UpdateCommand message for each button): IDC_CHAR_BOLD, IDC_CHAR_ITALIC, IDC_CHAR_UNDERLINE, IDC_CHAR_STRIKEOUT, IDC_PARA_LEFT, IDC_PARA_CENTER, IDC_PARA_RIGHT
  5. Add only the COMMAND functions for the IDC_INSERT_COLOR and IDC_INSERT_BULLET
  6. Click Edit Code
  7. As described earlier, implement the events as follows: 
     
    void CExoView::OnCharBold() 
    {
    	// TODO: Add your command handler code here
    	CRichEditCtrl& rCtrl = GetRichEditCtrl();
    	CHARFORMAT cfm; 
    
    	rCtrl.GetSelectionCharFormat(cfm);
    	cfm.cbSize = sizeof(cfm);
    	cfm.dwMask = CFM_BOLD;
    	cfm.dwEffects ^= CFE_BOLD; 
    
    	rCtrl.SetSelectionCharFormat(cfm);
    	bBold = !bBold;
    }
    
    void CExoView::OnUpdateCharBold(CCmdUI* pCmdUI) 
    {
    	// TODO: Add your command update UI handler code here
    	pCmdUI->SetCheck( bBold );
    }
    
    void CExoView::OnCharItalic() 
    {
    	// TODO: Add your command handler code here
    	CRichEditCtrl& rCtrl = GetRichEditCtrl();
    	CHARFORMAT cfm; 
    
    	rCtrl.GetSelectionCharFormat(cfm);
    	cfm.cbSize = sizeof(cfm);
    	cfm.dwMask = CFM_ITALIC;
    	cfm.dwEffects ^= CFE_ITALIC; 
    
    	rCtrl.SetSelectionCharFormat(cfm);
    	bItalic = !bItalic;
    }
    
    void CExoView::OnUpdateCharItalic(CCmdUI* pCmdUI) 
    {
    	// TODO: Add your command update UI handler code here
    	pCmdUI->SetCheck( bItalic );
    }
    
    void CExoView::OnCharUnderline() 
    {
    	// TODO: Add your command handler code here
    	CRichEditCtrl& rCtrl = GetRichEditCtrl();
    	CHARFORMAT cfm; 
    
    	rCtrl.GetSelectionCharFormat(cfm);
    	cfm.cbSize = sizeof(cfm);
    	cfm.dwMask = CFM_UNDERLINE;
    	cfm.dwEffects ^= CFE_UNDERLINE; 
    
    	rCtrl.SetSelectionCharFormat(cfm);
    	bUnderline = !bUnderline;
    }
    
    void CExoView::OnUpdateCharUnderline(CCmdUI* pCmdUI) 
    {
    	// TODO: Add your command update UI handler code here
    	pCmdUI->SetCheck( bUnderline );
    }
    
    void CExoView::OnCharStrikeout() 
    {
    	// TODO: Add your command handler code here
    	CRichEditCtrl& rCtrl = GetRichEditCtrl();
    	CHARFORMAT cfm; 
    
    	rCtrl.GetSelectionCharFormat(cfm);
    	cfm.cbSize = sizeof(cfm);
    	cfm.dwMask = CFM_STRIKEOUT;
    	cfm.dwEffects ^= CFE_STRIKEOUT; 
    
    	rCtrl.SetSelectionCharFormat(cfm);
    	bStrikeout = !bStrikeout;
    }
    
    void CExoView::OnUpdateCharStrikeout(CCmdUI* pCmdUI) 
    {
    	// TODO: Add your command update UI handler code here
    	pCmdUI->SetCheck( bStrikeout );
    }
  8. In your CExoView::OnLeft() function, create a CRichEditCtrl structure. Declare a PARAFORMAT object. Set the mask attributes as PFM_ALIGNMENT. Set the appropriate alignment attribute for each button. Finally, assign the formatting features to the CRichEditCtrl control. When you finish, this section of your file should look like this: 
     
    void CExoView::OnParaLeft() 
    {
    	// TODO: Add your command handler code here
    	m_Justify = LEFT;
    	
    	CRichEditCtrl& rCtrl = GetRichEditCtrl(); 
    	PARAFORMAT pfm; 
    
    	pfm.cbSize = sizeof( pfm );
    	pfm.dwMask = PFM_ALIGNMENT;
    	pfm.wAlignment = PFA_LEFT; 
    
    	rCtrl.SetParaFormat(pfm);
    }
    
    void CExoView::OnUpdateParaLeft(CCmdUI* pCmdUI) 
    {
    	// TODO: Add your command update UI handler code here
    	pCmdUI->SetRadio( m_Justify == LEFT );
    }
    
    void CExoView::OnParaCenter() 
    {
    	// TODO: Add your command handler code here
    	m_Justify = CENTER;
    	
    	CRichEditCtrl& rCtrl = GetRichEditCtrl();
    	PARAFORMAT pfm; 
    
    	pfm.cbSize = sizeof( pfm );
    	pfm.dwMask = PFM_ALIGNMENT;
    	pfm.wAlignment = PFA_CENTER; 
    
    	rCtrl.SetParaFormat(pfm);
    }
    
    void CExoView::OnUpdateParaCenter(CCmdUI* pCmdUI) 
    {
    	// TODO: Add your command update UI handler code here
    	pCmdUI->SetRadio( m_Justify == CENTER );
    }
    
    void CExoView::OnParaRight() 
    {
    	// TODO: Add your command handler code here
    	m_Justify = RIGHT;
    	
    	CRichEditCtrl& rCtrl = GetRichEditCtrl();
    	PARAFORMAT pfm; 
    
    	pfm.cbSize = sizeof( pfm );
    	pfm.dwMask = PFM_ALIGNMENT;
    	pfm.wAlignment = PFA_RIGHT; 
    
    	rCtrl.SetParaFormat(pfm);
    }
    
    void CExoView::OnUpdateParaRight(CCmdUI* pCmdUI) 
    {
    	// TODO: Add your command update UI handler code here
    	pCmdUI->SetRadio( m_Justify == RIGHT );
    }
  9. For the color button, we will do a little more gymnastics. In the public implementation section of the ExoView header file, declare a COLORREF variable named m_TextColor:
     
    // Operations
    public:
    	BOOL bBold; 
    	BOOL bItalic; 
    	BOOL bUnderline; 
    	BOOL bList;
    	COLORREF m_TextColor;
  10. In the implementation of the OnCharColor() function, create a CRichEditCtrl object that will eventually control the selection. Get the current selection. Initialize the COLORREF element you have created. Initialize a CColorDialog object. Call the CColorDialog and set the CHARFORMAT attributes you want(related to the color) and assign them to the CRichEditCtrl

    Here is another implementation of the color dialog: 
     
    void CExoView::OnCharColor() 
    {
    	// TODO: Add your command handler code here
    	CColorDialog dlg;
    
    	if ( dlg.DoModal() == IDOK)
    	{
    		OnColorPick(dlg.GetColor());
    	}
    }
  11. The code for the InsertBullet() function is a little fancier than the usual. You first change the state of the button from its current behavior, using its Boolean attribute. Then you declare a CRichEditCtrl variable that will control the paragraph's behavior. You need to declare a PARAFORMAT variable that will carry the attributes you want to set for the button. Next, you specify the size of the structure, in bytes. After that, you need to validate the Boolean state of the button before assigning it the structure mask. Finally, you take control by assigning those features to the selection: 
     
    void CExoView::OnInsertBullet() 
    { 
    bList = !bList; 
    CRichEditCtrl& rCtrl = GetRichEditCtrl(); 
    PARAFORMAT pf; 
    
    pf.cbSize = sizeof(pf); 
    pf.dwMask = PFM_NUMBERING; 
    
    if( bList == TRUE ) 
    
    pf.wNumbering = PFN_BULLET; 
    rCtrl.SetParaFormat(pf); 
    }
  12. Test the application
 

Home Copyright 2004 FunctionX, Inc.