Visual Basic Projects

Pure VB: Create a Find and Replace Dialog
Step 1: Introduction, Layout and Calling Form Code
     
Posted:   Monday August 05, 2002
Updated:   Monday December 26, 2011
     
Applies to:   VB4-32, VB5, VB6
Developed with:   Original: VB3/Win 3.1. Updated: VB6/Windows XP
OS restrictions:   None
Author:   VBnet - Randy Birch
     
 Other project pages:   Step 2: Building the Find/Replace Form
     
 Prerequisites
None.

Time to dust off some really old code to provide some new functionality to VB!

I wrote this initial search/replace routine from scratch way back in VB2/3 days, as I needed this functionality in a utility I wrote for myself to format scientific text into decent slide format.

With the release of Win9x I have routinely hauled out Window's API-based Find & Replace dialog code in an attempt to get that puppy working under VB.  Provided as Windows' standard common dialog (like notepad's Find dialog), each attempt was met with limited, flaky and unpredictable success.

So tonight I dusted off my old VB3 code, removed all the formerly-familiar data type declaration characters and, with nary an API in sight (for a change), present the code here as viable way to add Find and/or Replace functionality to a VB app.

Because we're all comfortable with VB's own Find & Replace dialogs, I decided to base my updated version on those familiar dialogs. This demo mimics the layout of those VB dialogs and with the features built in to the code using them should be just as intuitive as using VB's own. The features includes:

  • ability to specify either a text or rich text box when calling
  • both Find and Replace functionality are built into the same form, with appropriate controls revealed based on the mode
  • KeyDown processing in the main (calling) form allows Ctrl+F to invoke the Find dialog, and Ctrl+H to invoke Replace, again just as in VB.
  • clicking the Replace... button, just like in VB, changes the dialog from the Find to Replace modes. But unlike VB it does not need to destroy the Find dialog before showing the Replace ... so no flicker
  • Find functionality is independent of Replace, that is, you can use Find Next or Replace alternately when an item should be skipped.
  • the Match Case and Count Matches Only options are working in the demo. 
  • this code is fairly VB-version insensitive - although it has no VB5 or VB6-specific commands to restrict its use across all 32-bit versions of VB (and quite probably VB2/3 as well), the use of block With statements does require those passages to be downgraded if used VB earlier than VB5.

When the Find or Replace dialog has been invoked without the user making a text selected in the target control, the dialog recognizes this and runs a routine that examines characters to either side of the current cursor position in that control, looking for a word-break delimiter (delimiters you can add to edit as required). When recognized delimiters are located on either side of the cursor position, the routine returns the word between those delimiters as the potential Find search word. For example, if the cursor was between the u and b in Public, the routine would present Public as the proposed search word. When the dialog has been invoked and the user has made a text selection, that selection is presented as the search word regardless of whether its a legitimate word (ie if ub was selected in Public, ub would be the search word proposed. Finally, if the cursor is on a blank line, no word is proposed.

Similarly, because a public UDT defined public in a bas module is used to hold many of the counting and tracking parameters, the dialog will automatically display the last-entered Replace string on subsequent Replace actions.

There is a lot going on in this code, therefore it's obscenely commented with most routines remarkably compact once my embedded novels are removed. There is a wee bit of hard-coding in the SetupInit sub in dlgReplace that may futz up display on large-font systems - this was only done for expediency and can easily be soft-coded to use the proper screen resolution info to position the controls. SetupInit is the procedure responsible for showing, hiding and repositioning controls based on the dialog type shown.

The Find Whole Word Only, search Direction, and the Search range (current window, all windows etc.) are not coded in this demo and are left for you to implement if desired. In addition, the control does not save a Most-Recently-Used list of entries, though this too is simple enough to implement.

The layout form below shows the names of the respective controls you must add and their relative positions on the form. The controls in gold indicate controls that are required and referenced in code in this demo but that do not have any code attached (per the statement above). The second cropped form below is provided as a convenience and can be assigned to your working form's Picture property to provide a rough layout guide while you develop/align the controls. Because of the hard-coded values mentioned above, it is strongly recommended you use the template for layout while testing and examining this app.  Once you've soft-coded the SetupInit code, the initial position of the controls is less of an issue.

This project is divided into two pages ... this page with the form design, BAS module and main form code (not illustrated), and a second page with the complete routine for the Find/Replace dialog. The main form requires only a multiline textbox and two default command buttons.

 

 

 

 

 

 

   

 BAS Code: FindReplace.bas
Place the following code into the general declarations area of a bas module and save the file as FindReplace.bas:

Option Explicit
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Copyright ©1996-2011 VBnet/Randy Birch, All Rights Reserved.
' Some pages may also contain other copyrights by the author.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Distribution: You can freely use this code in your own
'               applications, but you may not reproduce 
'               or publish this code on any web site,
'               online service, or distribute as source 
'               on any media without express permission.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'constants used to specify the
'action taken on dialog startup
Public Const swFindText As Boolean = 0
Public Const swReplaceText As Boolean = 1

Public Type FindReplaceData2

  'Strings
  'the text to search
   sWorkText             As String
  
  'the string to search for
   sSearchText           As String
  
  'string to replace it with
   sReplaceText          As String
  
  'Flags
  'if true Trim leading & trailing spaces
   bTrimSpaces           As Boolean
  
  'flag indicating whether to match case.
  'Treated as a Boolean, but declared as
  'int as the values must be 0 or 1 (not -1)
   bMatchCase            As Integer
  
  'flag to force the search to start
  'at the top of the current document
   bStartAtTop           As Integer
  
  'flag to chose between find/replace
  'or count-only routines
   bCountOnly            As Boolean

  'flag indicating counting will take
  'place in the replace sub
   bInReplaceMode        As Boolean
  
  'Position trackers
  'cursor position when the dialog called
   nCursorPos            As Long
  
  'Tracks the position of the found word.
  'When dialog starts, holds cursor position
  'within the work text
   nCurrPos              As Long
 
  'Counters
  'number of matches and replacements made
   nNumFound             As Long
   nNumReplaced          As Long
  
End Type

Public frd As FindReplaceData2


Public Function CreateInitialSearchString(ctl As Control) As String
  
   Dim initSelLen As Long
   Dim work As String
   Dim wordStart As Long
   Dim wordEnd As Long
   Dim currPos As Long
   
   Dim part As String
   Dim initSelStart As Long
   Dim BreakChrs As String
   
  'was something selected in the
  'text box?
   initSelLen = Len(ctl.SelText)
  
  'yes, so use that as the search criteria
  'and save start of the current selection
  'to a variable
   If initSelLen > 0 Then
      CreateInitialSearchString = ctl.SelText
      currPos = ctl.SelStart - initSelLen
      Exit Function
   End If
  
  'nothing was selected, so try and find
  'the next adjacent word to the cursor
   initSelStart = ctl.SelStart
  
  'typical delimiters used to separate words
   BreakChrs = ",()<>[]\|:;=/*-+" & _
                Chr$(32) & _
                Chr$(13) & _
                Chr$(10) & _
                Chr$(9) & _
                Chr$(39)
  
  'create a buffer of the
  'passed control contents
   work = ctl.Text
  
  'if the cursor is presently at position 0,
  'set the cursor position variable to 1.
  'If further in the text, use that value.
   currPos = IIf(initSelStart > 0, initSelStart, 1)
  
  'from the current cursor position in currPos,
  'work backwards to find a break char and
  'save that value as wordStart
   For wordStart = currPos To 1 Step -1
      If InStr(BreakChrs, Mid$(work, wordStart, 1)) Then
         Exit For
      End If
   Next wordStart

  'because the code above found a
  'break chr, add 1 to obtain the
  'first letter of the search word
  'and clip the work text from that
  'point on
   wordStart = wordStart + 1
   part = Mid$(work, wordStart, Len(work))
  
  'starting at currPos (which is now 1 since
  'the preceding text was nuked in the
  'above call), work forward to find the
  'next break char.
   For wordEnd = 1 To Len(part)
      If InStr(BreakChrs, Mid$(part, wordEnd, 1)) Then
         Exit For
      End If
   Next wordEnd
  
  'again, since we know where the break
  'chr is, subtract one for the last
  'letter of the word
   wordEnd = wordEnd - 1
  
  'return the word determined above
   If wordEnd + wordStart > wordStart Then
      CreateInitialSearchString = Mid$(work, wordStart, wordEnd)
   End If

End Function


Public Function CreateInitialReplaceString() As String
  
  'if there is already a value
  'saved as the replace text,
  'return that
   If Len(frd.sReplaceText) > 0 Then
   
      CreateInitialReplaceString = frd.sReplaceText
      Exit Function
   
   Else
   
      CreateInitialReplaceString = ""
   
   End If

End Function


Public Sub CentreFormInParent(cform, pform)

  'cform = child form to centre
  'pform = parent form to centre in
   cform.Move (pform.Left + ((pform.Width - cform.Width) \ 2)), _
              (pform.Top + ((pform.Height - cform.Height) \ 2))

End Sub
 Form Code: Form1 - the main application form
To Form1 add two command buttons (Command1, Command2) and a text box (Text1) with the following code. Don't try to run this form until the dlgReplace form from page 2 of this project has been created and saved.

In order to see the "found text" highlighted in the main form textbox the Text1 HideSelection property must be set to False at design time. In order to have the find/replace dialogs shown when the Control keys (F or H) are pressed, the form's KeyPreview needs to be set to True as well.


Option Explicit
Dim killBackspace  As Boolean


Private Sub Form_Load()

   Dim openfilename As String
   Dim fileNum As Integer
   
  'just a local file to search - change as desired 
   openfilename = "findreplace.bas"
   Me.Caption = "VBnet Find/Replace Dialog Demo"
   
   fileNum = FreeFile
   Open openfilename For Input As fileNum
      Text1.Text = Input$(LOF(fileNum), fileNum)
   Close fileNum
   
   Command1.Caption = "Find..."
   Command2.Caption = "Replace..."
   
End Sub


Private Sub Command1_Click()

   Dim sSearch As String

  'determine the search text - pass the *control*, not the Text property
   sSearch = CreateInitialSearchString(Text1)
   
   With frd
      
      .bCountOnly = False
      
      .sSearchText = sSearch
      If .bTrimSpaces Then .sSearchText = Trim$((sSearch))

      With dlgReplace
         'show find dlg
         .SetupInit swFindText, Text1, Me
         .Show , Me
      End With
      
   End With
      
End Sub

Private Sub Command2_Click()

   Dim sSearch As String
   Dim sReplace As String
  
  'determine text to pass as
  'sSearch and sReplace
   sSearch = CreateInitialSearchString(Text1)
   sReplace = CreateInitialReplaceString()
  
   With frd
      .bCountOnly = False
      .sSearchText = sSearch
      .sReplaceText = sReplace
  
      If .bTrimSpaces Then .sSearchText = Trim$((sSearch))

      With dlgReplace
         'show replace dlg
         .SetupInit swReplaceText, Text1, Me
         .Show , Me
      End With
      
   End With
   
End Sub


Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)

  'ctrl+H is also the keycode for a backspace
  'in a textbox, so in order to display the
  'replace form on a Ctrl+H, we need to eat
  'the keystroke.
  '
  'To do this, we set a flag indicating that
  'the backspace ctrl combination had been
  'pressed and respond show the dialog instead,
  'eating the keystroke in the KeyPress event,
  'and resetting it in KeyUp.  This does not
  'interfere with normal backspace key presses.
   
   If (Shift) = 2 Then
   
      Select Case KeyCode
         Case 72  'H - replace
            killBackspace = True
            Command2.Value = True
         Case 70  'F - find
            killBackspace = True
            Command1.Value = True
         Case Else
         
      End Select
   End If
   
End Sub

Private Sub Form_KeyPress(KeyAscii As Integer)

  'if the ctrl+ backspace combo flag
  'set, eat the keystroke
   If killBackspace Then KeyAscii = 0
   
End Sub


Private Sub Form_KeyUp(KeyCode As Integer, Shift As Integer)

   If killBackspace = True Then killBackspace = False
   
End Sub

 Comments
Save the project thus far and move on to Step 2: Building the Find/Replace Form.

 
 

PayPal Link
Make payments with PayPal - it's fast, free and secure!

 
 
 
 

Copyright ©1996-2011 VBnet and Randy Birch. All Rights Reserved.
Terms of Use  |  Your Privacy

 

Hit Counter