Basic Text Editor Sample Application

 

In this tutorial, we will build a basic text editor, similar to Windows Notepad, that will allow a user to create, save, and print a plain text file. Three sample applications will be developed. The first sample application enables the user to perform basic text editing functions such as copying, cutting, pasting, and finding text. The second sample application builds on the first and adds the capability to replace text. The third application adds the ability to turn word wrapping on or off.

 

This tutorial incorporates the use of menus and the Common Dialog control, both introduced in earlier tutorials. This tutorial also introduces the Clipboard object.

 

Sample Application 1

 

To build the basic text editor application, start a new VB project, and perform the following steps.

 

(1)        From the Project -> Components menu, add the Common Dialog control ("Microsoft Common Dialog Control 6.0") to your project, then place the Common Dialog control on your form. Name it dlgEditor.        

 

(2)        Using the Menu Editor, build the menu structure indicated by the table below. For the "Level" column of the table below ("1" indicates a top- level menu item, "2" indicates a submenu item below the top-level menu item).

           

Caption

Name

Shortcut

Level

&File

mnuFile

 

1

&New

mnuFileNew

Ctrl+N

2

&Open...

mnuFileOpen

Ctrl+O

2

-

Hyphen1

 

2

&Save

mnuFileSave

Ctrl+S

2

Save &As...

mnuFileSaveAs

Ctrl+A

2

-

Hyphen2

 

2

&Print...

mnuFilePrint

Ctrl+P

2

-

Hyphen3

 

2

E&xit

mnuFileExit

 

2

&Edit

mnuEdit

 

1

Cu&t

mnuEditCut

Ctrl+X

2

&Copy

mnuEditCopy

Ctrl+C

2

&Paste

mnuEditPaste

Ctrl+V

2

-

Hyphen4

 

2

&Find...

mnuEditFind

Ctrl+F

2

Find &Next

mnuEditFindNext

F3

2

F&ormat

mnuFormat

 

1

&Font...

mnuFormatFont

 

2

 

            Screen shots of the Menu Editor with the completed entries are shown below:

 

 

(3)        Add a textbox to your form. Set the following properties (make sure to set both the Top and Left properties to 0, which will make the textbox appear in the upper left corner of the form) :

 

Property

Value

Name

txtEditor

MulitLine

True

Scrollbars

2 – Vertical

Font

Lucida Console, size 10

Top

0

Left

0

 

(4)        Set the following properties of the form:

 

Property

Value

Name

frmTextEditor

Caption

(Untitled) – Text Editor

WindowState

2 – Maximized

Icon

(notepad.ico file, included in download)

 

The completed design-time form should look something like the screen shot below.

 

 

Now for the coding ...

 

General Declarations Section

 

The General Declarations section contains declarations for a number of form-level variables, as shown below. The mblnChanged Boolean variable is used throughout the application as indicator for whether or not the text that the user is working with has been saved. The mblnCancelSave Boolean variable is used to test whether or not the user clicked the "Cancel" button on the "Save" dialog box. The variable mstrSearchFor is used in conjunction with the Find function, and the remaining form-level variables defined here are related to the Print function.

 

Option Explicit

 

Private mblnChanged                 As Boolean

Private mblnCancelSave              As Boolean

Private mstrSearchFor               As String

 

' Printing-related variables

Private mstrPrintFileName           As String

Private mintOrientation             As Integer  ' 1 = portrait, 2 = landscape

Private mintLineCtr                 As Integer

Private mintPageCtr                 As Integer

Private mintLinesPerPage            As Integer

Private mintCharsPerLine            As Integer

Private Const mintLINE_START_POS    As Integer = 6

 

The Form_Resize Event

 

The purpose of the code inside the Form_Resize event, shown below, is to size the txtEditor textbox within the interior of the form (also called the "client area" of the form) whenever the size of the form changes. The Form's Resize event occurs when the form is first shown on the screen, as well as when the user minimizes, maximizes, or restores it, as well as when the user uses the form's borders to resize it.   The form's ScaleHeight property measures the height of the form's client area; the ScaleWidth property measures the width of the form's client area. By setting the textbox's Height and Width properties to the form's ScaleHeight and ScaleWidth properties, respectively, in the Form_Resize event, the textbox will always fit perfectly within the interior of the form.

 

'-----------------------------------------------------------------------------

Private Sub Form_Resize()

'-----------------------------------------------------------------------------

 

    txtEditor.Height = frmTextEditor.ScaleHeight

    txtEditor.Width = frmTextEditor.ScaleWidth

   

End Sub

 

The txtEditor_Change Event

 

This event contains only one statement, but an important one. It sets the mblnChanged Boolean variable to True, indicating a change has been made to the text.

 

'-----------------------------------------------------------------------------

Private Sub txtEditor_Change()

'-----------------------------------------------------------------------------

    mblnChanged = True

End Sub

 

File Menu Logic

 

The coding for the events of the sub-menu items of the File menu item (New, Open, Save, Save As, Print, and Exit) will now be examined.

 

The mnuFileNew_Click Event

 

The "New" operation involves the following four steps:

(1)        clear the text entry area (the txtEditor control)

(2)        set the "changed flag" (mblnChanged variable) to False

(3)        set the form's caption back to "(Untitled) - Text Editor"

(4)        clear the Filename property of the Common Dialog control

 

However, before these steps can be carried out, we must first check to see if the user has made changes to the "current document" (whether it is a file they had loaded in and modified, or whether they had just starting typing in the text entry area and have not yet saved). If they have made changes to the current document, a prompt appears asking them if they want to save it. The prompt allows three possible responses: Yes, No, or Cancel. If No, the "New" operation steps above are carried out without saving the current document; if Cancel, nothing at all happens; if Yes, a couple of paths may be taken: if the current document is an existing file that was loaded in, it is saved "silently" and the "New" steps are carried out; if the current document is new and unsaved, the "Save As" dialog will appear, prompting the user for the drive, folder, and filename to give the current document. If, when the "Save As" dialog is displayed, the user clicks the dialog's "Save" button, the current document is saved and the "New" steps are carried out; if, however, the user clicks the dialog's "Cancel" button, the mnuFileNew_Click event will exit without doing anything.

 

The following code implements the "New" logic described above:

 

'-----------------------------------------------------------------------------

Private Sub mnuFileNew_Click()

'-----------------------------------------------------------------------------

 

    Dim intUserResponse As Integer

   

    If mblnChanged = True Then

        ' current file was changed since last save

        intUserResponse = MsgBox("The current file has changed. " _

                              & "Do you want to save it before starting a new file?", _

                                vbYesNoCancel + vbQuestion + vbDefaultButton3, _

                                "Basic Text Editor Demo")

        Select Case intUserResponse

            Case vbYes

                ' user wants to save current file

                Call mnuFileSave_Click

                If mblnCancelSave = True Then

                    Exit Sub

                End If

            Case vbNo

                ' user does not want to save current file,

                ' proceed with "New" command logic

            Case vbCancel

                ' user wants to cancel New command

                Exit Sub

        End Select

    End If

   

    ' reset for new file

    txtEditor.Text = ""

    mblnChanged = False

    frmTextEditor.Caption = "(Untitled) - Text Editor"

    dlgTextEditor.FileName = ""

   

