Home

VCL Controls: The Rich Edit

 

Introduction

Text is considered rich if it can afford a series of colorful and multi-style features that make it more attractive than a regular ASCII text. Such a text can have some of its sections in different colors. Its paragraphs can have customized attributes or arranged independent of each other. Although you can create a complete rich but static text, the common use of a rich text is to let the users process most of the formatting they need.

In VCL applications, a rich text is based on the TRichEdit class. To add rich text to your application, you can use the RichEdit button from the Win32 tab of the Component Palette. After adding a RichEdit control to a form, you can use the Object Inspector to customize it, using its Properties.

Practical Learning Practical Learning: Introducing the RichEdit Control

  1. Start a new application with its default form
  2. To save it, on the Standard toolbar, click the Save All button
  3. Create a new folder called Editor1 and display it in the Save In combo box
  4. Save the unit as Main and save the project as Editor
  5. Change the Caption of the form to Editor – Untitled and change its Name to frmMain
  6. Set the ShowHint property to true
  7. In the header file, declare a private AnsiString variable named CurrentFileName
    //---------------------------------------------------------------------------
    #ifndef MainH
    #define MainH
    //---------------------------------------------------------------------------
    #include <Classes.hpp>
    #include <Controls.hpp>
    #include <StdCtrls.hpp>
    #include <Forms.hpp>
    //---------------------------------------------------------------------------
    class TfrmMain : public TForm
    {
    __published: // IDE-managed Components
    private:
    	AnsiString CurrentFileName; // User declarations
    public: // User declarations
    	__fastcall TfrmMain(TComponent* Owner);
    };
    //---------------------------------------------------------------------------
    extern PACKAGE TfrmMain *frmMain;
    //---------------------------------------------------------------------------
    #endif
  8. In the constructor of the form, initialize the variable as:
    //---------------------------------------------------------------------------
    
    #include <vcl.h>
    #pragma hdrstop
    
    #include "Main.h"
    //---------------------------------------------------------------------------
    #pragma package(smart_init)
    #pragma resource "*.dfm"
    TfrmMain *frmMain;
    //---------------------------------------------------------------------------
    __fastcall TfrmMain::TfrmMain(TComponent* Owner)
    	: TForm(Owner)
    {
    	// The following string is not allowed for a Windows file
    	// This will allow us to know when the name of the file has been changed
    	CurrentFileName = "<Not Allowed>";
    }
    //---------------------------------------------------------------------------
  9. Display the form and double-click its body to access its OnCreate() event
  10. Because we will use the features of the rich edit control stored in a DLL, implement the event as follows:
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::FormCreate(TObject *Sender)
    {
    	int iRichDLL = (int)LoadLibrary("RICHED20.DLL");
    
    	if( !iRichDLL ) // If you could not load RICHED20.DLL
    	{
    		ShowMessage("Could not load the RICHED20.DLL");
    		Application->Terminate();
    		return;
    	}
    }
    //---------------------------------------------------------------------------
  11. On the Win32 tab of the Component Palette, click RichEdit and click in the body of the form. Change its Name to rchEditor
  12. On the Win32 tab of the Component Palette, double-click ImageList and, on the form, double-click ImageList1
  13. Using the Add button, add the following bitmaps: New, Save, Redo, NumBullet, Mail, FirstIndent, IndentLeft, IndentRight, and Paragraph
  14. Click OK
  15. From the Standard tab of the Component Palette, double-click ActionList and, while the new ActionList1 is still selected, on the Object Inspector, set its Images property to ImageList1
  16. On the form, double-click ActionList1 to create a list of actions
  17. In the ActionList window, right-click an empty area and click New Action. Set its properties as follows:
    Caption: &New
    Category: File 
    Hint: New|Create a new document
    ImageIndex: 0
    Name: FileNew 
    Shortcut: Ctrl+N
  18. In the same way, create a new action and set its properties as follows: 
    Caption: &Save
    Category: File 
    Hint: Save|Save the current document
    ImageIndex: 1
    Name: FileSave 
    Shortcut: Ctrl+S
  19. Once again, create a new action and set its properties as follows: 
    Caption: &Redo
    Category: Edit 
    Hint: Redo|Redo the previous action
    ImageIndex: 2
    Name: EditRedo 
    Shortcut: Ctrl+Y
  20. Add Another action and set its properties as follows: 
    Caption: &Numbering
    Category: Format 
    Hint: Numbering|Format a numeric list
    ImageIndex: 3
    Name: FormatNbr
  21. Add one more action and set its properties as follows: 
    Caption: &Numbering
    Category: Format 
    Hint: Numbering|Format a numeric list
    ImageIndex: 3
    Name: FormatNbr
  22. Add one more action and set its properties as follows: 
    Caption: &Standard
    Category: View 
    Checked: true
    Hint: Standard|Toggle the Standard toolbar 
    Name: ViewStandard
  23. Add one more action and set its properties as follows: 
    Caption: &Formatting
    Category: View 
    Checked: true
    Hint: Formatting|Toggle the Formatting toolbar 
    Name: ViewFormatting
  24. Add one more action and set its properties as follows: 
    Caption: Status &Bar
    Category: View 
    Checked: true
    Hint: Status Bar|Toggle the Status Bar 
    Name: ViewStatusBar
  25. Right-click any frame in the Action List Editor and click New Standard Action. From the Standard Action Classes dialog box, under the Internet node, double-click TSendMail
  26. In the left frame of the ActionList Editor, click Internet and, in the right frame, click SendMail1. On the Object Inspector, set ImageIndex to 4
  27. Right-click an empty area in the ActionList Editor and click New Standard Action. Once in the Standard Action Classes dialog box, click Edit. Press and hold Shift. Then click TRichEditAlignCenter and release Shift:
     
  28. Click OK
  29. Right-click in the ActionList Editor again and click New Standard Action. Click TFileOpen. Press and hold Ctrl. Then click TFileSaveAs, TFilePrintSetup, TFileExit, TSearchFind, TSearchFindNext, TSearchReplace, TSearchFindFirst, TColorSelect, TFontEdit, and TPrintDlg. Release Ctrl and click OK
  30. In the ActionList Editor window, on the left frame, click File. In the right frame, click FileSaveAs1
  31. In the Object Inspector, click the + button of Dialog to expand it
  32. Click DefaultExt and type rtf
  33. Click Filter and click its ellipsis button
  34. Complete the Filter Editor dialog box as follows:
     
  35. Click OK
  36. Click Title, type Save File As and press Enter
  37. Click the - button of Dialog to collapse it and click the Events property page
  38. Double-click the empty box on the right side of OnAccept() and implement the event as follows:
     
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::FileSaveAs1Accept(TObject *Sender)
    {
    	// Save the file as we reviewed in the lesson on strings
    	rchEditor->Lines->SaveToFile(FileSaveAs1->Dialog->FileName);
    	// Change the name of the file
    	CurrentFileName = FileSaveAs1->Dialog->FileName;
    	// Retrieve the name of the file to display on the title bar
    	AnsiString FName = ExtractFileName(CurrentFileName);
    	Caption = "Editor - " + FName;
    }
    //---------------------------------------------------------------------------
  39. Press F12 to display the form. Save the project
  40. To proceed with the traditional Save As implementation, on the Component Palette, click Dialogs. Click the SaveDialog button and click on the form. While the new SaveDialog1 control is still selected, on the Object Inspector, click the Properties tab and click DefaultExt. Type rtf
  41. Click Filter and click its ellipsis button . Complete the Filter Editor dialog box as follows:

  42. Click OK
  43. Click Title, type Save File As and press Enter
  44. Double-click ActionList1 to return to the ActionList Editor window. On the left frame, click File. On the right frame, click FileSave1 and, in the Object Inspector, click the Events tab. Double-click the empty box on the right side of OnExecute and implement the event as follows:
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::FileSaveExecute(TObject *Sender)
    {
    	// Find out if the current document has never been saved
    	// but is not empty
    	if( rchEditor->Modified && CurrentFileName == "<Not Allowed>" )
    	{
    		// Since the document is dirty, display the Save As dialog box
    		if( SaveDialog1->Execute() )
    		{
    			// Retrieve the new name file and display
    			// the file in the rich edit control
    			rchEditor->Lines->SaveToFile(SaveDialog1->FileName);
    			// Change/Update the global and complete name of the file,
    			// including its path
    			CurrentFileName = SaveDialog1->FileName;
    			// Extract the name of the file
    			AnsiString FName = ExtractFileName(CurrentFileName);
    			// Display the name of the current file on the title bar
    			Caption = "Editor - " + FName;
    		}
    	}
    	else
    	{
    		// It appears that this document already had a name
    		// but the document was previously modified
    		// Therefore, simply save it internally
    		rchEditor->Lines->SaveToFile(CurrentFileName);
    	}
    }
    //---------------------------------------------------------------------------
  45. Press F12 to display the form. Click the form to select it
  46. To handle the closing of the application, click the Events tab. Then, double-click the empty field of the OnClose event and implement it as follows:
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::FormClose(TObject *Sender, TCloseAction &Action)
    {
    	// Is the document dirty?
    	if( rchEditor->Modified )
    	{
    	// Since the document is dirty, find out if the user wants to save it
    		int Response = Application->MessageBox(
    			"The document has changed. Do you want to save it?"
    			"\nClick\n"
    			"Yes:\tTo save the document and close the application.\n"
    			"No:\tNot to save the document but close the application.\n"
    			"Cancel:\tNot to do anything",
    			"Editor - Saving a File",
    			MB_YESNOCANCEL | MB_ICONQUESTION);
    
    		// If the user wants to save it
    		if( Response == IDYES )
    		{
    			// Behave as if the user had clicked File -> Save
    			FileSave1Execute(Sender);
    			// Free the action
    			Action = caFree;
    		}
    		// If the user doesn't want to save the document
    		else if( Response == IDNO )
    			Action = caFree; // Simply free the action
    		else
    			Action = caNone; // The user cancelled the action: do nothing
    	}
    	else // There is no action to take
    		Action = caFree;
    }
    //---------------------------------------------------------------------------
  47. Display the ActionList Editor. Make sure that File is selected on the left frame
  48. To handle the creation of a new document, click the FileNew. In the Object Inspector, click the Events tab if necessary. Double-click the empty box on the right side of OnExecute and implement the event as follows:
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::FileNewExecute(TObject *Sender)
    {
    	// When the user clicks the New button to start a new document,
    	// find out if the document is dirty
    	if( rchEditor->Modified )
    	{
    	// Since the document is dirty, find out if the user wants to save it
    		int Response = Application->MessageBox(
    			"The document has changed. Do you want to save it?"
    			"\nClick\n"
    			"Yes:\tTo save the document and create a new one.\n"
    			"No:\tNot to save the document but create a new one.\n"
    			"Cancel:\tNot to do anything",
    			"Editor - Saving a File",
    		MB_YESNOCANCEL | MB_ICONQUESTION);
    
    		// If the user wants to save the document
    		if( Response == IDYES )
    		{
    			// Behave as if the Save action was initiated
    			FileSave1Execute(Sender);
    			// After saving the document, delete the whole document
    			rchEditor->Clear();
    			// Reset the current file name to garbage
    			CurrentFileName = "<Not Allowed>";
    			// Show on the title bar that the file is clean
    			Caption = "Editor - Untitled";
    			// Reset the Modified flag of the Rich Edit control
    			rchEditor->Modified = False;
    		}
    		// If the user doesn't want to save the document
    		else if( Response == IDNO )
    		{
    			// Delete the whole contents of the rich edit text
    			rchEditor->Clear();
    			// Fill the file name with garbage
    			CurrentFileName = "<Not Allowed>";
    			// Show on the title bar that the document is clean
    			Caption = "Editor - Untitled";
    			// Reset the Modified flag
    			rchEditor->Modified = False;
    		}
    		// The user clicked Cancel: Don't do nothing
    		else
    			Action = caNone;
    	}
    	else
    	{
    		rchEditor->Clear();
    		CurrentFileName = "<Not Allowed>";
    		Caption = "Editor - Untitled";
    		rchEditor->Modified = False;
    	}
    }
    //---------------------------------------------------------------------------
  49. In the ActionList Editor, in the right frame, click FileOpen1. Move FileOpen1 to be just under FileNew1 in the Actions list
  50. In the Object Inspector, click the Properties tab and expand the Dialog property if necessary. Click DefaultExt and type rtf
  51. Click Filter and click its ellipsis button . Complete the Filter Editor dialog box as follows:
     
  52. Click OK
  53. Click Title, type Open an Existing Document and press Enter
  54. Click the - button of Dialog to collapse it and click the Events tab
  55. Double-click the event field of OnAccept and implement it as follows:
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::FileOpen1Accept(TObject *Sender)
    {
    	// Before opening a new document, find out if the current one is dirty
    	if ( rchEditor->Modified )
    	{
    		// Since the document is dirty, prompt the user to save
    		int Response = Application->MessageBox(
    			"The document has changed. Do you want to save it?"
    			"\nClick\n"
    			"Yes:\tTo save the document and open the new one.\n"
    			"No:\tNot to save the document but open the new one.\n"
    			"Cancel:\tNot to do anything",
    			"Editor - Saving a File",
    			MB_YESNOCANCEL | MB_ICONQUESTION);
    
    		// If the user wants to save the current document
    		if( Response == IDYES )
    		{
    			// Behave as if the Save action was initiated
    			FileSaveExecute(Sender);
    			rchEditor->Lines->LoadFromFile(FileOpen1->Dialog->FileName);
    			CurrentFileName = FileOpen1->Dialog->FileName;
    			AnsiString FName = ExtractFileName(CurrentFileName);
    			Caption = "Editor - " + FName;
    			rchEditor->Modified = False;
    		}
    		// If the user doesn't want to save the document
    		else if( Response == IDNO )
    		{
    			// Open the new document after letting the user select it
    			rchEditor->Lines->LoadFromFile(FileOpen1->Dialog->FileName);
    			// Change the file name of our archives
    			CurrentFileName = FileOpen1->Dialog->FileName;
    			// Get the name of the file that the user selected
    			AnsiString FName = ExtractFileName(CurrentFileName);
    			// Display the file name on the title bar
    			Caption = "Editor - " + FName;
    			// Reset the Modified flag of the rich edit control
    			rchEditor->Modified = False;
    		}
    	}
    	// Apparently the current document is not dirty
    	else
    	{
    		rchEditor->Lines->LoadFromFile(FileOpen1->Dialog->FileName);
    		CurrentFileName = FileOpen1->Dialog->FileName;
    		AnsiString FName = ExtractFileName(CurrentFileName);
    		Caption = "Editor - " + FName;
    		rchEditor->Modified = False;
    	}
    }
    //---------------------------------------------------------------------------
  56. Close the Action Editor window. Save All and display the form
  57. From the Standard tab of the Component Palette, click the MainMenu button and click the form
  58. On the Object Inspector, click the Properties tab and set the Images of the MainMenu1 item to ImageList1
  59. On the form, double-click MainMenu1
  60. Right-click an empty area in the Menu Designer and click Insert From Template. In the Insert Template dialog box, double-click File Menu
  61. Click the last item under Exit and, on the Object Inspector, set its Action property to SendMail1
  62. Set the caption of the empty box under SendMail to - to add a separator. Move the Send Mail menu item and its separator above the Exit menu item
  63. Set the Actions to the menu items as follows: New = FileNew, Open = FileOpen1, Save = FileSave, Save As = FileSaveAs1, Print = PrintDlg1, Print Setup = FilePrintSetup1, Exit = FileExit1

  64. Click the box on the right side of File. Right-click an empty area in the Menu Editor and click Insert From Template
  65. In the Insert Template dialog box, double-click Edit Menu
  66. Delete the Paste Special, Go To, Links, and Object menu items
  67. In the Edit menu of the Menu Designer, Set the Actions as follows: Undo = EditUndo, Repeat <command> = EditRedo, Cut = EditCut1, Copy = EditCopy1, Paste = EditPaste1, Find = SearchFind1, Replace = SearchReplace1
  68. Using the empty boxes under the Replace menu item, Add the following actions: SearchFindFirst1, SearchFirstNext1, EditDelete1, EditSelectAll1. Arrange the menu item to have the following order:

  69. Click the box right to Edit. In the Object Inspector, click Caption and type &View and press Enter
  70. Click View and click the box under View. Using the Object Inspector, set its Action to ViewStandard. Set the Action of the next empty box to ViewFormatting. Set the action of the next empty box to ViewStatusBar
  71. Close the Menu Editor
  72. From the Standard tab of the Component Palette, click PopupMenu and click on the form
  73. On the form, right-click PopupMenu1 and click Menu Designer
  74. As the first item is selected, on the Object Inspector, set the Action to EditCut1. Set the Action of the second menu item to EditCopy1. Set the Action of the third menu item to EditPaste1
  75. Set the Caption of the 4th menu item to - to add a separator
  76. Set the 5th menu item to ColorSelect1. Set the new menu item’s Action to FontEdit1. Set the Action of the next item to RichEditBullets1
  77. Close the Menu Designer
  78. From the Win32 property page of the Component Palette, click ToolBar and click an empty area on the form. 
  79. On the Object Inspector, set the Images property of the toolbar to ImageList1. Set its Flat property to true and set its Height to 24. Set the Name to tbrStandard
  80. Right-click the toolbar, position the mouse on Edit and click Copy
  81. Right-click an empty area on the form, position your mouse on Edit and click Paste
  82. While the new toolbar is still selected, on the Object Inspector, change its Name to tbrFormatting
  83. Right-click the top toolbar and click New Button. Set its Action property to FileNew. Add other buttons and set their Action values to FileOpen1, FileSave, a separator, EditCut1, EditCopy1, EditPaste1, a separator, EditUndo1, EditRedo, a separator, and PrintDlg1:

  84. Add the buttons and separators to the bottom toolbar and set their Action values to the RichEditBold1, RichEditItalic1, RichEditUnderline1, RichEditStrikeOut1, a separator, RichEditBullets1, FormatNbr, a separator, RichEditAlignLeft1, RichEditAlignCenter1, and RichEditAlignRight1, a separator
  85. Right-click the bottom toolbar again and click New Button. Set the ImageIndex to 5 (FirstIndex) and change its Name to btnFirstIndent
  86. Add a New Button to the bottom toolbar. Set the ImageIndex to 6 and change its Name to btnIndentLeft
  87. Add one more button for the ImageIndex 7 and named btnIndentRight

  88. From the Win32 tab of the Component Palette, click StatusBar and click an empty area on the form
  89. Using the Object Inspector, change its Name to StatusBar
  90. On the form, double-click the status bar
  91. In the Panels Editor, right-click and click Add and, on the Object Inspector, set the Width to 320
  92. Right-click in the Panels Editor again and click Add. Change the Width to 100
  93. Right-click the Panels Editor again and click Add
  94. Close the Panels Editor and save the project
  95. In the Class Explorer, right-click TfrmMain and click New Method...

  96. Set the Method Name to ShowHints. Specify its Argument as TObject *Sender and the Function Result to void. Click the __fastcall check box and click OK
  97. Implement the method as follows:
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::ShowHints(TObject * Sender)
    {
    	StatusBar->Panels->Items[0]->Text = Application->Hint;
    }
    //---------------------------------------------------------------------------
  98. Press F12 to get back to the form. Double-click in an unoccupied area of the form and change its OnCreate event as follows:
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::FormCreate(TObject *Sender)
    {
    	int iRichDLL = (int)LoadLibrary("RICHED20.DLL");
    
    	if( !iRichDLL ) // If you could not load RICHED20.DLL
    	{
    		ShowMessage("Could not load the RICHED20.DLL");
    		Application->Terminate();
    	return;
    	}
    
    	Application->OnHint = ShowHints;
    }
    //---------------------------------------------------------------------------
  99. Press F12 to display the form
  100. On the form, double-click ActionList1 to open the Action List Editor. In the left frame, click View. In the right frame, click ViewStandard. In the Object Inspector, click the Events tab. Double-click the event side of the OnExecute field and implement its event as follows:
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::ViewStandardBarExecute(TObject *Sender)
    {
    	tbrStandard->Visible = !tbrStandard->Visible;
    	ViewStandard->Checked = !ViewStandard->Checked;
    }
    //---------------------------------------------------------------------------
  101. In the right frame of the ActionList Editor, double-click ViewFormatting and implement its OnExecute event as follows:
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::ViewFormattingExecute(TObject *Sender)
    {
    	tbrFormatting->Visible = !tbrFormatting->Visible;
    	ViewFormatting->Checked = !ViewFormatting->Checked;
    }
    //---------------------------------------------------------------------------
  102. In the right frame of the ActionList Editor, double-click ViewStatusBar and implement its OnExecute event as follows:
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::ViewStatusBarExecute(TObject *Sender)
    {
    	StatusBar->Visible = !StatusBar->Visible;
    	StatusBar1->Checked = !StatusBar1->Checked;
    }
    //---------------------------------------------------------------------------
  103. Save your project
    104. On the form, click the rich edit control to select it. In the Properties tab of the Object Inspector, click the Font field. Then click the ellipsis button 
    105. On the Font dialog box, scroll down in the Font combo box and select Times New Roman. In the Font Style combo box, select Regular. In the Size combo box, click 10. Make sure no style is selected in the Effects section and make sure the Color combo box displays Black:

  104. Click OK
  105. Save All

