What's New
Q & A
Tip Jar
C# Helper...
Follow VBHelper on Twitter
MSDN Visual Basic Community
  Ready-to-Run Visual Basic Code Library: Sample Text  
Overview Table of Contents Sample Text
Updates Wiley

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 & "]"
        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.
            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.
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
        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 = ""
            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.
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
        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
    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.


  Ready-to-Run Visual Basic Code Library: Sample Text  
Overview Table of Contents Sample Text
Updates Wiley

Copyright © 1997-2010 Rocky Mountain Computer Consulting, Inc.   All rights reserved.