End Sub

 

 

The mnuFileOpen_Click Event

 

The "Open" operation involves the following steps:

(1)        Show the "Open flavor" of the Common Dialog control to allow the user to navigate to the file he or she wants to open. If the use selects a file to open and clicks the "Open" button, continue; if they click the "Cancel" button, do nothing.

(2)        Open the selected file, load it into the txtEditor control, and close the file.

(3)        set the "changed flag" (mblnChanged variable) to False

(4)        include the selected file's name in the form's caption

 

However, before these steps can be carried out, we must first check to see if the user has made changes to the "current document" (whether it is a file they had loaded in and modified, or whether they had just starting typing in the text entry area and have not yet saved). If they have made changes to the current document, a prompt appears asking them if they want to save it. The prompt allows three possible responses: Yes, No, or Cancel. If No, the "Open" operation steps above are carried out without saving the current document; if Cancel, nothing at all happens; if Yes, a couple of paths may be taken: if the current document is an existing file that was loaded in, it is saved "silently" and the "Open" steps are carried out; if the current document is new and unsaved, the "Save As" dialog will appear, prompting the user for the drive, folder, and filename to give the current document. If, when the "Save As" dialog is displayed, the user clicks the dialog's "Save" button, the current document is saved and the "Open" steps are carried out; if, however, the user clicks the dialog's "Cancel" button, the mnuFileOpen_Click event will exit without doing anything.

 

The following code implements the "Open" logic described above:

 

'-----------------------------------------------------------------------------

Private Sub mnuFileOpen_Click()

'-----------------------------------------------------------------------------

 

    Dim intUserResponse As Integer

    Dim intFileNbr      As Integer

   

    On Error GoTo mnuFileOpen_Click_ErrHandler

   

    If mblnChanged = True Then

        ' current file was changed since last save

        intUserResponse = MsgBox("The current file has changed. " _

                              & "Do you want to save it before opening a new file?", _

                                vbYesNoCancel + vbQuestion + vbDefaultButton3, _

                                "Basic Text Editor Demo")

        Select Case intUserResponse

            Case vbYes

                ' user wants to save current document

                Call mnuFileSave_Click

                If mblnCancelSave = True Then

                    ' user canceled save

                    mblnCancelSave = False

                    Exit Sub

                End If

            Case vbNo

                ' user doesn't want to save current document, proceed with Open dialog

            Case vbCancel

                ' user wants to cancel Open command

                Exit Sub

        End Select

    End If

   

    With dlgTextEditor

        .CancelError = True

        .Filter = "Text Files(*.txt)|*.txt|All Files(*.*)|*.*"

        .FileName = ""

        .ShowOpen

    End With

   

    intFileNbr = FreeFile

    Open dlgTextEditor.FileName For Input As #intFileNbr

    txtEditor.Text = Input(LOF(intFileNbr), intFileNbr)

    Close #intFileNbr

   

    mblnChanged = False

   

    frmTextEditor.Caption = dlgTextEditor.FileName & " - Text Editor"

   

    Exit Sub

   

mnuFileOpen_Click_ErrHandler:

    Exit Sub

   

End Sub

 

The mnuFileSave_Click Event

 

If the user is working on a new document (i.e., one that has never been saved), the mnuFileSaveAs_Click procedure (detailed a little further below) is invoked; otherwise, the user is working on an existing document and the programmer-defined Sub SaveCurrentFile (also detailed a little further below) is invoked to "silently" save the file.

 

'-----------------------------------------------------------------------------

Private Sub mnuFileSave_Click()

'-----------------------------------------------------------------------------

 

    If frmTextEditor.Caption = "(Untitled) - Text Editor" Then

        ' new document

        Call mnuFileSaveAs_Click

    Else

        ' existing document

        SaveCurrentFile

    End If

   

End Sub

 

The mnuFileSaveAs_Click Event

 

This event shows the "Save As flavor" of the Common Dialog box to let the user choose the drive, directory and filename for the file to be saved.  If the user clicks the "Cancel" button on the Save As dialog box, the error-handler routine (mnuFileSaveAs_Click_ErrHandler) is invoked, which sets the mblnCancelSave flag to True and exits the procedure; otherwise the programmer-defined Sub SaveCurrentFile (detailed a little further below) is invoked to save the file, and the name of the file is then incorporated into the form's title.

 

'-----------------------------------------------------------------------------

Private Sub mnuFileSaveAs_Click()

'-----------------------------------------------------------------------------

 

    On Error GoTo mnuFileSaveAs_Click_ErrHandler

   

    With dlgTextEditor

        .CancelError = True

        .Flags = cdlOFNOverwritePrompt + cdlOFNPathMustExist

        .Filter = "Text Files(*.txt)|*.txt"

        .ShowSave

    End With

   

    SaveCurrentFile

   

    frmTextEditor.Caption = dlgTextEditor.FileName & " - Text Editor"

   

    Exit Sub

   

mnuFileSaveAs_Click_ErrHandler:

    mblnCancelSave = True

    Exit Sub

   

End Sub

 

The SaveCurrentFile Sub

 

This procedure actually saves the file. The filename as selected in the "Save As" dialog box is opened for output, the Print #n statement is used to output the contents of the txtEditor control to the file, and the file is closed. The mblnChanged flag is then set to False.

 

'-----------------------------------------------------------------------------

Private Sub SaveCurrentFile()

'-----------------------------------------------------------------------------

   

    Dim intFileNbr As Integer

 

    intFileNbr = FreeFile

    Open dlgTextEditor.FileName For Output As #intFileNbr

    Print #intFileNbr, txtEditor.Text

    Close #intFileNbr

   

    mblnChanged = False

 

End Sub

 

The mnuFilePrint_Click Event

 

This event shows the "Print flavor" of the Common Dialog box to let the user choose the printer to be used for printing the document. The Orientation (Portrait or Landscape), if the printer selected supports that property, can also be selected from the Print dialog box; its value can be read from the Orientation property of the control. If the user clicks the "Print" button on the Print dialog box, the Orientation is saved in the form-level variable mintOrientation, and the programmer-defined Sub PrintCurrentFile (detailed a little further below) is invoked; if the user clicks the "Cancel" button on the Print dialog box, the procedure is exited without printing.

 

'-----------------------------------------------------------------------------

Private Sub mnuFilePrint_Click()

'-----------------------------------------------------------------------------

 

    On Error GoTo mnuFilePrint_Click_ErrHandler

   

    With dlgTextEditor

        .Flags = cdlPDNoSelection + cdlPDHidePrintToFile + cdlPDNoPageNums

        .CancelError = True

        .ShowPrinter

    End With

   

    mintOrientation = dlgTextEditor.Orientation

   

    PrintCurrentFile

   

    Exit Sub

 

mnuFilePrint_Click_ErrHandler:

    Exit Sub

       

End Sub

 

The PrintCurrentFile and PrintHeadngs Sub

 

In this Sub, the VB Printer object is used to print the current contents of the txtEditor control to the default printer. Regardless of the font selected for the display of the text on-screen, the font used for hard copy printing will be set to Courier, size 10. If Portrait orienatation is selected, printing limits are set to 80 characters per line, 60 lines per page; if Landscape orientation is selected, limits are set to 118 characters per line, 47 lines per page.

 