Rich Text Implementation

The most fundamental property on any text-based control is the text it holds, for a RichEdit, the text is stored in objects called lines and represented by the Lines property. For a RichEdit control, a Lines item is in fact an individual object based on the TStrings class. We will learn many techniques of manipulating a TStrings object when we study lists.

For a text-based control, a paragraph is a series of words that start with a letter or empty space until the flow of text is interrupted, which is usually considered a carriage return, or simply the end of the document. The management of such a paragraph is performed for a TRichEdit object using the Paragraph property. The paragraph is actually controlled by a class called TParaAttributes. By itself, this paragraph controls the alignment of the block, whether the block is part of a bulleted list, and such details as Tab measurements or indentation. To set or change the properties of a paragraph, you must first select it. To select a paragraph, you do not need to formally select it or any portion of its text. As long as the cursor is positioned inside of the paragraph, any paragraph attribute you set or change would apply to the whole paragraph. To manipulate more than one paragraph at the same time, you or your user must select them. The paragraphs do not need to be wholly selected. As long as a section is selected on each, the paragraphs are considered selected.

The most common property of a paragraph is its alignment, which states whether the paragraph is positioned to the left, the center, or the right. This capability is controlled by the Alignment property. Its three possible values are taLeftJustify, taCenter, and taRightJustify. To align text at design time, select the desired value using the Alignment property on the Object Inspector. Unfortunately, this would apply to the whole contents of the RichEdit control. Most of the time, you will want to let users change a paragraph's alignment while they are using your application. To change the alignment at run time, assign the desired value to the Alignment property. In the following examples, the alignment of the selected paragraph is set in response to the user clicking one of the buttons:

