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.