We then want to prepare the string variable mstrPrintFileName to hold the name of the currently saved file so that it can be printed on the second heading line of the printout (when printing the contents of the txtEditor textbox, this program adds two heading lines containing print date, time, and filename). For mstrPrintFileName, we want the filename portion only, without the full path information. The path information is stripped from the filename as follows:

 

      mstrPrintFileName = dlgTextEditor.FileName

      mstrPrintFileName = Mid$(mstrPrintFileName, InStrRev(mstrPrintFileName, "\") + 1)

 

We then want to make sure that there is enough room on the line for the filename to fit. The line has enough room for mintCharsPerLine – 21 characters (21 characters are needed to display "Print Time: hh:nn:ss "). Therefore, if mstrPrintFileName is longer than "mintCharsPerLine – 21", the data is truncated to fit. Hence the following code:

 

    If Len(mstrPrintFileName) > (mintCharsPerLine - 21) Then

        mstrPrintFileName = Right$(mstrPrintFileName, (mintCharsPerLine - 21))

    End If

 

Following this, the PrintHeadings Sub is called to print the headings for the first page.

 

The real work of the PrintCurrentFile Sub then begins. The method basically entails breaking up the text from the txtEditor control into paragraphs (i.e., separate the text by line breaks).  This is accomplished with the statement

 

      astrParags = Split(txtEditor.Text, vbNewLine)

 

For each paragraph, we need to figure out how this content will fit on the printed page (i.e., we must "word wrap" this text using code – nothing is going to do it for us automatically). The code within the inner loop beginning "Do Until blnParagComplete" handles this. The basic approach is look for the blank space prior to the word within the text that would cause the line to overflow with too many characters, print the text up to that blank space, then repeat that process starting with the next word. This process is performed for each paragraph, as the inner loop is executed inside the outer loop beginning For intX = 0 To UBound(astrParags).

 

When printing is complete, the EndDoc method of the Printer object must be invoked.

 

The code for the PrintCurrentFile Sub is shown below:

 

'-----------------------------------------------------------------------------

Private Sub PrintCurrentFile()

'-----------------------------------------------------------------------------

   

    Dim astrParags()        As String

    Dim strCurrLine         As String

    Dim intX                As Integer

    Dim intStart            As Integer

    Dim intBlankPos         As Integer

    Dim blnParagComplete    As Boolean

  

    ' Set the printer font to Courier, if available (otherwise, we would be

    ' relying on the default font for the Windows printer, which may or

    ' may not be set to an appropriate font) ...

    For intX = 0 To Printer.FontCount - 1

        If Printer.Fonts(intX) Like "Courier*" Then

            Printer.FontName = Printer.Fonts(intX)

            Exit For

        End If

    Next

   

    Printer.FontSize = 10

    Printer.Orientation = mintOrientation

   

    ' set limits based on orientation

    If Printer.Orientation = vbPRORPortrait Then

        mintLinesPerPage = 60

        mintCharsPerLine = 80

    Else

        mintLinesPerPage = 47

        mintCharsPerLine = 118

    End If

   

    ' set filename as it will appear in the heading

    mstrPrintFileName = dlgTextEditor.FileName

    mstrPrintFileName = Mid$(mstrPrintFileName, InStrRev(mstrPrintFileName, "\") + 1)

    If Len(mstrPrintFileName) > (mintCharsPerLine - 21) Then

        mstrPrintFileName = Right$(mstrPrintFileName, (mintCharsPerLine - 21))

    End If

   

    ' initialize page counter ...

    mintPageCtr = 0

   

    ' print first page headings

    PrintHeadings

    

    astrParags = Split(txtEditor.Text, vbNewLine)

   

    For intX = 0 To UBound(astrParags)

        blnParagComplete = False

        intStart = 1

        Do Until blnParagComplete

            If mintLineCtr > mintLinesPerPage Then

                PrintHeadings

            End If

            strCurrLine = Mid$(astrParags(intX), intStart, mintCharsPerLine)

            If Len(strCurrLine) < mintCharsPerLine Then

                blnParagComplete = True

            ElseIf Right$(strCurrLine, 1) = " " Then

                intStart = intStart + mintCharsPerLine

            ElseIf Mid$(astrParags(intX), intStart + mintCharsPerLine, 1) = " " Then

                intStart = intStart + mintCharsPerLine + 1

            Else

                intBlankPos = InStrRev(strCurrLine, " ")

                If intBlankPos = 0 Then

                    intStart = intStart + mintCharsPerLine

                Else

                    intStart = intBlankPos + 1

                    strCurrLine = Left$(strCurrLine, intBlankPos - 1)

                End If

            End If

            Printer.Print Tab(mintLINE_START_POS); strCurrLine

            mintLineCtr = mintLineCtr + 1

        Loop

    Next

           

    ' Important! When done, the EndDoc method of the Printer object must be invoked.

    ' The EndDoc method terminates a print operation sent to the Printer object,

    ' releasing the document to the print device or spooler.

    Printer.EndDoc

 

End Sub

 

The code for the PrintHeadings Sub is shown below:

 

'-----------------------------------------------------------------------------

Private Sub PrintHeadings()

'-----------------------------------------------------------------------------

   

    ' If we are about to print any page other than the first, invoke the NewPage

    ' method to perform a page break. The NewPage method advances to the next

    ' printer page and resets the print position to the upper-left corner of the

    ' new page.

    If mintPageCtr > 0 Then

        Printer.NewPage

    End If

   

    ' increment the page counter

    mintPageCtr = mintPageCtr + 1

   

    ' Print 4 blank lines, which provides a for top margin. These four lines do NOT

    ' count toward the limit of lines per page.

    Printer.Print

    Printer.Print

    Printer.Print

    Printer.Print

    

    ' Print the headers

    Printer.Print Tab(mintLINE_START_POS); _

                  "Print Date: "; _

                  Format$(Date, "mm/dd/yy"); _

                  Tab(mintLINE_START_POS + (((mintCharsPerLine - 19) \ 2) + 1)); _

                  "THEVBPROGRAMMER.COM"; _

                  Tab(mintLINE_START_POS + (mintCharsPerLine - 7)); _

                  "Page:"; _

                  Format$(mintPageCtr, "@@@")

    Printer.Print Tab(mintLINE_START_POS); _

                  "Print Time: "; _

                  Format$(Time, "hh:nn:ss"); _

                  Tab(mintLINE_START_POS + _

                      (((mintCharsPerLine - Len(mstrPrintFileName)) \ 2) + 1)); _

                  mstrPrintFileName

    Printer.Print

   

    ' reset the line counter to reflect the number of lines that have now

    ' been printed on the new page.

    mintLineCtr = 3

 

End Sub

 

The mnuFileExit_Click and Form_Unload Events

 

The mnuFileExit_Click event contains the statement Unload Me which fires the Form_Unload event.

 

'-----------------------------------------------------------------------------

Private Sub mnuFileExit_Click()

'-----------------------------------------------------------------------------

 

    Unload Me

   

End Sub

 

Before we allow the form to be unloaded (i.e., exit the application), we must first check to see if the user has made changes to the "current document" (whether it is a file they had loaded in and modified, or whether they had just starting typing in the text entry area and have not yet saved). If they have made changes to the current document, a prompt appears asking them if they want to save it. The prompt allows three possible responses: Yes, No, or Cancel. If No, the form unloads and the application exits  without saving the current document; if Cancel, nothing at all happens (we set the Cancel argument of the Form_Unload event to 1, cancelling the Unload event and thus remaining in the application); if Yes, a couple of paths may be taken: if the current document is an existing file that was loaded in, it is saved "silently" and then the form unloads; if the current document is new and unsaved, the "Save As" dialog will appear, prompting the user for the drive, folder, and filename to give the current document. If, when the "Save As" dialog is displayed, the user clicks the dialog's "Save" button, the current document is saved and then the form unloads; if, however, the user clicks the dialog's "Cancel" button, we set the Cancel argument of the Form_Unload event to 1, cancelling the Unload event and thus remaining in the application.

 

The following code implements the logic described above:

 

'-----------------------------------------------------------------------------

Private Sub Form_Unload(Cancel As Integer)

'-----------------------------------------------------------------------------

   

    Dim intUserResponse As Integer

   

    If mblnChanged = True Then

        ' file was changed since last save

        intUserResponse = MsgBox("The file has changed. Do you want to save it?", _

                             vbYesNoCancel + vbQuestion + vbDefaultButton3, _

                             "Basic Text Editor Demo")

        Select Case intUserResponse

            Case vbYes

                ' user wants to save current document

                Call mnuFileSave_Click

                If mblnCancelSave = True Then

                    ' user canceled save

                    ' return to document-don't unload form

                    Cancel = 1

                    mblnCancelSave = False

                End If

            Case vbNo

                ' user does not want to save current document

                ' unload form and exit

            Case vbCancel

                ' return to document-don't unload form

                Cancel = 1

        End Select

    End If

      

End Sub

 

Edit Menu Logic

 

The coding for the Edit menu item Click event as well as its sub-menu items (Cut, Copy, Paste, Find, and Find Next) will be examined after we take two brief "detours": one to introduce the Clipboard object, the other to review the "Sel" properties of the textbox control (SelStart, SelLength, and SelText).

The Clipboard Object

The Clipboard object provides access to the Windows system Clipboard through VB. The Clipboard object is used to manipulate text and graphics on the Clipboard. You can use this object to enable a user to copy, cut, and paste text or graphics in your application. The Clipboard object is shared by all Windows applications, and thus, the contents are subject to change whenever you switch to another application.

Before copying any material to the Clipboard object, you should clear its contents by as performing a Clear method, such as Clipboard.Clear.

The sample applications presented in this article process text only (not graphics), so the two text-related methods of the Clipboard object, GetText and SetText, are used in the applications.

The GetText method returns a text string from the Clipboard object. If the Clipboard contains no text, the zero-length string ("") is returned.

The SetText method returns places a text string on the Clipboard object.

There are three other methods of the Clipboard objects, which are not used for this article. These are GetFormat (allows you to test to see if there is an item on the Clipboard that matches the desired format – for example, you can test to see if a bitmap image has been copied to the Clipboard), the SetData method (places a picture on the Clipboard object using the specified graphic format), and the GetData method (allows you to retrieve a graphic from the Clipboard object).

 

The Textbox's "Sel" Properties (SelStart, SelLength, and SelText)

The SelStart and SelLength properties are used to highlight all or partial text in a textbox. The SelText  property contains the contents of the portion of text that is highlighted. These properties are only available through code; they are not available the Properties window.

The SelStart property specifies on which character position of the text the highlighting should begin. SelStart is zero-based, so 0 means the first character. SelStart also indicates the position of the insertion point (blinking cursor) of the textbox. For example, if the blinking cursor is positioned directly before the "t" in "Hello there" (the 7th character of that string), the value of SelStart is 6.

The SelLength property specifies how many characters of the text should be (or are) highlighted. For example, if the substring "the" in "Hello there" is highlighted, the value of SelLength is 3.

The SelText  property contains the contents of the portion of text that is highlighted. For example, if the substring "the" in "Hello there" is highlighted, the contents of SelText is "the".

 

The mnuEdit_Click Event

 

In the mnuEdit_Click event (the top-level menu item), code is written to determine which of the Cut, Copy, and Paste sub-menu items will be enabled.

 

If no text is currently selected (by testing the contents of the txtEditor control's SelText property), then the Cut and Copy menu items are disabled. (After all, if there is no text currently selected, then there is nothing to cut or copy.) Otherwise, Cut and Copy are enabled.

 

If there is no text currently on the Clipboard (by testing the results of the GetText method), then the Paste menu item is disabled (because there is nothing to paste). Otherwise, Paste is enabled.

 

'-----------------------------------------------------------------------------

Private Sub mnuEdit_Click()

'-----------------------------------------------------------------------------

 

    If txtEditor.SelText = "" Then

        ' no text is selected

        mnuEditCut.Enabled = False

        mnuEditCopy.Enabled = False

    Else

        ' some text is selected

        mnuEditCut.Enabled = True

        mnuEditCopy.Enabled = True

    End If

   

    If Clipboard.GetText() = "" Then

        ' no text is on the clipboard

        mnuEditPaste.Enabled = False

    Else

        ' some text is on the clipboard

        mnuEditPaste.Enabled = True

    End If

   

End Sub

 

The mnuEditCut_Click Event

 

In this event, we first clear the Clipboard, then use the Clipboard's SetText method to place the contents of the currently selected text on the Clipboard. We then remove the selected text from the textbox by setting its SelText property to the zero-length string ("").

 

'-----------------------------------------------------------------------------

Private Sub mnuEditCut_Click()

'-----------------------------------------------------------------------------

 

    ' clear the clipboard

    Clipboard.Clear

   

    'send text to clipboard

    Clipboard.SetText txtEditor.SelText

   

    ' remove selected text from text box

    txtEditor.SelText = ""

   

End Sub

 

The mnuEditCopy_Click Event

 

In this event, as in the mnuEditCut_Click event, we first clear the Clipboard, then use the Clipboard's SetText method to place the contents of the currently selected text on the Clipboard. However, since this is a "Copy" rather than a "Cut", we are done - we do NOT remove the selected text from the textbox; it stays where it is.

 

'-----------------------------------------------------------------------------

Private Sub mnuEditCopy_Click()

'-----------------------------------------------------------------------------

 

    ' clear the clipboard

    Clipboard.Clear

   

    ' send text to clipboard

    Clipboard.SetText txtEditor.SelText

   

End Sub

 

The mnuEditPaste_Click Event

 

The single line of code in this event retrieves the text from the Clipboard and pastes it into the textbox, beginning at the current location of the blinking insertion point. If no text is currently selected in the textbox, the retrieved text will be inserted at the current location of the insertion point. If some text is currently selected in the textbox, the retrieved text will replace the selected text.

 

'-----------------------------------------------------------------------------

Private Sub mnuEditPaste_Click()

'-----------------------------------------------------------------------------

 

    'retrieve text from clipboard and paste into text box

    txtEditor.SelText = Clipboard.GetText()

   

End Sub

 

The mnuEditFind_Click Event

 

In this event, the InputBox function is used to prompt the user for a string to search for. The Instr function is then used to search for the given string within the contents of the txtEditor control, beginning at the first position. If found, the text that we were searching for is highlighted in the textbox; if not, we inform the user of this of fact.

 

'-----------------------------------------------------------------------------

Private Sub mnuEditFind_Click()

'-----------------------------------------------------------------------------

 

    Dim intFoundPos As Integer

   

    ' prompt user for text to find

    mstrSearchFor = InputBox("Find what?", "Find")

   

    ' search for the text

    intFoundPos = InStr(1, txtEditor.Text, mstrSearchFor, 1)

   

    If intFoundPos = 0 Then

        ' text was not found

        MsgBox "The search string was not found.", , "Find"

    Else

        ' text was found, highlight the text

        txtEditor.SelStart = intFoundPos - 1

        txtEditor.SelLength = Len(mstrSearchFor)

    End If

   

End Sub

 

The mnuEditFindNext_Click Event

 

This event is nearly identical to the mnuEditFind_Click event. The difference for "Find Next" is that instead of beginning the search at the first position of the text in the txtEditor control, we must begin the search at the position following the end of the last find. The calculation txtEditor.SelStart + txtEditor.SelLength + 1, which we are assigning to the variable intBegSearch,  gives us the correct position to use in finding the next occurrence of the search string. The variable intBegSearch is then used as the first argument to the Instr function, telling the Instr function to start the search at that position.

 

'-----------------------------------------------------------------------------

Private Sub mnuEditFindNext_Click()

'-----------------------------------------------------------------------------

   

    Dim intFoundPos     As Integer

    Dim intBegSearch    As Integer

   

    intBegSearch = txtEditor.SelStart + txtEditor.SelLength + 1

 

    intFoundPos = InStr(intBegSearch, txtEditor.Text, mstrSearchFor, 1)

   

    If intFoundPos = 0 Then

        ' text was not found

        MsgBox "The search has been completed.", , "Find Next"

    Else

        ' text was found, highlight the text

        txtEditor.SelStart = intFoundPos - 1

        txtEditor.SelLength = Len(mstrSearchFor)

    End If

   

End Sub

 

Format Menu Logic

 

The coding for the event on the sub-menu item of the Format menu item (Font) will now be examined.

 

The mnuFormatFont_Click Event

 

After showing the "Font flavor" of the Common Dialog control, the properties FontName, FontSize, FontItalic, and FontBold are obtained from the Common Dialog control and applied to the corresponding properties of the txtEditor control. Bear in mind that a textbox supports only one font in one style, so whatever you choose from the Font dialog box will apply to the textbox as a whole (i.e., you can't have two different fonts in the textbox control, nor can you have the same font with some bold, some not bold).

 

'--------------------------------------------------------------------------

Private Sub mnuFormatFont_Click()

'--------------------------------------------------------------------------

 

    On Error GoTo mnuFormatFont_Click_ErrHandler

   

    With dlgTextEditor

        .CancelError = True

        .Flags = cdlCFBoth Or cdlCFApply

        .ShowFont

        txtEditor.FontName = .FontName

        txtEditor.FontSize = .FontSize

        txtEditor.FontItalic = .FontItalic

        txtEditor.FontBold = .FontBold

    End With

   

    Exit Sub

   

mnuFormatFont_Click_ErrHandler:

    Exit Sub

   

End Sub

 

Download the project files for Sample Application 1 here.

 

Sample Application 2

 

Sample Application 2 builds on Sample Application 1 in that a Replace function is added to complement the Find function. To build Sample Application 2, perform the following steps:

 

(1)        Copy the project files from Sample Application 1 to a new folder, and open the .vbp file. Make sure the form is showing and then open the Menu Editor (the Menu Editor won't be available unless you have the form open). Add the "Replace" menu item in the position shown:

 

 

            Click OK to dismiss the Menu Editor. The menu on your design-time form should look like this:      

 

 

(2)        Add a new Form to the project. On it, you'll need two labels, two textboxes, four command buttons, and a checkbox. The final result will look like this:

 

 

            Set the properties of the objects as follows:

 

            Form:

 

Property

Value

Name

frmFind

BorderStyle

3 – Fixed Dialog

Caption

(blank)

 

            Labels:

 

Name

Caption

lblFindWhat

Find What:

lblReplaceWith

Replace with:

 

            TextBoxes:

 

Name

txtFindWhat

txtReplaceWith

           

            Command Buttons:

 

Name

Caption

cmdFindNext

Find Next

cmdReplace

&Replace

cmdReplaceAll

Replace &All

cmdCancel

Cancel

 

            Checkbox:

 

Name

Caption

chkMatchCase

Match Case

 

            The coding for frmFind will be examined a little further below, but first two modules must be added to the project.

 

(3)        Add a new module to the project (Project menu -> Add Module). Name the module modFind. Place the following code into the module. It contains a set of Public variables as well as a Public Sub called FindNext. These variables and this Sub are Public, because both the main form (frmTextEditor) and the new form (frmFind) will access these variables as well as call upon the FindNext sub.

 

The FindNext Sub first uses the Instr function to search for the next occurrence of the search string entered by the user. The values used for the Instr arguments are as follows: glngSearchStart specifies the position in the text where the search is to begin; frmTextEditor.txtEditor.Text is the text in the txtEditor control to be searched; gstrSearchString contains the string of text to search for (obtained from the "find what" textbox of the Find form); and the IIf expression in the last argument tests the value of gblnMatchCase, which is set from the "Match Case" checkbox on the Find form – if true, the expression will resolve to zero (vbBinaryCompare, or a case-sensitive search) or one (vbTextCompare, or a case-insensitive search).

 

If the item is not found, the Boolean variable gblnFindItemFound is set to False, then the integer variable gintRestartFind is set. The purpose of gintRestartFind is to save the user's response to whether or not the search should be restarted at the top of the document – if we are in the middle of a "Replace All" operation, gintRestartFind is automatically set to vbNo; otherwise a MsgBox prompts the user with that question, and the response of either vbYes or vbNo is saved in the gintRestartFind variable.

 

If the item is found, it is highlighted in the textbox, glngSearchStart is reset, and the Boolean variable gblnFindItemFound is set to True.

 

Option Explicit

 

Public glngSearchStart      As Long

Public gstrSearchString     As String

Public gstrReplaceWith      As String

Public gblnFindItemFound    As Boolean

Public gintRestartFind      As Integer

Public gblnReplacingAll     As Boolean

Public gblnMatchCase        As Boolean

 

'--------------------------------------------------------------------------

Public Sub FindNext()

'--------------------------------------------------------------------------

 

    Dim lngFoundPos     As Long

    Dim intResponse     As Integer

 

    lngFoundPos = InStr(glngSearchStart, _

                        frmTextEditor.txtEditor.Text, _

                        gstrSearchString, _

                        IIf(gblnMatchCase, vbBinaryCompare, vbTextCompare))

 

    If lngFoundPos = 0 Then

        gblnFindItemFound = False

        If gblnReplacingAll Then

            gintRestartFind = vbNo

        Else

            gintRestartFind = MsgBox("Item was not found between current cursor location and " _

                                   & "the end of the document. Do you want to restart the search " _

                                   & "at the top of the document?", _

                                     vbYesNo + vbQuestion, _

                                    "Find")

        End If

    Else

        frmTextEditor.txtEditor.SelStart = lngFoundPos - 1

        frmTextEditor.txtEditor.SelLength = Len(gstrSearchString)

        frmTextEditor.txtEditor.SetFocus

        glngSearchStart = lngFoundPos + Len(gstrSearchString)

        gblnFindItemFound = True

    End If

   

End Sub

 

(4)        Add a new module to the project (Project menu -> Add Module). Name the module modForm. Place the following code into the module. This is a set of generalized form-related routines that can be incorporated into any VB project. This is the same set of routines presented in the previous article "Extra Topic: Form Procedures". Of these general routines, the CenterForm, FormIsLoaded, and MakeTopmost will be used in Sample Application 2.

 

Option Explicit

 

Private Declare Function DrawMenuBar Lib "user32" (ByVal hwnd As Long) As Long

Private Declare Function GetSystemMenu Lib "user32" (ByVal hwnd As Long, ByVal bRevert As Long) As Long

Private Declare Function GetMenuItemCount Lib "user32" (ByVal hMenu As Long) As Long

Private Declare Function LockWindowUpdate Lib "user32" (ByVal hwnd As Long) As Long

Private Declare Function RemoveMenu Lib "user32" (ByVal hMenu As Long, ByVal nPosition As Long, ByVal wFlags As Long) As Long

Private Declare Sub SetWindowPos Lib "user32" (ByVal hwnd As Long, ByVal hWndInsertAfter As Long, ByVal X As Long, ByVal Y As Long, ByVal cx As Long, ByVal cy As Long, ByVal wFlags As Long)

 

Private Const MF_BYPOSITION             As Long = &H400&

Private Const MF_REMOVE                 As Long = &H1000&

Private Const HWND_TOPMOST              As Long = -1

Private Const HWND_NOTOPMOST            As Long = -2

Private Const SWP_NOSIZE                As Long = &H1

Private Const SWP_NOMOVE                As Long = &H2

Private Const SWP_NOACTIVATE            As Long = &H10

Private Const SWP_SHOWWINDOW            As Long = &H40

 

'=============================================================================

'                     Form-related Routines

'=============================================================================

 

'-----------------------------------------------------------------------------

Public Sub CenterForm(pobjForm As Form)

'-----------------------------------------------------------------------------

 

    With pobjForm

        .Top = (Screen.Height - .Height) / 2

        .Left = (Screen.Width - .Width) / 2

    End With

 

  End Sub

 

'-----------------------------------------------------------------------------

Public Function FormIsLoaded(pstrFormName As String) As Boolean

'-----------------------------------------------------------------------------

   

    Dim objForm As Form

   

    For Each objForm In Forms

        If objForm.Name = pstrFormName Then

            FormIsLoaded = True

            Exit Function

        End If

    Next

   

    FormIsLoaded = False

 

End Function

 

'-----------------------------------------------------------------------------

Public Sub EnableFormXButton(pobjForm As Form, pblnEnable As Boolean)

'-----------------------------------------------------------------------------

 

    Dim lngSysMenuID As Long

    Dim lngMenuItemCount     As Long

   

    ' Get handle To our form's system menu

    ' (Contains items for Restore, Maximize, Move, Close etc.)

    lngSysMenuID = GetSystemMenu(pobjForm.hwnd, pblnEnable)

 

    If lngSysMenuID <> 0 Then

        ' Get System menu's menu count

        lngMenuItemCount = GetMenuItemCount(lngSysMenuID)

        If lngMenuItemCount > 0 Then

            ' Remove (hide) the "Close" item itself (the last menu item) ...

            RemoveMenu lngSysMenuID, lngMenuItemCount - 1, MF_BYPOSITION Or MF_REMOVE

            ' Remove (hide) the seperator bar above the "Close" item

            ' (the next to last menu item) ...

            RemoveMenu lngSysMenuID, lngMenuItemCount - 2, MF_BYPOSITION Or MF_REMOVE

        End If

    End If

   

    DrawMenuBar pobjForm.hwnd

   

End Sub

 

'-----------------------------------------------------------------------------

Public Sub LockWindow(hwnd As Long)

'-----------------------------------------------------------------------------

    LockWindowUpdate hwnd

End Sub

 

'-----------------------------------------------------------------------------

Public Sub UnlockWindow()

'-----------------------------------------------------------------------------

    LockWindowUpdate 0

End Sub

 

'-----------------------------------------------------------------------------

Public Sub MakeTopmost(pobjForm As Form, pblnMakeTopmost As Boolean)

'-----------------------------------------------------------------------------

 

    Dim lngParm As Long

   

    lngParm = IIf(pblnMakeTopmost, HWND_TOPMOST, HWND_NOTOPMOST)

   

    SetWindowPos pobjForm.hwnd, _

                 lngParm, _

                 0, _

                 0, _

                 0, _

                 0, _

                 (SWP_NOACTIVATE Or SWP_SHOWWINDOW Or _

                  SWP_NOMOVE Or SWP_NOSIZE)

 

End Sub

 

(5)        In the main form (frmTextEditor), the mnuEditFind_Click event is modified as shown below. When the user clicks the Find menu item, this code checks to see first if the Find form (frmFind) is already loaded. If so, and frmFind is in "Find" mode, then there is nothing further to do and sub exits. Otherwise, if frmFind is loaded, but is in "Replace" mode, then frmFind is unloaded and the code continues. The search start variable (glngSearchStart) is set to begin at the current location of the insertion point (the "+ 1" is necessary because glngSearchStart will be used for an Instr function, and Instr is "1-based", while SelStart is "0-based"). The caption of the Find form (frmFind) is then set to "Find" and the Find form is shown (the first reference to frmFind (when its caption is set) will cause its Load event to fire).

 

'--------------------------------------------------------------------------

Private Sub mnuEditFind_Click()

'--------------------------------------------------------------------------

 

    If FormIsLoaded("frmFind") Then

        If frmFind.Caption = "Find" Then

            Exit Sub

        Else

            Unload frmFind

        End If

    End If

   

    glngSearchStart = txtEditor.SelStart + 1

   

    With frmFind

        .Caption = "Find"

        .Show

    End With

   

End Sub

 

(6)        In the main form (frmTextEditor), the mnuEditFindNext_Click event is modified as shown below. If there is currently no value to search for, the mnuFind_Click event procedure is called and we exit from this event procedure.  Otherwise, a loop is set up and the FindNext sub is called. The purpose of setting up a loop is that if, after calling FindNext, the item is not found and the user wants to restart the search, then we reset the search start position to 1 and set the Boolean variable blnFindAgain to True so that we go back for another iteration of the loop to invoke FindNext again.

 

'--------------------------------------------------------------------------

Private Sub mnuEditFindNext_Click()

'--------------------------------------------------------------------------

 

    Dim blnFindAgain    As Boolean

 

    If gstrSearchString = "" Then

        mnuEditFind_Click

        Exit Sub

    End If

 

   

    Do

        FindNext

       

        blnFindAgain = False

       

        If Not gblnFindItemFound Then

            If gintRestartFind = vbYes Then

                glngSearchStart = 1

                blnFindAgain = True

            End If

        End If

       

    Loop While blnFindAgain

   

End Sub

 

(7)        In the main form (frmTextEditor), the mnuEditReplace_Click event is added. When the user clicks the Replace menu item, this code checks to see first if the Find form (frmFind) is already loaded. If so, and frmFind is in "Replace" mode, then there is nothing further to do and sub exits. Otherwise, if frmFind is loaded, but is in "Find" mode, then frmFind is unloaded and the code continues. The caption of the Find form (frmFind) is then set to "Replace" and the Find form is shown (the first reference to frmFind (when its caption is set) will cause its Load event to fire).

 

'--------------------------------------------------------------------------

Private Sub mnuEditReplace_Click()

'--------------------------------------------------------------------------

 

    If FormIsLoaded("frmFind") Then

        If frmFind.Caption = "Replace" Then

            Exit Sub

        Else

            Unload frmFind

        End If

    End If

   

    With frmFind

        .Caption = "Replace"

        .Show

    End With

 

End Sub

 

(8)        Now we will turn our attention to the code for the Find form (frmFind).

 

            The General Declarations section contains one form-level variable, mblnActivated, which is used to ensure that the code in the Form_Activate event only executes once (in this form, the "startup" code resides in the Form_Activate event rather than in the Form_Load event).

 

Option Explicit

 

Private mblnActivated   As Boolean

 

The Form_Activate Event

 

The Form_Activate contains "startup" code for this form. We only want this code to execute once. However, if another form or even a MsgBox is displayed on top of this form, the Form_Activate event would fire again when the other form was dismissed and focus returned to this form. This is why the mblnActivated Boolean variable is used to test whether or not this code was executed already. If so, we immediately Exit Sub. Otherwise, the startup code is executed.

 

First, the txtFindWhat, txtReplaceWith, and chkMatchCase controls are pre-filled with the contents of the gstrSearchString, gstrReplaceWith, and gblnMatchCase variables respectively (to show what was selected previously – if this is the first time the FInd form is being displayed, those variables will be empty and so the controls will be empty as well).

 

We then test the value of the form's Caption. On the main form, when the "Find" menu item is selected from the menu, we set the Caption of the Find form to "Find" before showing it; when the "Replace" menu item is selected from the menu, we set the Caption of the Find form to "Replace" before showing it.  If we are in "Find" mode, we hide the "Replace" and "Replace All" buttons and rearrange some of the other controls so that the form looks like a "plain" "Find" form rather than a "Find and Replace" form.

 

The CenterForm sub is called to center the Find form on the screen, and the MakeTopmost sub is called so that the Find form is displayed on top of the main text editor screen, yet the Find form is not "modal" – meaning we can still interact with the main text editor form even though the Find form is sitting on top.

 

The mblnActivated Boolean variable is then set to True, ensuring that this startup code will not be executed more than once.

 

'--------------------------------------------------------------------------

Private Sub Form_Activate()

'--------------------------------------------------------------------------

   

    If mblnActivated Then Exit Sub

   

    With txtFindWhat

        .Text = gstrSearchString

        .SelStart = 0

        .SelLength = Len(gstrSearchString)

        .SetFocus

    End With

    txtReplaceWith.Text = gstrReplaceWith

    chkMatchCase.Value = IIf(gblnMatchCase, vbChecked, vbUnchecked)

   

    If Me.Caption = "Find" Then

        lblReplaceWith.Visible = False

        txtReplaceWith.Visible = False

        chkMatchCase.Top = lblReplaceWith.Top

        cmdReplace.Visible = False

        cmdReplaceAll.Visible = False

        cmdCancel.Top = cmdReplace.Top

        Me.Height = Me.Height _

                  - (cmdReplace.Height + cmdReplaceAll.Height + 120)

    End If

               

    CenterForm Me

 

    MakeTopmost Me, True

 

    mblnActivated = True

 

End Sub

 

The txtFindWhat_Change Event

 

In the Change event of the "Find What" textbox, we monitor whether or not there is any text present. If so, the Find Next, Replace, and Replace All buttons are enabled; if there is no text present, those buttons are disabled (as there would be nothing to Find or Replace).

 

'--------------------------------------------------------------------------

Private Sub txtFindWhat_Change()

'--------------------------------------------------------------------------

 

    If Len(txtFindWhat.Text) > 0 Then

        cmdFindNext.Enabled = True

        cmdReplace.Enabled = True

        cmdReplaceAll.Enabled = True

    Else

        cmdFindNext.Enabled = False

        cmdReplace.Enabled = False

        cmdReplaceAll.Enabled = False

    End If

   

End Sub

 

The cmdFindNext_Click Event

 

In this event, the variables gstrSearchString and gblnMatchCase are set to the values entered in the txtFindWhat and chkMatchCase controls, respectively.

 

            A "Do" loop is then set up. The MakeTopmost sub is called with "False" for the second argument (meaning turn "off" the "topmost" feature), because on the subsequent call to the FindNext sub, if the FindNext sub must show a MsgBox, we want that MsgBox to be on top. After the call to FindNext, we call the MakeTopmost sub again with "True" for the second argument (thus reinstating the "topmost" feature). The purpose of setting up a loop is that if, after calling FindNext, the item is not found and the user wants to restart the search, then we reset the search start position to 1 and set the Boolean variable blnFindAgain to True so that we go back for another iteration of the loop to invoke FindNext again.

 

'--------------------------------------------------------------------------

Private Sub cmdFindNext_Click()

'--------------------------------------------------------------------------

 

    Dim lngFoundPos     As Long

    Dim intResponse     As Integer

    Dim blnFindAgain    As Boolean

   

    gstrSearchString = txtFindWhat.Text

   

    gblnMatchCase = IIf(chkMatchCase.Value = vbChecked, True, False)

   

    Do

 

        MakeTopmost Me, False

       

        FindNext

       

        MakeTopmost Me, True

       

        blnFindAgain = False

       

        If Not gblnFindItemFound Then

            If gintRestartFind = vbYes Then

                glngSearchStart = 1

                blnFindAgain = True

            Else

                Unload Me

                Exit Sub

            End If

        End If

 

    Loop While blnFindAgain

 

End Sub

 

The cmdReplace_Click Event

 

In this event, the variable glngSearchStart is set to begin at the current insertion point. The cmdFindNext_Click event procedure is then called, which, if it finds the search string, will highlight the search string. When we return here, we test to see if the search string was found, and if so, replace it with the contents of the txtReplaceWith control.

 

'--------------------------------------------------------------------------

Private Sub cmdReplace_Click()

'--------------------------------------------------------------------------

 

    glngSearchStart = frmTextEditor.txtEditor.SelStart + 1

   

    cmdFindNext_Click

   

    If gblnFindItemFound Then

        frmTextEditor.txtEditor.SelText = txtReplaceWith.Text

    End If

   

End Sub

 

The cmdReplaceAll_Click Event

 

In this event, the Boolean variable gblnReplacingAll is set to True. The insertion point of the text editor is set to the beginning of the text. In a loop, the cmdReplace_Click event procedure is called, which will find and replace one occurrence of the search string. If the item was found we add 1 to the lngReplacementCount variable. This process is repeated until no more occurrences of the search string are found. At the end of the process, we report our findings in a MsgBox.

 

'--------------------------------------------------------------------------

Private Sub cmdReplaceAll_Click()

'--------------------------------------------------------------------------

 

    Dim lngReplacementCount As Long

 

    gblnReplacingAll = True

   

    frmTextEditor.txtEditor.SelStart = 0

   

    Do

        cmdReplace_Click

        If gblnFindItemFound Then

            lngReplacementCount = lngReplacementCount + 1

        End If

    Loop While gblnFindItemFound

   

    MsgBox "Done searching the document. " _

         & lngReplacementCount & " replacements were made.", _

           vbInformation, _

           "Replace All"

   

    gblnReplacingAll = False

   

End Sub

 

The cmdCancel_Click Event

 

The Cancel button code issues the "Unload Me" statement, which will fire the Find form's Form_Unload event.

 

'--------------------------------------------------------------------------

Private Sub cmdCancel_Click()

'--------------------------------------------------------------------------

    Unload Me

End Sub

 

The Form_Unload Event

 

In the Form_Unload event, we set the form to Nothing, which removes the form from memory and clears its variables.

 

'--------------------------------------------------------------------------

Private Sub Form_Unload(Cancel As Integer)

'--------------------------------------------------------------------------

 

    Set frmFind = Nothing

 

End Sub

 

Download the project files for Sample Application 2 here.

 

 

Sample Application 3

 

Sample Application 3 builds on Sample Application 2. Sample Application adds the functionality to turn Word Wrap on or off. In the previous applications, word wrap occurred automatically in the txtEditor control, because the Scrollbars property was set to 2 – Vertical. When you only have vertical (as opposed to horizontal) scrolling, a multi-line textbox will perform word wrapping automatically. If you set the Scrollbars property to 3 – Both (or 1 – Horizontal)  word wrap no longer occurs. The presence of a horizontal scroll bar effectively turns off word wrapping, and every paragraph (string of text up to a carriage return) will appear on one line, which can be horizontally scrolled if necessary.

 

So you might think to turn word wrap on or off, you simply change the value of the Scrollbars property when the user clicks "Word Wrap" on the menu. Ah, if only it was that easy ... Unfortunately, the Scrollbars property cannot be modified at run-time! So to support a feature that turns WordWrap on or off, you must use two textboxes: one with Scrollbars set to 2 – Vertical, the other set to 3 – Both, with only one of these visible at any given time. When you switch from one to the other, you copy the contents of the "old" one to the "new" one.

 

Rather than a "blow-by-blow" description of all the coding in this project, a summary of what needs to be done to modify Sample Application 2 to create Sample Application 3 will be presented.

 

Assuming you have copied the Sample Application 2 project files over to a new folder, begin modifying the project by adding the Word Wrap menu item to the Format menu as shown below. You should also check the Checked property.

 

 

Next, set the Index property of the original txtEditor control to 0, thus making it a control array. Then copy and paste txtEditor(0) to create txtEditor(1), shown circled below. Set the Scrollbars property of txtEditor(1) to 3 – Both and set its Visible property to False.

 

 

As far as the coding is concerned, a Public variable named gintCurrTBIndex is declared in the General Declarations section of the form:

 

      Public gintCurrTBIndex As Integer

 

The value of this variable will always be either 0 or 1, indicating the Index for the "current" txtEditor control. Whenever you refer to txtEditor in the main form, you must use an index. Some examples are:

 

      txtEditor(gintCurrTBIndex).Text = Input(LOF(intFileNbr), intFileNbr)

 

      Clipboard.SetText txtEditor(gintCurrTBIndex).SelText

 

The crucial coding for this version of the application is in the mnuFormatWordWrap_Click event, shown below. In it, we first save the "state" (Ii.e., True or False) of the mblnChanged variable (because when we copy the contents of the "old" textbox to the "new" textbox, the Change event for the txtEditor control array will fire, but we don't want that to "count" as a change). If word wrap is currently "on" (the value of gintCurrTBIndex will be 0), we turn word wrap "off" by first "unchecking" the Word Wrap menu item, copying the contents of  txtEditor(0) to txtEditor(1), making txtEditor(1) visible and txtEditor(0) not, and setting gintCurrTBIndex to 1;  if word wrap is currently "off" (the value of gintCurrTBIndex will be 1), we turn word wrap "on" by first checking the Word Wrap menu item, copying the contents of  txtEditor(1) to txtEditor(0), making txtEditor(0) visible and txtEditor(1) not, and setting gintCurrTBIndex to 0.  We then restore the state of the mblnChanged variable.

 

'--------------------------------------------------------------------------

Private Sub mnuFormatWordWrap_Click()

'--------------------------------------------------------------------------

 

    Dim intX                    As Integer

    Dim blnSavedChangedState    As Boolean

   

    blnSavedChangedState = mblnChanged

   

    If gintCurrTBIndex = 0 Then

        mnuFormatWordWrap.Checked = False

        txtEditor(1).Text = txtEditor(0).Text

        txtEditor(1).Visible = True

        txtEditor(0).Visible = False

        gintCurrTBIndex = 1

    Else

        mnuFormatWordWrap.Checked = True

        txtEditor(0).Text = txtEditor(1).Text

        txtEditor(0).Visible = True

        txtEditor(1).Visible = False

        gintCurrTBIndex = 0

    End If

 

    mblnChanged = blnSavedChangedState

 

End Sub

 

Other coding considerations are that when you refer to the current element of the txtEditor control array outside of the main form, you must qualify both the name of the control (txtEditor) and the Public variable gintCurrTBIndex with the form name. So you wind up with syntax lke the following:

 

    glngSearchStart _

        = frmTextEditor.txtEditor(frmTextEditor.gintCurrTBIndex).SelStart + 1

   

    cmdFindNext_Click

   

    If gblnFindItemFound Then

        frmTextEditor.txtEditor(frmTextEditor.gintCurrTBIndex).SelText _

                                                        = txtReplaceWith.Text

    End If

 

There are two places in the code where both elements of the txtEditor control array are processed at the same time. These are in the Form_Resize event and mnuFormatFont_Click event, both shown below:

 

'-----------------------------------------------------------------------------

Private Sub Form_Resize()

'-----------------------------------------------------------------------------

 

    Dim intX    As Integer

   

    For intX = 0 To 1

        txtEditor(intX).Top = 0

        txtEditor(intX).Left = 0

        txtEditor(intX).Height = frmTextEditor.ScaleHeight

        txtEditor(intX).Width = frmTextEditor.ScaleWidth

    Next

   

End Sub

 

. . .

 

'--------------------------------------------------------------------------

Private Sub mnuFormatFont_Click()

'--------------------------------------------------------------------------

 

    Dim intX    As Integer

 

    On Error GoTo mnuFormatFont_Click_ErrHandler

   

    With dlgTextEditor

        .CancelError = True

        .Flags = cdlCFBoth Or cdlCFApply

        .ShowFont

        For intX = 0 To 1

            txtEditor(intX).FontName = .FontName

            txtEditor(intX).FontSize = .FontSize

            txtEditor(intX).FontItalic = .FontItalic

            txtEditor(intX).FontBold = .FontBold

        Next

    End With

   

    Exit Sub

   

mnuFormatFont_Click_ErrHandler:

    Exit Sub

   

End Sub

 

Download the project files for Sample Application 3 here.