//---------------------------------------------------------------------------
void __fastcall TForm1::btnAlignLeftClick(TObject *Sender)
{
	rchEditor->Paragraph->Alignment = taLeftJustify; 
}
//---------------------------------------------------------------------------
void __fastcall TForm1::btnCenterClick(TObject *Sender)
{
	rchEditor->Paragraph->Alignment = taCenter; 
}
//---------------------------------------------------------------------------
void __fastcall TForm1::btnAlignRightClick(TObject *Sender)
{
	rchEditor->Paragraph->Alignment = taRightJustify; 
}
//---------------------------------------------------------------------------

Indentation consists of pushing a paragraph from one of the margins of the document. The TParaAttributes class allows you to indent the first line of a paragraph using the FirstIndent property. It is an integer value that sets or controls the amount of indentation of the first line. The LeftIndent property is an integer number that controls how much a paragraph is indented from the left margin of the document. The RightIndent value controls a similar indentation from the right margin.

To create an unordered list of items in your document, you can use the TParaAttributes::Numbering property. This property controls the assignment of a bulleted list. You have two options, If the paragraph is a regular one and you want to create a list, assign the nsBullet value to the Numbering property. If the paragraph is already a list but you want to convert it into a regular paragraph, assign the nsNone value to the property. Here is an example that applies when the user clicks a button called BulletList:

