The TextEditor application, shown in Figure 4.2, demonstrates most of the TextBox control’s properties and methods described so far. TextEditor is a basic text editor that you can incorporate into your programs and customize for special applications. The TextEditor project’s main form is covered by a TextBox control, whose size is adjusted every time the user resizes the form. This feature doesn’t require any programming — just set the Dock property of the TextBox control to Fill.
The name of the application’s main form is frmTextEditor, and the name of the Find & Replace dialog box is frmFind. You can design the two forms as shown in the figures of this chapter, or open the TextEditor project. To design the application’s interface from scratch, place a MenuStrip control on the form and dock it to the top of the form. Then place a TextBox control on the main form, name it txtEditor, and set the following properties: Multiline to True, MaxLength to 0 (to edit text documents of any length), HideSelection to False (so that the selected text remains highlighted even when the main form doesn’t have the focus), and Dock to Fill, so that it will fill the form.
Figure 4.2 – VB.NET TextEditor Project Example
The menu bar of the form contains all the commands you’d expect to find in text-editing applications; they’re listed in Table 4.1.
Table 4.1 – The TextEditor Form’s Menu
|File||New||Clears the text|
|Open||Loads a new text file from disk|
|Save||Saves the text to its file on disk|
|Save As||Saves the text with a new filename on disk|
|Prints the text|
|Exit||Terminates the application|
|Edit||Undo/Redo||Undoes/redoes the last edit operation|
|Copy||Copies selected text to the Clipboard|
|Cut||Cuts the selected text|
|Paste||Pastes the Clipboard's contents to the editor|
|Select All||Selects all text in the control|
|Find & Replace||Displays a dialog box with Find and Replace options|
|Process||Convert To Upper||Converts selected text to uppercase|
|Convert To Lower||Converts selected text to lowercase|
|Number Lines||Numbers the text lines|
|Format||Font||Sets the text's font, size, and attributes|
|Page Color||Sets the control's background color|
|Text Color||Sets the color of the text|
|WordWrap||Toggle menu item that turns text wrapping on and off|
The File menu commands are implemented with the Open and Save As dialog boxes, the Font command with the Font dialog box, and the Color command with the Color dialog box. These dialog boxes are discussed in the following chapters, and as you’ll see, you don’t have to design them yourself. All you have to do is place a control on the form and set a few properties; the Framework takes it from there. The application will display the standard Open File/Save File/Font/Color dialog boxes, in which the user can select or specify a filename or select a font or color. Of course, we’ll provide a few lines of code to actually move the text into a file (or read it from a file and display it on the control), change the control’s background color, and so on.
The Editing Commands
The options on the Edit menu move the selected text to and from the Clipboard. For the TextEditor application, all you need to know about the Clipboard are the SetText method, which places the currently selected text on the Clipboard, and the GetText method, which retrieves information from the Clipboard (The Copy, Cut, and Paste operations can be used to exchange text with any other application).
The Copy command, for example, is implemented with a single line of code (txtEditor is the name of the TextBox control). The Cut command does the same, and it also clears the selected text. The code for these and for the Paste command, which assigns the contents of the Clipboard to the current selection, is presented in Listing 4.2.
Listing 4.2: The Cut, Copy, and Paste Commands
Private Sub EditCopyItem Click(...) Handles EditCopyItem.Click If txtEditor.SelectionLength > 0 Then Clipboard.SetText(txtEditor.SelectedText) End If End Sub Private Sub EditCutItem Click(...) Handles EditCutItem.Click Clipboard.SetText(txtEditor.SelectedText) txtEditor.SelectedText = "" End Sub Private Sub EditPasteItem Click(...) Handles EditPasteItem.Click If Clipboard.ContainsText Then txtEditor.SelectedText = Clipboard.GetText End If End SubCode language: VB.NET (vbnet)
If no text is currently selected, the Clipboard’s text is pasted at the pointer’s current location. If the Clipboard contains a bitmap (placed there by another application) or any other type of data that the TextBox control can’t handle, the paste operation will fail; that’s why we handle the Paste operation with an If statement. You could provide some hint to the user by including an Else clause that informs them that the data on the Clipboard can’t be used with a text-editing application.
The Process and Format Menus
The commands of the Process and Format menus are straightforward. The Format menu commands open the Font or Color dialog box and change the control’s Font, ForeColor, and BackColor properties. You will learn how to use these controls in the following chapter. The Upper Case and Lower Case commands of the Process menu are also trivial: they select all the text, convert it to uppercase or lowercase, respectively, and assign the converted text to the control’s SelectedText property with the following statements:
txtEditor.SelectedText = txtEditor.SelectedText.ToLower txtEditor.SelectedText = txtEditor.SelectedText.ToUpperCode language: VB.NET (vbnet)
Notice that the code uses the SelectedText property to convert only the selected text, not the entire document. The Number Lines command inserts a number in front of each text line and demonstrates how to process the individual lines of text on the control. However, it doesn’t remove the line numbers, and there’s no mechanism to prevent the user from editing the line numbers or inserting/deleting lines after they have been numbered. Use this feature to create a numbered listing or to number the lines of a file just before saving it or sharing it with another user. Listing 4.3 shows the Number Lines command’s code and demonstrates how to iterate through the TextBox control’s Lines array.
Listing 4.3: The Number Lines Command
Private Sub ProcessNumberLinesItem Click(...) Handles ProcessNumberLines.Click Dim iLine As Integer Dim newText As New System.Text.StringBuilder() For iLine = 0 To txtEditor.Lines.Length - 1 newText.Append((iLine + 1).ToString & vbTab & _ txtEditor.Lines(iLine) & vbCrLf) Next txtEditor.SelectAll() Clipboard.SetText(newText.ToString) txtEditor.Paste() End SubCode language: VB.NET (vbnet)
This event handler uses a StringBuilder variable. The StringBuilder class, which is discussed in detail in Chapter, “Designing Custom Windows Controls,” is equivalent to the String class; it exposes similar methods and properties, but it’s much faster at manipulating dynamic strings than the String class.
Search and Replace Operations
The last option in the Edit menu — and the most interesting — displays a Find & Replace dialog box (shown in Figure 4.2). This dialog box works like the similarly named dialog box of Microsoft Word and many other Windows applications. The buttons in the Find & Replace dialog box are relatively self-explanatory:
Find – The Find command locates the first instance of the specified string in the text after the cursor location. If a match is found, the Find Next, Replace, and Replace All buttons are enabled.
Find Next – This command locates the next instance of the string in the text. Initially, this button is disabled; it’s enabled only after a successful Find operation.
Replace – This replaces the current selection with the replacement string and then locates the next instance of the same string in the text. Like the Find Next button, it’s disabled until a successful Find operation occurs.
Replace All – This replaces all instances of the string specified in the Search For box with the string in the Replace With box.
Design a form like the one shown in Figure 4.2 and set its TopMost property to True. We want this form to remain on top of the main form, even when it doesn’t have the focus.
Whether the search is case-sensitive or not depends on the status of the Case Sensitive CheckBox control. If the string is found in the control’s text, the program highlights it by selecting it. In addition, the program calls the TextBox control’s ScrollToCaret method to bring the selection into view. The Find Next button takes into consideration the location of the pointer and searches for a match after the current location. If the user moves the pointer somewhere else and then clicks the Find Next button, the program will locate the first instance of the string after the current location of the pointer — and not after the last match. Of course, you can always keep track of the location of each match and continue the search from this location. The Find button executes the code shown in Listing 4.4.
Listing 4.4: The Find Button
Private Sub bttnFind Click(...) Handles bttnFind.Click Dim selStart As Integer If chkCase.Checked = True Then selStart = _ frmTextEditor.txtEditor.Text.IndexOf( _ searchWord.Text, StringComparison.Ordinal) Else selStart = _ frmTextEditor.txtEditor.Text.IndexOf( _ searchWord.Text, _ StringComparison.OrdinalIgnoreCase) End If If selStart = -1 Then MsgBox("Can't find word") Exit Sub End If frmTextEditor.txtEditor.Select( _ selStart, searchWord.Text.Length) bttnFindNext.Enabled = True bttnReplace.Enabled = True bttnReplaceAll.Enabled = True frmTextEditor.txtEditor.ScrollToCaret() End SubCode language: VB.NET (vbnet)
The Find button examines the value of the chkCase CheckBox control, which specifies whether the search will be case-sensitive and calls the appropriate form of the IndexOf method. The first argument of this method is the string we’re searching for; the second argument is the searchmode, and its value is a member of the StringComparison enumeration: Ordinal for case-sensitive searches and OrdinalIgnoreCase for case-insensitive searches. If the IndexOf method locates the string, the program selects it by calling the control’s Select method with the appropriate arguments. If not, it displays a message. Notice that after a successful Find operation, the Find Next, Replace, and Replace All buttons on the form are enabled.
The code of the Find Next button is the same, but it starts searching at the character following the current selection. This way, the IndexOf method locates the next instance of the same string. Here’s the statement that locates the next instance of the search argument:
selStart = frmTextEditor.txtEditor.Text.IndexOf( _ searchWord.Text, _ frmTextEditor.txtEditor.SelectionStart + 1, _ StringComparison.Ordinal)Code language: VB.NET (vbnet)
The Replace button replaces the current selection with the replacement string and then locates the next instance of the find string. The Replace All button replaces all instances of the search word in the document. Listing 4.5 presents the code behind the Replace and Replace All buttons.
Listing 4.5: The Replace and Replace All Operations
Private Sub bttnReplace Click(...) _ Handles bttnReplace.Click If frmTextEditor.txtEditor.SelectedText <> ”” Then frmTextEditor.txtEditor.SelectedText = replaceWord.Text End If bttnFindNext Click(sender, e) End Sub Private Sub bttnReplaceAll Click(...) _ Handles bttnReplaceAll.Click Dim curPos, curSel As Integer curPos = frmTextEditor.txtEditor.SelectionStart curSel = frmTextEditor.txtEditor.SelectionLength frmTextEditor.txtEditor.Text = _ frmTextEditor.txtEditor.Text.Replace( _ searchWord.Text.Trim, replaceWord.Text.Trim) frmTextEditor.txtEditor.SelectionStart = curPos frmTextEditor.txtEditor.SelectionLength = curSel End SubCode language: VB.NET (vbnet)
The Replace method is case-sensitive, which means that it replaces instances of the search argument in the text that have the exact same spelling as its first argument. For a case-insensitive replace operation, you must write the code to perform consecutive case-insensitive search-and-replace operations. Alternatively, you can use the Replace built-in function to perform case-insensitive searches. Here’s how you’d call the Replace function to perform a case-insensitive replace operation:
Replace(frmTextEditor.txtEditor.Text, searchWord.Text.Trim, _ replaceWord.Text.Trim, , , CompareMethod.Text)Code language: VB.NET (vbnet)
The last, optional, argument determines whether the search will be case-sensitive (CompareMethod.Binary) or case-insensitive (CompareMethod.Text).
The Undo/Redo Commands
The Undo command (shown in Listing 4.6) is implemented with a call to the Undo method. However, because the Undo method works like a toggle, we must also toggle its caption from Undo to Redo (and vice versa) each time the command is activated.
Listing 4.6: The Undo/Redo Command of the EditMenu
Private Sub EditUndoItem Click(...) _ Handles EditUndoItem.Click If EditUndoItem.Text = ”Undo” Then If txtEditor.CanUndo Then txtEditor.Undo() EditUndoItem.Text = ”Redo” End If Else If txtEditor.CanUndo Then txtEditor.Undo() EditUndoItem.Text = ”Undo” End If End If End SubCode language: VB.NET (vbnet)
f you edit the text after an undo operation, you can no longer redo the last undo operation. This means that as soon as the contents of the TextBox control change, the caption of the first command in the Edit menu must become Undo, even if it’s Redo at the time. The Redo command is available only after undoing an operation and before editing the text. So, how do we know that the text has been edited? The TextBox control fires the TextChanged event every time its contents change. We’ll use this event to restore the caption of the Undo/Redo command to Undo. Insert the following statement in the TextChanged event of the TextBox control:
EditUndoItem.Text = "Undo"Code language: VB.NET (vbnet)
The TextBox control can’t provide more-granular undo operations— unlike Word, which keeps track of user actions (insertions, deletions, replacements, and so on) and then undoes them in steps. If you need a more-granular undo feature, you should use the RichTextBox control. The RichTextBox control can display formatted text, but it can also be used as an enhanced TextBox control. By the way, setting the menu item’s caption from within the TextChanged event handler is an overkill, because this event takes place every time the user presses a key. However, the operation takes no time at all and doesn’t make the application less responsive. A better choice would be the DropDownOpening event of the editFormat item, which is fired every time the user opens the Edit menu.