Creating a functional — even fancy — word processor based on the RichTextBox control is unexpectedly simple. The challenge is to provide a convenient interface that lets the user select text, apply attributes and styles to it, and then set the control’s properties accordingly. The RichTextBoxPad sample application of this section does just that.
The RichTextBoxPad application (refer to Figure 4.16 in the section “RichTextBox control“) is based on the TextEditor project developed the section “The TextEditor Project“. It contains the same text-editing commands and some additional text formatting commands that can be implemented only with the RichTextBox control; for example, it allows you to apply multiple fonts and styles to the text, and, of course, multiple Undo/Redo operations.
The two TrackBar controls above the RichTextBox control manipulate the indentation of the text. We already explored this arrangement in the discussion of the “TrackBar control in VB 2008” earlier in this chapter, but let’s review the operation of the two controls again. Each TrackBar control has a width of 816 pixels, which is equivalent to 8.5 inches on a monitor that has a resolution of 96 dots per inch (dpi).
The height of the TrackBar controls is 42 pixels, but unfortunately they can’t be made smaller. The Minimum property of both controls is 0, and the Maximum property is 16. The TickFrequency is 1. With these values, you can adjust the indentation in steps of 1/2 inch. Set the Maximum property to 32 and you’ll be able to adjust the indentation in steps of 1/4 inch. It’s not the perfect interface, as it’s built for A4 pages in portrait orientation only. You can experiment with this interface to build an even more functional word processor.
Each time the user slides the top TrackBar control, the code sets the SelectionIndent property to the proper percentage of the control’s width. Because the SelectionHangingIndent includes the value of the SelectionIndent property, it also adjusts the setting of the SelectionHangingIndent property. Listing 4.22 is the code that’s executed when the upper TrackBar control is scrolled.
Listing 4.22: Setting the SelectionIndent Property
Private Sub TrackBar1 Scroll(...) _ Handles TrackBar1.Scroll Editor.SelectionIndent = Convert.ToInt32( _ Editor.Width * _ (TrackBar1.Value / TrackBar1.Maximum)) Editor.SelectionHangingIndent = Convert.ToInt32( _ Editor.Width * _ (TrackBar2.Value / TrackBar2.Maximum) - _ Editor.SelectionIndent) End SubCode language: VB.NET (vbnet)
Editor is the name of the RichTextBox control on the form. The code sets the control’s indentation to the same percentage of the control’s width, as indicated by the value of the top TrackBar control. It also does the same for the SelectionHangingIndent property, which is controlled by the lower TrackBar control. If the user has scrolled the lower TrackBar control, the code sets the RichTextBox control’s SelectionHangingIndent property in the event handler, as presented in Listing 4.23.
Listing 4.23: Setting the SelectionHangingIndent Property
Private Sub TrackBar2 Scroll(...) _ Handles TrackBar2.Scroll Editor.SelectionHangingIndent = _ Convert.ToInt32(Editor.Width * _ (TrackBar2.Value / TrackBar2.Maximum) - _ Editor.SelectionIndent) End SubCode language: VB.NET (vbnet)
Enter a few lines of text in the control, select one or more paragraphs, and check out the operation of the two sliders.
The Scroll events of the two TrackBar controls adjust the text’s indentation. The opposite action must take place when the user rests the pointer on another paragraph: The sliders’ positions must be adjusted to reflect the indentation of the selected paragraph. The selection of a new paragraph is signaled to the application by the SelectionChanged event. The statements of Listing 4.24, which are executed from within the SelectionChanged event, adjust the two slider controls to reflect the indentation of the text.
Listing 4.24: Setting the Slider Controls
Private Sub Editor SelectionChanged(...) _ Handles Editor.SelectionChanged If Editor.SelectionIndent = Nothing Then TrackBar1.Value = TrackBar1.Minimum TrackBar2.Value = TrackBar2.Minimum Else TrackBar1.Value = Convert.ToInt32( _ Editor.SelectionIndent * _ TrackBar1.Maximum / Editor.Width) TrackBar2.Value = Convert.ToInt32( _ (Editor.SelectionHangingIndent / _ Editor.Width) * _ TrackBar2.Maximum + TrackBar1.Value) End If End SubCode language: VB.NET (vbnet)
If the user selects multiple paragraphs with different indentations, the SelectionIndent property returns Nothing. The code examines the value of this property and, if it’s Nothing, it moves both controls to the left edge. This way, the user can slide the controls and set the indentations for multiple paragraphs. Some applications make the handles gray to indicate that the selected text doesn’t have uniform indentation, but unfortunately you can’t gray the sliders and keep them enabled. Of course, you can always design a custom control. This wouldn’t be a bad idea, especially if you consider that the TrackBar controls are too tall for this type of interface and can’t be made very narrow (as a result, the interface of the RichTextBoxPad application isn’t very elegant).
The File Menu
The RichTextBoxPad application’s File menu contains the usual Open, Save, and Save As commands, which are implemented with the control’s LoadFile and SaveFile methods. Listing 4.25 shows the implementation of the Open command in the File menu.
Listing 4.25: The Open Command
Private Sub OpenToolStripMenuItem Click(...) _ Handles OpenToolStripMenuItem.Click If DiscardChanges() Then OpenFileDialog1.Filter = _ "RTF Files|*.RTF|DOC Files|*.DOC|" & _ "Text Files|*.TXT|All Files|*.*" If OpenFileDialog1.ShowDialog() = _ DialogResult.OK Then fName = OpenFileDialog1.FileName Editor.LoadFile(fName) Editor.Modified = False End If End If End SubCode language: VB.NET (vbnet)
The fName variable is declared on the form’s level and holds the name of the currently open file. This variable is set every time a new file is successfully opened and it’s used by the Save command to automatically save the open file, without prompting the user for a filename.
DiscardChanges() is a function that returns a Boolean value, depending on whether the control’s contents can be discarded. The function examines the Editor control’s Modified property. If True, it prompts users as to whether they want to discard the edits. Depending on the value of the Modified property and the user response, the function returns a Boolean value. If the DiscardChanges() function returns True, the program goes on and opens a new document. If the function returns False, the program aborts the operation to give the user a chance to save the document. Listing 4.26 shows the DiscardChanges() function.
Listing 4.26: The DiscardChanges() Function
Function DiscardChanges() As Boolean If Editor.Modified Then Dim reply As MsgBoxResult reply = MsgBox( _ "Text hasn’t been saved. Discard changes?", _ MsgBoxStyle.YesNo) If reply = MsgBoxResult.No Then Return False Else Return True End If Else Return True End If End FunctionCode language: VB.NET (vbnet)
The Modified property becomes True after typing the first character and isn’t reset back to False. The RichTextBox control doesn’t handle this property very intelligently and doesn’t reset it to False even after saving the control’s contents to a file. The application’s code sets the Editor.Modified property to False after creating a new document, as well as after saving the current document.
The Save As command (see Listing 4.27) prompts the user for a filename and then stores the Editor control’s contents to the specified file. It also sets the fName variable to the file’s path, so that the Save command can use it.
Listing 4.27: The Save As Command
Private Sub SaveAsToolStripMenuItem Click(...) _ Handles SaveAsToolStripMenuItem.Click SaveFileDialog1.Filter = _ "RTF Files|*.RTF|DOC Files" & _ "|*.DOC|Text Files|*.TXT|All Files|*.*" SaveFileDialog1.DefaultExt = "RTF" If SaveFileDialog1.ShowDialog() = DialogResult.OK Then fName = SaveFileDialog1.FileName Editor.SaveFile(fName) Editor.Modified = False End If End SubCode language: VB.NET (vbnet)
The Save command’s code is similar, only it doesn’t prompt the user for a filename. It calls the SaveFile method, passing the fName variable as an argument. If the fName variable has no value (in other words, if a user attempts to save a new document by using the Save command), the code activates the event handler of the Save As command automatically and resets the control’s Modified property to False. Listing 4.28 shows the code behind the Save command.
Listing 4.28: The Save Command
Private Sub SaveToolStripMenuItem Click(...) _ Handles SaveToolStripMenuItem.Click If fName <> "" Then Editor.SaveFile(fName) Editor.Modified = False Else SaveAsToolStripMenuItem Click(sender, e) End If End SubCode language: VB.NET (vbnet)
The Edit Menu
The Edit menu contains the usual commands for exchanging data through the Clipboard (Copy, Cut, Paste), Undo/Redo commands, and a Find command to invoke the Search & Replace dialog box. All the commands are almost trivial, thanks to the functionality built into the control. The basic Cut, Copy, and Paste commands call the RichTextBox control’s Copy, Cut, and Paste methods to exchange data through the Clipboard. Listing 4.29 shows the implementation of the Paste command.
Listing 4.29: The Paste Command
Private Sub PasteToolStripMenuItem Click(...) _ Handles PasteToolStripMenuItem.Click Try Editor.Paste() Catch exc As Exception MsgBox( _ "Can't paste current clipboard's contents") End Try End SubCode language: VB.NET (vbnet)
As you may recall from the discussion of the Paste command, we can’t use the CanPaste method because it’s not trivial; you have to handle each data type differently. By using an exception handler, we allow the user to paste all types of data that the RichTextBox control can accept, and display a message when an error occurs.
The Undo and Redo commands of the Edit menu are coded as follows. First, we display the name of the action to be undone or redone in the Edit menu. When the Edit menu is selected, the DropDownOpened event is fired. This event takes place before the Click event, so I inserted a few lines of code that read the name of the most recent action that can be undone or redone and print it next to the Undo or Redo command’s caption. If there’s no such action, the program will disable the corresponding command. Listing 4.30 is the code that’s executed when the Edit menu is dropped.
Listing 4.30: Setting the Captions of the Undo and Redo Commands
Private Sub EditToolStripMenuItem DropDownOpened(...) _ Handles EditToolStripMenuItem.DropDownOpened If Editor.UndoActionName <> "" Then UndoToolStripMenuItem.Text = _ "Undo " & Editor.UndoActionName UndoToolStripMenuItem.Enabled = True Else UndoToolStripMenuItem.Text = "Undo" UndoToolStripMenuItem.Enabled = False End If If Editor.RedoActionName <> "" Then RedoToolStripMenuItem.Text = _ "Redo" & Editor.RedoActionName RedoToolStripMenuItem.Enabled = True Else RedoToolStripMenuItem.Text = "Redo" RedoToolStripMenuItem.Enabled = False End If End SubCode language: VB.NET (vbnet)
When the user selects one of the Undo or Redo commands, the code simply calls the appropriate method from within the menu item’s Click event handler, as shown in Listing 4.31.
Listing 4.31: Undoing and Redoing Actions
Private Sub RedoToolStripMenuItem Click(...) _ Handles RedoToolStripMenuItem.Click If Editor.CanRedo Then Editor().Redo() End Sub Private Sub UndoToolStripMenuItem Click(...) _ Handles UndoToolStripMenuItem.Click If Editor.CanUndo Then Editor.Undo() End SubCode language: VB.NET (vbnet)
Calling the CanUndo and CanRedo method is unnecessary; if the corresponding action can’t be performed, the two menu items will be disabled, but an additional check does no harm.
The Format Menu
The commands of the Format menu control the alignment and the font attributes of the current selection. The Font command displays the Font dialog box and then assigns the font selected by the user to the current selection. Listing 4.32 shows the code behind the Font command.
Listing 4.32: The Font Command
Private Sub FontToolStripMenuItem Click(...) _ Handles FontToolStripMenuItem.Click If Not Editor.SelectionFont Is Nothing Then FontDialog1.Font = Editor.SelectionFont Else FontDialog1.Font = Nothing End If FontDialog1.ShowApply = True If FontDialog1.ShowDialog() = DialogResult.OK Then Editor.SelectionFont = FontDialog1.Font End If End SubCode language: VB.NET (vbnet)
Notice that the code preselects a font in the dialog box, which is the font of the current selection. If the current selection isn’t formatted with a single font, no font is preselected.
To enable the Apply button of the Font dialog box, set the control’s ShowApply property to True and insert the following statement in its Apply event handler:
Private Sub FontDialog1 Apply( _ ByVal sender As Object, _ ByVal e As System.EventArgs) _ Handles FontDialog1.Apply Editor.SelectionFont = FontDialog1.Font End SubCode language: VB.NET (vbnet)
The options of the Align menu set the RichTextBox control’s SelectionAlignment property to different members of the HorizontalAlignment enumeration. The Align > Left command, for example, is implemented with the following statement:
Editor.SelectionAlignment = HorizontalAlignment.LeftCode language: VB.NET (vbnet)
The Search & Replace Dialog Box
The Find command in the Edit menu opens the dialog box shown in Figure 4.19, which performs search-and-replace operations (whole-word or case-sensitive match, or both). The Search & Replace form (it’s the frmFind form in the project) has its TopMost property set to True, so that it remains visible while it’s open, even if it doesn’t have the focus. The code behind the buttons on this form is quite similar to the code for the Search & Replace dialog box of the TextEditor Project application, with one basic difference: the RichTextBoxPad project’s code uses the RichTextBox control’s Find method; the simple TextBox control doesn’t provide an equivalent method and we ha\d to use the methods of the String class to perform the same operations. The Find method of the RichTextBox control performs all types of searches, and some of its options are not available with the IndexOf method of the String class.
Figure 4.19 – The Search & Replace dialog box of the RichTextBoxPad application
To invoke the Search & Replace dialog box, the code calls the Show method of the frmFind form, as discussed in Chapter 6, via the following statement:
frmFind.Show()Code language: VB.NET (vbnet)
The Find method of the RichTextBox control allows you to perform case-sensitive or case-insensitive searches, as well as search for whole words only. These options are specified through an argument of the RichTextBoxFinds type. The SetSearchMode() function (see Listing 4.33) examines the settings of the two check boxes at the bottom of the form and sets the Find method’s search mode.
Listing 4.33: Setting the Search Options
Function SetSearchMode() As RichTextBoxFinds Dim mode As RichTextBoxFinds = _ RichTextBoxFinds.None If chkCase.Checked = True Then mode = mode Or RichTextBoxFinds.MatchCase End If If chkWord.Checked = True Then mode = mode Or RichTextBoxFinds.WholeWord End If Return mode End FunctionCode language: VB.NET (vbnet)
The Click event handlers of the Find and Find Next buttons call this function to retrieve the constant that determines the type of search specified by the user on the form. This value is then passed to the Find method. Listing 4.34 shows the code behind the Find and Find Next buttons.
Listing 4.34: The Find and Find Next Commands
Private Sub bttnFind Click(...) _ Handles bttnFind.Click Dim wordAt As Integer Dim srchMode As RichTextBoxFinds srchMode = SetSearchMode() wordAt = frmEditor.Editor.Find( _ txtSearchWord.Text, 0, srchMode) If wordAt = -1 Then MsgBox("Can't find word") Exit Sub End If frmEditor.Editor.Select(wordAt, _ txtSearchWord.Text.Length) bttnFindNext.Enabled = True bttnReplace.Enabled = True bttnReplaceAll.Enabled = True frmEditor.Editor.ScrollToCaret() End Sub Private Sub bttnFindNext Click(...) _ Handles bttnFindNext.Click Dim selStart As Integer Dim srchMode As CompareMethod srchMode = SetSearchMode() selStart = frmEditor.Editor.Find( _ txtSearchWord.Text, _ frmEditor.Editor.SelectionStart + 2, _ srchMode) If selStart = -1 Then MsgBox("No more matches") Exit Sub End If frmEditor.Editor.Select( _ selStart, txtSearchWord.Text.Length) frmEditor.Editor.ScrollToCaret() End SubCode language: VB.NET (vbnet)
Notice that both event handlers call the ScrollToCaret method to force the selected text to become visible — should the Find method locate the desired string outside the visible segment of the text.