//---------------------------------------------------------------------------
void __fastcall TForm1::BulletList1Click(TObject *Sender)
{
	if( rchEditor->Paragraph->Numbering == nsBullet )
		rchEditor->Paragraph->Numbering = nsNone;
	else
		rchEditor->Paragraph->Numbering = nsBullet;
}
//---------------------------------------------------------------------------

One of the main characteristics of a rich text is the ability to set or control individual characteristics of sections of its text. The rich characteristics of text are controlled by the TTextAttributes class. This property allows you to change the font, its color, size, and/or style. To manipulate the text attributes, the text must be selected first. This means that the change applies only if there is a formal selection. For example, you can set or change the Bold style of the selected text when the user clicks a button called btnBold as follows:

//---------------------------------------------------------------------------
void __fastcall TForm1::btnBoldClick(TObject *Sender)
{
	if( btnBold->Down )
		rchEditor->SelAttributes->Style = TFontStyles() << fsBold;
	else
		rchEditor->SelAttributes->Style = TFontStyles() >> fsBold;
}
//---------------------------------------------------------------------------

Besides the proper characteristics of a rich text, the TRichEdit also shares or inherits the characteristics of a memo or an edit control.

Borland C++ Builder simplifies the configuring of a RichEdit control through the use of an ActionList control. The ActionList has many attributes already configured to seamlessly function with a RichEdit present on a form.

