Chapter 8. Taming TextBoxes
Visual Basic's TextBox control provides many useful features including copy, cut, and paste, scroll bars,
and multiline text entry. The RichTextBox control provides even more features including the ability to
display multiple fonts and character styles in a single piece of text. This chapter explains ways you
can get even more out of the TextBox and RichTextBox controls.
<Text omitted>
66. Make a RichTextBox Editor ***
The RichTextBox control is an enhanced TextBox that provides many additional formatting features.
Example program RichEdit uses a RichTextBox control to implement a simple text editor. It implements
several standard menu items in addition to several text formatting commands.
Program RichEdit.
How It Works
The three most interesting tasks performed by program RichEdit are protecting data, displaying a Most
Recently Used (MRU) file list, and allowing the user to select fonts. These topics are described in
the following sections.
Protecting Data
Any program that manages data must ensure that its data is safe. It cannot let the user accidentally
lose changes that have been made to the data. When the user wants to exit, begin a new file, or load
a different file, the program must ask if it should save the modified data first.
Program RichEdit uses the DataModified Boolean variable to keep track of whether the current data has
been modified since it was loaded or last saved.
Whenever the program's RichTextBox detects a change, its Change event handler invokes subroutine
SetDataModified. This routine updates DataModified and sets the program's caption so it displays an
asterisk if the data is modified. For instance, if the file's title is MyData.txt, the routine would
make the program's caption read "RichEdit*[MyData.txt]."
Option Explicit
Private FileName As String ' The full file name.
Private FileTitle As String ' The file name without path.
Private DataModified As Boolean
Private Sub rchEditor_Change()
SetDataChanged True
End Sub
' Set DataModified. Display an asterisk in the
' form's Caption next to the file name if
' appropriate.
Private Sub SetDataModified(changed As Boolean)
' Don't bother if it's already been done.
If DataModified = changed Then Exit Sub
DataModified = changed
If changed Then
Caption = "RichEdit*[" & FileTitle & "]"
Else
Caption = "RichEdit [" & FileTitle & "]"
End If
End Sub |
When the user wants to exit, load a different file, or start a new file, the program calls the DataSafe
function. This function returns True if it is safe to remove the old data.
DataSafe begins by checking the value of DataModified. If DataModified is False, the current data has
not been modified so it can safely be discarded. DataSafe returns True to indicate that it is safe to
proceed with the operation that will remove the current data.
If the data has been modified, DataSafe warns the user and asks if it should save the changes. If the
user clicks the No button, the user does not want to save the data so it is safe to discard. DataSafe returns True.
If the user clicks the Cancel button, the user has decided not to continue with the operation that would
remove the current data. DataSafe returns False to tell the calling routine that it is not safe to remove
the current data.
Finally, if the user clicks the Yes button, the user wants to save the data. DataSafe invokes the
mnuFileSave_Click event handler just as if the user had selected the Save menu item from the File menu.
This routine saves the modified data, prompting the user for a file name if necessary. If the save succeeds,
the program sets DataModified to False. DataSafe sets its return value to Not DataModified. This tells the
calling routine that the data is safe if the data was properly saved.
' Return True if the data is safe.
Private Function DataSafe() As Boolean
' No problem if the data is unmodified.
If Not DataModified Then
DataSafe = True
Exit Function
End If
' See if the user wants to save changes.
Select Case MsgBox("The data has been modified. " & _
Do you want to save the changes?", vbYesNoCancel)
Case vbYes
' Save the data. Procedure SaveData
' will reset DataModified.
mnuFileSave_Click
DataSafe = Not DataModified
Case vbNo
' Discard the changes to the data.
DataSafe = True
Case vbCancel
' Cancel.
DataSafe = False
End Select
End Function |
The RichTextBox's Change event handler calls SetDataModified to mark the data as modified.
The program also calls SetDataModified when the data is freshly loaded or created and it has
not yet been modified. For instance, the following code executes when the user selects the File
menu's New command. It first verifies that the current data is safe. It then blanks the text
document and the file name and calls SetDataModified to indicate that the data has not been modified yet.
' Start a new file.
Private Sub mnuFileNew_Click()
' Make sure the existing data is safe.
If Not DataSafe Then Exit Sub
' Start a new document.
rchEditor.Text = ""
' Save the file name and title.
FileTitle = ""
FileName = ""
' Make sure the caption gets updated.
DataModified = True
SetDataModified False
End Sub |
This technique is quite flexible and is useful in any program that allows the user to modify data.
MRU List
Most recently used (MRU) file lists have become standard on commercial applications.
As Figure 8.1 shows, Program RichEdit displays the last four files it accessed in its MRU list.
Figure 8.1 Example program RichEdit uses a four-item MRU list.
Program RichEdit uses three routines to manage its MRU list. LoadMRUList loads the list from the
system registry. DisplayMRUList updates the menu items in the program's File menu to display the
MRU choices. SaveFileName adds a name to the MRU list in the File menu and updates the registry.
When the program starts, it calls subroutine LoadMRUList to load the MRU file names from the Registry.
The file names are stored in the Registry in the RichEdit section, MRUList subsection. The files' names
are stored in keys named Name1, Name2, Name3, and Name4. The files' names without path information are
stored in the keys Title1, Title2, Title3, and Title4. Subroutine LoadMRUList uses Visual Basic's
GetSetting statement to read the file names and titles and store them in the MRUName and MRUTitle
collections. It then calls DisplayMRUList to update the File menu.
' The MRU list.
Private MRUName As Collection
Private MRUTitle As Collection
' Load the MRU list.
Private Sub LoadMRUList()
Dim i As Integer
Dim file_title As String
Dim file_name As String
' Load the saved entries.
Set MRUName = New Collection
Set MRUTitle = New Collection
For i = 1 To 4
file_name = GetSetting("RichEdit", _
"MRUList", "Name" & Format$(i), "")
If Len(file_name) > 0 Then
file_title = GetSetting("RichEdit", _
"MRUList", "Title" & Format$(i), "")
MRUTitle.Add file_title
MRUName.Add file_name
End If
Next i
' Display the MRU items.
DisplayMRUList
End Sub |
Program RichEdit's File menu contains five menu items that initially have their Visible properties
set to False. These items come just before the menu's Exit command. The first four are all named
mnuFileMRUItem and have Index properties 1, 2, 3, and 4. This is where the MRU file names will be displayed.
The fifth menu item is named mnuFileMRUSep. Its Caption property is set to a dash (-) so it displays
as a separator in the menu. This is the separator that lies between the MRU file names and the Exit
command in the menu.
Subroutine DisplayMRUList examines the strings in the MRUName and MRUTitle collections. For each
nonblank entry, it sets the caption of one of the mnuFileMRUItem menu items and makes that item visible.
It sets the Visible property for the other MRU menu items to False so they are not displayed.
' Display the MRU list.
Private Sub DisplayMRUList()
Dim i As Integer
' Load the used entries.
For i = 1 To 4
If i > MRUName.Count Then Exit For
mnuFileMRUItem(i).Caption = "&" & _
Format$(i) & " " & MRUTitle(i)
mnuFileMRUItem(i).Visible = True
Next i
' Hide unneeded entries.
For i = MRUName.Count + 1 To 4
mnuFileMRUItem(i).Visible = False
Next i
' Show the separator if necessary.
mnuFileMRUSep.Visible = (MRUName.Count > 0)
End Sub |
When the user saves a file with a new name or opens a file, the program calls subroutine SaveFileName.
This routine records the file's name and title in the FileName and FileTitle variables. It then searches
the MRUName and MRUTitle collections to see if the file is already in the MRU file list. If it is, the
routine removes it from the collections so that it will not appear twice when it is added again.
SaveFileName then inserts the new file title at the beginning of the MRU collections. This puts the file
at the top of the MRU list. If the list contains more than four items, the program removes the last one.
This item will not appear on the MRU list anyway because subroutine DisplayMRUList considers at most four
files, but removing this item prevents the MRU collections from becoming cluttered.
Next, SaveFileName uses Visual Basic's SaveSetting command to save the new MRU list in the Registry.
Finally, the routine invokes DisplayMRUList to update the items in the program's File menu.
' Save the file name and title. Update the MRU list.
Private Sub SaveFileName(ByVal fname As String, _
ByVal ftitle As String)
Dim i As Integer
FileTitle = ftitle
FileName = fname
' See if the file is already in the MRU list.
For i = 1 To 4
If i > MRUName.Count Then Exit For
If LCase$(MRUName(i)) = LCase$(fname) Then
' It is here. Remove it.
MRUName.Remove i
MRUTitle.Remove i
Exit For
End If
Next i
' Add the file at the top of the list.
If MRUName.Count > 0 Then
MRUName.Add fname, , 1
MRUTitle.Add ftitle, , 1
Else
MRUName.Add fname
MRUTitle.Add ftitle
End If
' If there are more than 4 files in the
' collections, remove the last one.
If MRUName.Count > 4 Then
MRUName.Remove 5
MRUTitle.Remove 5
End If
' Save the modified MRU list.
For i = 1 To 4
If i > MRUName.Count Then
fname = ""
ftitle = ""
Else
fname = MRUName(i)
ftitle = MRUTitle(i)
End If
SaveSetting "RichEdit", "MRUList", _
"Name" & Format$(i), fname
SaveSetting "RichEdit", "MRUList", _
"Title" & Format$(i), ftitle
Next i
' Redisplay the MRU items.
DisplayMRUList
End Sub |
A few commercial applications provide a configuration dialog that allows the user to specify the number
of items that should be displayed in the MRU list. This feature is not widely implemented, but you could
modify the code presented here to implement it if you like. While this feature does not seem to be turning
into a standard, it does not hurt because the program can default to the more typical four-item MRU list
until the user changes it.
Formatting Fonts
The RichTextBox control provides several properties that affect the appearance of the text it displays.
The SelBold, SelItalic, SelStrikethru, and SelUnderline properties are Boolean values that determine
whether the selected text has a bold, italic, strike through, or underlined
style. The RichEdit program provides simple menu entries to let the user change these values.
When the user opens the program's Font menu, its mnuFormat_Click event handler executes.
This routine examines the properties of the currently selected text and places check marks
beside the appropriate menu items. For instance, if the selected text is bold, the event
handler checks the Bold menu item. To checking these values easier, the routine uses function FontAttributeValue.
FontAttributeValue determines whether a property value is Null and returns a default value
if it is. The RichTextBox control returns Null for a property value if it is ambiguous.
For instance, if the user selects two words, only one of which is bold, the control's SelBold
property is Null. In that case the program leaves the Bold menu item unchecked.
' Get the proper settings.
Private Sub mnuFormat_Click()
mnuFormatBold.Checked = _
FontAttributeValue(rchEditor.SelBold, False)
mnuFormatItalic.Checked = _
FontAttributeValue(rchEditor.SelItalic, False)
mnuFormatUnderline.Checked = _
FontAttributeValue(rchEditor.SelUnderline, False)
mnuFormatStrikethrough.Checked = _
FontAttributeValue(rchEditor.SelStrikethru, False)
End Sub
' Return a font attribute value. Return the default
' if the value is Null.
Private Function FontAttributeValue( _
ByVal attr_value As Variant, _
ByVal default As Variant) As Variant
If IsNull(attr_value) Then
FontAttributeValue = default
Else
FontAttributeValue = attr_value
End If
End Function |
When the user selects one of these menu items, the program sets the selected text's property
to the opposite of the value displayed by the corresponding menu. For instance, if the Italic
command is marked in the menu, the selected text is currently italicized. If the user selects
this menu item, the program switches the text to nonitalic.
Private Sub mnuFormatItalic_Click()
rchEditor.SelItalic = Not mnuFormatItalic.Checked
End Sub |
The RichTextBox control also allows the user to specify different fonts and font sizes for
the selected text. The program's Font command in the Font menu presents the Common Dialog
control's font selection dialog. It initializes the dialog's font properties using the selected
text's values. If the user clicks the dialog's OK button, the program applies the new properties to the text.
' Let the user select new font characteristics.
Private Sub mnuFormatFont_Click()
' Set the font attributes.
dlgFont.FontBold = _
FontAttributeValue(rchEditor.SelBold, False)
dlgFont.FontItalic = _
FontAttributeValue(rchEditor.SelItalic, False)
dlgFont.FontUnderline = _
FontAttributeValue(rchEditor.SelUnderline, False)
dlgFont.FontStrikethru = _
FontAttributeValue(rchEditor.SelStrikethru, False)
dlgFont.FontName = _
FontAttributeValue(rchEditor.SelFontName, "")
dlgFont.FontSize = _
FontAttributeValue(rchEditor.SelFontSize, 0)
' Let the user select the font attributes.
On Error Resume Next
dlgFont.flags = cdlCFScreenFonts
dlgFont.ShowFont
If Err.Number = cdlCancel Then
' The user canceled.
Exit Sub
ElseIf Err.Number <> 0 Then
MsgBox "Error" & Str$(Err.Number) & _
" selecting font." & _
vbCrLf & Err.Description
Exit Sub
End If
On Error GoTo 0
' Set the font.
rchEditor.SelBold = dlgFont.FontBold
rchEditor.SelItalic = dlgFont.FontItalic
rchEditor.SelUnderline = dlgFont.FontUnderline
rchEditor.SelStrikethru = dlgFont.FontStrikethru
rchEditor.SelFontName = dlgFont.FontName
rchEditor.SelFontSize = dlgFont.FontSize
End Sub |
The RichTextBox control provides other properties that let a program perform such tasks as justifying text,
creating bulleted lists, and using hanging indentation. You can easily modify program RichEdit to provide
access to these features.
|