Practical Learning Practical Learning: Creating a RichEdit-Based Application

  1. On the form, click rchEditor to select the RichEdit control
  2. On the Object Inspector, change its Align property to alClient. Set HideSelection to false. Double-click the right field to Lines, delete the text and click OK. Set the PopupMenu property to PopupMenu1. Set the WantTabs property to true
  3. In the Object Inspector, click the Events tab. Display the ActionList Editor. In the left frame, click Dialog. In the right frame, click FontEdit1. In the Object Inspector, double-click on the right field to BeforeExecute
  4. Implement the event as follows:
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::FontEdit1BeforeExecute(TObject *Sender)
    {
    	// Get the characteristics of the select text
    	// Apply them to the Font dialog box
    	FontEdit1->Dialog->Font->Name = rchEditor->SelAttributes->Name;
    	FontEdit1->Dialog->Font->Style = rchEditor->SelAttributes->Style;
    	FontEdit1->Dialog->Font->Size = rchEditor->SelAttributes->Size;
    	FontEdit1->Dialog->Font->Color = rchEditor->SelAttributes->Color;
    }
    //---------------------------------------------------------------------------
  5. Go back to the ActionList Editor. On the Events tab of the FontEdit1, double-click the right field of OnAccept and implement the event as follows:
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::FontEdit1Accept(TObject *Sender)
    {
    	// Do the inverse of the BeforeExecute event
    	// If the user clicks OK, get the characteristics of the font
    	// Apply them to the selected text of the Rich Edit control
    	rchEditor->SelAttributes->Name = FontEdit1->Dialog->Font->Name;
    	rchEditor->SelAttributes->Style = FontEdit1->Dialog->Font->Style;
    	rchEditor->SelAttributes->Size = FontEdit1->Dialog->Font->Size;
    	rchEditor->SelAttributes->Color = FontEdit1->Dialog->Font->Color;
    }
    //---------------------------------------------------------------------------
  6. Press F12 to access the form. On the form, double-click MainMenu1. Click the box on the right side of View. In the Object Inspector, click the Properties tab and click Caption. Type F&ormat and press Enter
  7. On the Menu Designer, click Format. Under the Format menu, click the empty box and, on the Object Inspector, set its Action to FontEdit1
  8. Close the Menu Designer
  9. On the bottom toolbar, double-click the FirstIndent button and implement its OnClick event as follows:
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::btnFirstIndentClick(TObject *Sender)
    {
    	rchEditor->Paragraph->FirstIndent += 10;
    }
    //---------------------------------------------------------------------------
  10. On the bottom toolbar, double-click the IndentLeft button and implement its OnClick event as follows:
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::btnIndentLeftClick(TObject *Sender)
    {
    	rchEditor->Paragraph->LeftIndent += 10;
    }
    //---------------------------------------------------------------------------
  11. On the bottom toolbar, double-click the IndentRight button and implement its OnClick event as follows:
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::btnIndentRightClick(TObject *Sender)
    {
    	rchEditor->Paragraph->RightIndent += 10;
    }
    //---------------------------------------------------------------------------
  12. Save All

Rich Text Management

Because it can be asked to perform various and sometimes complex assignments, a rich edit control can process two categories of messages: those originally built-in the control and those that have been added over the years. This is why you may have to first load a rich edit DLL from the operating system because the control has mostly been updated since you installed your programming environment. All of the basic functionality of a text-based control is natively supported in the rich edit control as we have seen or used it so far. Common operations include cutting or copying from another document, pasting text to the current document, formatting characters and paragraphs. The new release of the control may have added functionality that the version in your VCL implementation does not have.

To allow users to print its content, the RichEdit control is equipped with the Print() method. This method only requires the name of the document that is being printed.

One of the regular actions users perform on a document is to do, undo, or redo something on the application. The ability to undo an action is already implemented in the original version of the rich edit control. To all a user to redo an action, you can send an EM_REDO message to the control using the SendMessage() function. The wParam and the lParam parameters are not used and must be passed as 0.

Practical Learning Practical Learning: Implementing Rich Text Messages

  1. Display the ActionList Editor and, on the left frame, click Dialog. On the right frame, click PrintDlg. On the Object Inspector, click the Events tab and double-click the event side of the OnAccept field
  2. Implement it as follows:
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::PrintDlg1Accept(TObject *Sender)
    {
    	rchEditor->Print(CurrentFileName);
    }
    //---------------------------------------------------------------------------
  3. Display the ActionList Editor. In the left frame, click Edit. In the right frame, double-click EditRedo and implement its OnExecute() event as follows:
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::EditRedoExecute(TObject *Sender)
    {
    	SendMessage(rchEditor->Handle, EM_REDO, 0, 0);
    }
    //---------------------------------------------------------------------------
  4. On the main menu, click File -> New -> Other… In the New Items dialog box, click the Dialogs tab. Click Standard Dialog (Vertical) and click OK
  5. On the Object Inspector, change its Name to dlgFont and change its Caption to Font
  6. Save it as Font
  7. Test the application and return to Bcb

Text Formatting

Advanced text formatting on a Microsoft rich edit control is performed using the CHARFORMAT or the CHARFORMAT2 structures. Because we are interested in the latest features, we will use CHARFORMAT2. It is defined as follows:

typedef struct _charformat2 {
	UINT cbSize;
	DWORD dwMask;
	DWORD dwEffects;
	LONG yHeight;
	LONG yOffset;
	COLORREF crTextColor;
	BYTE bCharSet;
	BYTE bPitchAndFamily;
	TCHAR szFaceName[LF_FACESIZE];
	WORD wWeight;
	SHORT sSpacing;
	COLORREF crBackColor;
	LCID lcid;
	DWORD dwReserved;
	SHORT sStyle;
	WORD wKerning;
	BYTE bUnderlineType;
	BYTE bAnimation;
	BYTE bRevAuthor;
	BYTE bReserved1;
} CHARFORMAT2;

To use this structure, declare a variable of it and use its cbSize member variable to specify its size. Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
	CHARFORMAT2 cfm2;

	cfm2.cbSize = sizeof(CHARFORMAT2);
}
//---------------------------------------------------------------------------

Once the compiler is aware of the size of the structure, use the dwMask member variable to specify the type of formatting you want to perform. Formatting examples include all caps, bold, italic, subscript, etc.

After selecting the type of formatting that will applied, initialize the dwEffects member variable to the corresponding format. (Microsoft has highly improved the documentation on the RichEdit control libraries so much that, to save space on the book, we will not repeat that documentation here. Instead, we will provide examples).

Practical Learning Practical Learning: Formatting Text

  1. Display the main form. Double-click ImageList1. From the resources that accompany this book, add the AllCaps,
  2. Right-click the bottom toolbar and click Separator
  3. Right-click it again and click New Button. Set its ImageIndex to 29 (AllCaps). Change its Name to btnAllCaps. Double-click it again and implement its OnClick event as follows:
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::btnAllCapsClick(TObject *Sender)
    {
    	Richedit::CHARFORMAT2 cfm2;
    
    	cfm2.cbSize = sizeof(cfm2);
    	cfm2.dwMask = CFM_ALLCAPS;
    	cfm2.dwEffects = CFE_ALLCAPS;
    	SendMessage(RichEdit1->Handle, EM_SETCHARFORMAT,
    	static_cast<WPARAM>(SCF_SELECTION),
    	reinterpret_cast<LPARAM>(&cfm2));
    }
    //---------------------------------------------------------------------------

    4. Test the application and return to Bcb

Paragraph Formatting

Besides the regular techniques provided by the TRichEdit class, paragraph formatting on a rich edit control can be performed using the PARAFORMAT or the PARAFORMAT2 structures. Because everything available in the first is implemented in the second, we will use the PARAFORMAT2 structure. It is defined as follows:

typedef struct _paraformat { 
	UINT cbSize; 
	DWORD dwMask; 
	WORD wNumbering; 
	WORD wEffects; 
	LONG dxStartIndent; 
	LONG dxRightIndent; 
	LONG dxOffset; 
	WORD wAlignment; 
	SHORT cTabCount; 
	LONG rgxTabs[MAX_TAB_STOPS]; 
	LONG dySpaceBefore; 
	LONG dySpaceAfter; 
	LONG dyLineSpacing; 
	SHORT sStyle; 
	BYTE bLineSpacingRule; 
	BYTE bOutlineLevel; 
	WORD wShadingWeight; 
	WORD wShadingStyle;
	WORD wNumberingStart; 
	WORD wNumberingStyle; 
	WORD wNumberingTab; 
	WORD wBorderSpace; 
	WORD wBorderWidth; 
	WORD wBorders; 
} PARAFORMAT2; 
#define wEffects wReserved

To use it, declare a PARAFORMAT2 variable and use the cbSize member variable to specify the size of the structure. After this, use the dwMask to specify the type of formatting you want to perform.

Practical Learning Practical Learning: Formatting Paragraphs

  1. Make sure the Editor project you created is still opened. Display the main form and double-click MainMenu1
  2. In the Menu Designer, click Format and click the first empty box under the Format menu. On the Object Inspector, click Caption and type &Paragraph...
  3. Set the ImageIndex of the Paragraph menu item to 8 and close the Menu Designer
  4. To use one of the dialog templates, on the main menu of C++ Builder, click File -> New -> Other... On the New Items dialog box, click Dialogs and double-click Standard Dialog (Horizontal)
  5. While the new dialog box is still selected, on the Object Inspector, change its Caption value to Paragraph and change its Name property to dlgParagraph
  6. Save it as Paragraph
  7. From the Standard tab of the Component Palette, add three labels with captions as &Left:, &Right:, and &First Line:
  8. Add an edit box on the right side of each of the last three labels. Change their names to edtLeft, edtRight, edtFirstLine. Position and resize the controls as you see fit:

  9. Right-click anywhere on the dialog box and click Tab Order. Arrange the controls sequence in the following order: edtLeft, edtRight, edtFirstLine, OKBtn, and CancelBtn:

  10. While the dlgParagraph form is still displaying, on the main menu, click File -> Include Unit Hdr... In the Use Unit dialog box, make sure Main is selected and click OK
  11. Click the Events tab of the Object Inspector and double-click the empty field of OnActivate
  12. Implement the event as follows:
    //---------------------------------------------------------------------
    void __fastcall TdlgParagraph::FormActivate(TObject *Sender)
    {
    	// Make sure the values of the edit boxes
    	// reflect the indentation of the paragraph in the background
    	edtLeft->Text = frmMain->rchEditor->Paragraph->LeftIndent;
    	edtRight->Text = frmMain->rchEditor->Paragraph->RightIndent;
    	edtFirstLine->Text = frmMain->rchEditor->Paragraph->FirstIndent;
    }
    //---------------------------------------------------------------------
  13. On the View toolbar, click the View Form button
  14. On the View Form dialog box, click frmMain and click OK
  15. On the main menu, click File -> Include Unit Hdr...
  16. In the Use Unit dialog box, make sure Paragraph is selected and click OK
  17. On the main menu of the form, click Format -> Paragraph... and implement its OnClick event as follows:
    //---------------------------------------------------------------------------
    void __fastcall TForm1::Paragraph1Click(TObject *Sender)
    {
    	dlgParagraph->ShowModal();
    }
    //---------------------------------------------------------------------------
  18. Display the ActionList Editor. In the left frame, click Format. In the right frame, double-click FormatNbr, and implement its OnExecute() event as follows:
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::ToolButton26Click(TObject *Sender)
    {
    	PARAFORMAT2 pfm2;
    
    	pfm2.cbSize = sizeof(pfm2);
    	pfm2.dwMask = PFM_NUMBERING;
    	pfm2.wNumbering = 3;
    
    	SendMessage(RichEdit1->Handle, EM_SETPARAFORMAT,
    			0, reinterpret_cast<LPARAM>(&pfm2));
    }
    //---------------------------------------------------------------------------
  19. Test your application and return to Bcb
  20. Save your project

The Find Dialog Box

 

Introduction

When facing large text, users sometimes need to find a word, a group of words, or a section inside of the displayed text. Microsoft Windows provides a dialog box that can help with such a task. It is the Find dialog box:

Searching for Text

To search for a word or a group of words in a text, the user must first have a searchable document. To proceed, the user would call the Find dialog which can be available from a toolbar button or a menu item. The Find dialog box is equipped with a text box called Find What. In this text box, the user can type a word or an expression. She can specify whether the search should look for the whole word or not. This is set using the Match Whole Word Only check box. If she types a word or an expression and is sensitive to the case of characters, she can make sure that the dialog box would need to match only the word or expression with letter exactly as typed, in uppercase and lowercase. After specifying the options, the user can click the Find Next button. If a match is found, the found match should be selected and highlighted in the text in the background. The user can continue searching down the text by clicking Find Next continuously. Once all matches have been found, or if no match was found, a message box should let the user know.

By default, the search proceeds from the top section of the document to the bottom. The user can reverse this direction by clicking the Up radio button in the Direction section.

The Find dialog box is modeless, which allows the user to work on the background text, such as the found match, without closing the dialog box. After performing a search, the user can click Cancel.

Word and Expression Search

The Find dialog box is represented in the VCL by the TFindDialog class. To make this dialog box available at design time, from the Dialogs property page of the Component Palette, you can click FindDialog and click the Form.

The word or expression to find is a string known as FindText. If you have a default text you want to display in the Find What text box when the dialog box comes up, you can provide it in the FindText property of the Object Inspector. In the same way, when the user is performing a search, the text specified in the Find What text box is represented as the FindText value.

Because the Find dialog box is modeless, the user can decide to keep it open while she is working in the background text. Sometimes, the dialog box can be obstructing. Fortunately, with code, you can control the location of this dialog box to make it less annoying. The location of the dialog box is set or controlled using the Position property. The Position is a TPoint object that specifies the vertical measurement from the top of the document to the top border of the dialog box, and the horizontal measurement from the left border of the document to the left border of the Find dialog box. If you want to control only the vertical distance of the dialog box from the top border of the document, you can specify a natural number as the Top property. The TFindDialog object provides options to control the availability of the check boxes and radio buttons of the dialog box.

Practical Learning Practical Learning: Allowing Text Search

  1. Display the ActionList Editor. In the left frame, click Search. In the right frame, click SearchFind1
  2. On the Object Inspector, in the Properties tab, expand Dialog and expand Options. Set the frFindNext property to true
  3. click Events. Under the expanded Dialog, double-click the right empty field to OnFind and implement its event as follows:
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::SearchFind1FindDialogFind(TObject *Sender)
    {
    	int MatchPos, StartPos, EndPos;
    
    	if( rchEditor->SelLength )
    		StartPos = rchEditor->SelStart + rchEditor->SelLength;
    	else
    		StartPos = 0;
    
    	EndPos = rchEditor->Text.Length() - StartPos;
    	MatchPos = rchEditor->FindText(SearchFind1->Dialog->FindText,
    					StartPos,
    					EndPos,
    					TSearchTypes() << stMatchCase);
    
    	if( MatchPos != -1 )
    	{
    		rchEditor->SelStart = MatchPos;
    		rchEditor->SelLength = SearchFind1->Dialog->FindText.Length();
    	}
    }
    //---------------------------------------------------------------------------
  4. Save the project. Test the application and return to Bcb

The Replace Dialog Box

 

Introduction

Another regular operation users perform on text is to find a word or an expression and replace it with another word or an expression. This is possible through the use of the Replace dialog box:

To replace a word or an expression, the user first displays the Replace dialog. It is equipped with two text boxes. In the Find What text box, the user would type the word or the expression that should be searched in the whole text. In the Replace With edit box, the user can type another word or an expression that would replace a possible match of the Find What string. The user can proceed as if she were using the Find dialog box and click the Find Next button to find a match in the document. If a match is found, the user can click Replace to replace the matched word or expression. If the Replace edit box is empty, the match would be deleted. On the other hand, if the Replace With edit box contains a string, upon clicking Replace, the matched text would be replaced by the Replace With string. After the text has been found or replaced, the dialog box would attempt to find the next match. If the user wants to replace all occurrences of the Find What string, she can click Replace All.

At any time, the user can click Cancel to dismiss the dialog box or continue working in the background text without necessarily closing the dialog because it is modeless.

Making Text Replacement Possible

To make it possible for users to find and replace text in a document, the VCL provides the TReplaceDialog class, which is represented by the ReplaceDialog object ReplaceDialog from the Component Palette. Therefore, to make replacement of text available, from the Dialogs tab, double-click ReplaceDialog ReplaceDialog.

The ReplaceDialog object uses the same properties as the FindDialog object and adds to the FindDialog options. Because of its functionality, the FindReplace control adds the ReplaceText property. This carries the string that would replace the possible found text.

Practical Learning Practical Learning: Allowing Text Replacement

  1. Display the ActionList Editor. In the right frame, click SearchReplace1
  2. In the Properties tab of the Object Inspector, under the expanded Options, set the frFindNext to true
  3. Click the Events tab and double-click the empty field on the right side of OnFind
  4. Implement the event as follows:
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::SearchReplace1ReplaceDialogFind(TObject *Sender)
    {
    	int MatchPos, StartPos, EndPos;
    
    	if( rchEditor->SelLength )
    		StartPos = rchEditor->SelStart + rchEditor->SelLength;
    	else
    		StartPos = 0;
    
    	EndPos = rchEditor->Text.Length() - StartPos;
    	MatchPos = rchEditor->FindText(SearchReplace1->Dialog->FindText,
    					StartPos,
    					EndPos,
    					TSearchTypes() << stMatchCase);
    
    	if( MatchPos != -1 )
    	{
    		rchEditor->SetFocus();
    		rchEditor->SelStart = MatchPos;
    		rchEditor->SelLength = SearchReplace1->Dialog->FindText.Length();
    	}
    }
    //---------------------------------------------------------------------------
  5. On the Object Inspector, in the Events tab, double-click the box on the right side of OnReplace and implement its event as follows:
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::SearchReplace1ReplaceDialogReplace(TObject *Sender)
    {
    	// Find out if the Find What text is selected in the rich edit control
    	if( rchEditor->SelText == SearchReplace1->Dialog->FindText )
    	{
    		// Since a match was found, get ready to replace it with the content
    		// of the Replace With edit box
    
    		// First find out if the user clicked the Replace button
    		if( SearchReplace1->Dialog->Options.Contains(frReplace) )
    		{
    			// Since the user clicked Replace, replace only the selection
    			rchEditor->SelText = SearchReplace1->Dialog->ReplaceText;
    			// Perform a new search
    			SearchReplace1ReplaceDialogFind(Sender);
    		}
    		// Find out if the user clicked Replace All instead
    		else if( SearchReplace1->Dialog->Options.Contains(frReplaceAll) )
    		{
    			// Since the user clicked Replace All, replace all occurrences
    			// of the Find What edit box with the Replace With edit box
    			do {
    				// Find an occurrence and replace it
    				rchEditor->SelText = SearchReplace1->Dialog->ReplaceText;
    				// Find another occurrence before repeating
    				SearchReplace1ReplaceDialogFind(Sender);
    			} while( !rchEditor->SelText.IsEmpty() );
    
    			// Let the user know that all occurrences have been replaced
    			ShowMessage("No more text to replace");
    		}
    	}
    	else // If no text is selected or none was matched, let the user know
    		ShowMessage("No text to replace");
    }
    //---------------------------------------------------------------------------
  6. Save the project. Test the application and return to Bcb
Home Copyright © 2005-2012, FunctionX, Inc.