cDialogFindReplace class
A cDialogFindReplace shows the Windows Find dialog or the Find and Replace dialog. The dialog is modeless: ShowFind and ShowReplace return at once, and the dialog stays open beside the program’s own window. The class holds the text, the options and the last action of the user as properties, and builds the line of text that describes the search.
Warning
In version 1.1.0.0 the program never learns what the user did in the dialog. ShowFind and ShowReplace keep the structure that Windows works on in a local variable, which no longer exists when the method returns, although Windows keeps its address for as long as the dialog is open. The owner window then receives no message for Find Next, Replace, Replace All or Cancel, LastAction stays None, and hWnd and IsVisible still show a dialog after it was closed. In a 32-bit program, ShowReplace can also end the program with an access violation while the dialog is open, before anything is pressed, because Windows reads memory that has been reused. See How the dialog reports. A program that needs the dialog calls FindTextW or ReplaceTextW itself and keeps the FINDREPLACE structure in a module-level variable.
Properties: AllowMatchCase, AllowUpDown, AllowWholeWord, Direction, FindWhat, hWnd, LastAction, LastError, MatchCase, MessageId, Mode, OwnerHwnd, ReplaceWith, WholeWord. Methods: IsVisible, Reset, ShowFind, ShowReplace, ToDescription, UpdateFromFlags. Enumerations: DialogMode, FindAction, SearchDirection.
Create an object with Dim dlg As New cDialogFindReplace. The class is declared [COMCreatable(False)], so CreateObject cannot make one, and New can.
Private Sub OpenFind(ByVal ownerWindow As LongPtr)
Dim dlg As New cDialogFindReplace
dlg.OwnerHwnd = ownerWindow ' the window that receives the messages
dlg.FindWhat = "twin"
dlg.MatchCase = True
If dlg.ShowFind() Then
Debug.Print "Open, message " & dlg.MessageId
Else
Debug.Print "Not shown, error " & dlg.LastError
End If
End Sub
How the dialog reports
The dialog is not modal, so there is no result to return. Windows reports the user’s actions by sending a registered window message to the owner window, with the address of a FINDREPLACE structure as lParam. The number of the message is MessageId, the same for every object. In the structure, Flags tells what happened: FR_FINDNEXT (&H8), FR_REPLACE (&H10), FR_REPLACEALL (&H20) or FR_DIALOGTERM (&H40, the dialog closed), together with the options FR_DOWN (&H1), FR_WHOLEWORD (&H2) and FR_MATCHCASE (&H4). The program’s window procedure reads Flags, at byte 12 of the structure in a 32-bit program and byte 24 in a 64-bit one, and passes it to UpdateFromFlags, which sets LastAction, MatchCase, WholeWord, Direction and reads FindWhat and ReplaceWith back from the text the user typed.
The class has no event, callback or message loop of its own. To receive the message, the owner window needs a window procedure that the program controls, for example by subclassing the window, and the message loop of the program must run while the dialog is open.
Private Declare PtrSafe Sub FrCopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal Destination As LongPtr, ByVal Source As LongPtr, ByVal Length As LongPtr)
' Called from the owner window's procedure when the message number equals dlg.MessageId.
Public Sub FrHandleFindMessage(ByVal dlg As cDialogFindReplace, ByVal lParam As LongPtr)
Dim flags As Long
#If Win64 Then
FrCopyMemory VarPtr(flags), lParam + 24, 4
#Else
FrCopyMemory VarPtr(flags), lParam + 12, 4
#End If
dlg.UpdateFromFlags flags
Select Case dlg.LastAction
Case FindAction.FindNext
Debug.Print "Find: " & dlg.FindWhat
Case FindAction.ReplaceAll
Debug.Print "Replace all: " & dlg.FindWhat & " -> " & dlg.ReplaceWith
Case FindAction.DialogClosed
Debug.Print "Closed"
End Select
End Sub
The structure the class keeps in a local variable
Each of ShowFind and ShowReplace declares its FINDREPLACE as a local variable and passes it to FindTextW or ReplaceTextW. Windows does not copy it: it keeps the pointer for the whole life of the dialog and reads and writes the structure each time the user presses a button. When the method returns, the memory belongs to whatever uses the stack next. In a 32-bit and in a 64-bit program alike, the dialog opens, accepts text and closes, and the owner window gets no message at all, so a program that waits for LastAction never leaves the wait.
The fault is in the code of the class and not in Windows: the same call made with the structure in a module-level variable delivers a message for Find Next and one for Cancel, with the typed text in the buffer.
Properties
AllowMatchCase
Whether the user may change the Match case option.
Syntax: object.AllowMatchCase [ = value ]
A Boolean, True by default. When it is False, ShowFind and ShowReplace set FR_NOMATCHCASE, and the Match case check box is shown disabled.
Dim dlg As New cDialogFindReplace
dlg.AllowMatchCase = False
dlg.MatchCase = True ' fixed for the user
Debug.Print dlg.AllowMatchCase
AllowUpDown
Whether the user may change the search direction.
Syntax: object.AllowUpDown [ = value ]
A Boolean, True by default. When it is False, the methods set FR_NOUPDOWN, and the Up and Down buttons are shown disabled.
Dim dlg As New cDialogFindReplace
dlg.AllowUpDown = False
dlg.Direction = SearchDirection.Down
Debug.Print dlg.AllowUpDown
AllowWholeWord
Whether the user may change the Match whole word only option.
Syntax: object.AllowWholeWord [ = value ]
A Boolean, True by default. When it is False, the methods set FR_NOWHOLEWORD, and the check box is shown disabled.
Dim dlg As New cDialogFindReplace
dlg.AllowWholeWord = False
Debug.Print dlg.AllowWholeWord
Direction
The direction of the search.
Syntax: object.Direction [ = value ]
A SearchDirection, Down by default. When it is Down, the methods set FR_DOWN and the dialog opens with Down selected, otherwise with Up. UpdateFromFlags sets it from FR_DOWN.
Dim dlg As New cDialogFindReplace
dlg.Direction = SearchDirection.Up
Debug.Print dlg.Direction = SearchDirection.Up
FindWhat
The text to find.
Syntax: object.FindWhat [ = value ]
A String, empty by default. ShowFind and ShowReplace put it in the Find what box. The box holds 256 characters, and the methods raise error 5 (Invalid procedure call or argument) for a longer text, before they ask Windows for the dialog. UpdateFromFlags replaces it with the text of the box.
Dim dlg As New cDialogFindReplace
dlg.FindWhat = String$(300, "a")
On Error Resume Next
Debug.Print dlg.ShowFind()
Debug.Print Err.Number
' Output:
' 5
hWnd
The window handle of the dialog.
Syntax: object.hWnd
A read-only LongPtr, 0 by default and when no dialog was created. ShowFind and ShowReplace set it to the handle that Windows returns. UpdateFromFlags sets it to 0 when the flags tell that the dialog closed, and Reset sets it to 0 without closing the dialog.
Dim dlg As New cDialogFindReplace
Debug.Print dlg.hWnd
LastAction
What the user did last, as UpdateFromFlags read it.
Syntax: object.LastAction
A read-only FindAction, None by default. Only UpdateFromFlags and Reset change it.
Dim dlg As New cDialogFindReplace
If dlg.LastAction = FindAction.None Then Debug.Print "No action yet"
LastError
The error code of the last call that could not create a dialog.
Syntax: object.LastError
A read-only Long, 0 by default. After a ShowFind or ShowReplace that returned False it holds the value of CommDlgExtendedError; after one that returned True it is 0. With OwnerHwnd 0, Windows refuses the dialog and the value is &HFFFF.
Dim dlg As New cDialogFindReplace
Debug.Print dlg.ShowFind()
Debug.Print Hex$(dlg.LastError)
' Output:
' False
' FFFF
MatchCase
Whether the search distinguishes upper-case from lower-case letters.
Syntax: object.MatchCase [ = value ]
A Boolean, False by default. The methods set FR_MATCHCASE for True and the Match case check box starts selected. UpdateFromFlags sets it from FR_MATCHCASE.
Dim dlg As New cDialogFindReplace
dlg.MatchCase = True
Debug.Print dlg.ToDescription()
MessageId
The number of the window message that the dialog sends to the owner window.
Syntax: object.MessageId
A read-only Long: the value of RegisterWindowMessage("commdlg_FindReplace"), a number that Windows assigns once for the whole session, from &HC000 up. Every object gets the same number. The class reads it when it is created.
Dim dlg As New cDialogFindReplace
Debug.Print dlg.MessageId
Mode
Which dialog the object last showed, and how ToDescription describes the search.
Syntax: object.Mode [ = value ]
A DialogMode, FindMode by default. ShowFind sets FindMode and ShowReplace sets ReplaceMode, also when Windows refuses the dialog. Assigning a value changes only the text that ToDescription returns; the dialog that opens is the one of the method that is called.
Dim dlg As New cDialogFindReplace
dlg.Mode = DialogMode.ReplaceMode
dlg.ReplaceWith = "x"
Debug.Print dlg.ToDescription()
OwnerHwnd
The window that owns the dialog and receives its messages.
Syntax: object.OwnerHwnd [ = value ]
A LongPtr window handle, 0 by default. Windows requires an owner: with 0, ShowFind and ShowReplace return False and no dialog opens. Reset does not change it.
Private Sub SetOwner(ByVal ownerWindow As LongPtr)
Dim dlg As New cDialogFindReplace
dlg.OwnerHwnd = ownerWindow ' the handle of a window of the program
Debug.Print dlg.OwnerHwnd
End Sub
ReplaceWith
The replacement text.
Syntax: object.ReplaceWith [ = value ]
A String, empty by default. ShowReplace puts it in the Replace with box, which holds 256 characters, and raises error 5 for a longer text. ShowFind has no such box and does not use it. UpdateFromFlags replaces it with the text of the box; after a ShowFind the box does not exist, and the text becomes empty.
Dim dlg As New cDialogFindReplace
dlg.FindWhat = "old"
dlg.ReplaceWith = "new"
Debug.Print dlg.ReplaceWith
WholeWord
Whether the search matches whole words only.
Syntax: object.WholeWord [ = value ]
A Boolean, False by default. The methods set FR_WHOLEWORD for True and the check box starts selected. UpdateFromFlags sets it from FR_WHOLEWORD.
Dim dlg As New cDialogFindReplace
dlg.WholeWord = True
Debug.Print dlg.ToDescription()
Methods
IsVisible
Tells whether the object has a dialog.
Syntax: object.IsVisible ( ) As Boolean
Returns True when hWnd is not 0. It does not ask Windows whether the window is shown or still exists, so it stays True after the user closed the dialog, until UpdateFromFlags reads a closing message or Reset runs.
Dim dlg As New cDialogFindReplace
Debug.Print dlg.IsVisible()
' Output:
' False
Reset
Restores the search settings to their starting values.
Syntax: object.Reset
FindWhat and ReplaceWith become empty, MatchCase and WholeWord False, Direction Down, Mode FindMode, the three Allow properties True, hWnd 0, LastAction None and LastError 0. It does not close a dialog that is open, and it does not change OwnerHwnd or the text the dialog edits, so a later UpdateFromFlags can bring that text back.
Dim dlg As New cDialogFindReplace
dlg.FindWhat = "alpha"
dlg.MatchCase = True
dlg.Direction = SearchDirection.Up
dlg.Reset
Debug.Print dlg.ToDescription()
' Output:
' Find: "", Down
ShowFind
Shows the Find dialog.
Syntax: object.ShowFind ( ) As Boolean
Calls FindTextW and returns at once. Returns True when Windows created the dialog, and hWnd holds its handle. Returns False when it did not, and LastError holds the reason; a missing OwnerHwnd is the usual one. The dialog opens with FindWhat, MatchCase, WholeWord and Direction in its controls and has the title Find. Mode becomes FindMode. Read the warning at the head of the page: the owner window receives no message from the dialog.
Private Sub FindIn(ByVal ownerWindow As LongPtr)
Dim dlg As New cDialogFindReplace
dlg.OwnerHwnd = ownerWindow
dlg.FindWhat = "twinBASIC"
dlg.Direction = SearchDirection.Down
Debug.Print dlg.ShowFind()
End Sub
ShowReplace
Shows the Replace dialog.
Syntax: object.ShowReplace ( ) As Boolean
As ShowFind, with ReplaceTextW, a title Replace, the Replace with box filled from ReplaceWith, and the buttons Replace and Replace All beside Find Next. Mode becomes ReplaceMode.
Private Sub ReplaceIn(ByVal ownerWindow As LongPtr)
Dim dlg As New cDialogFindReplace
dlg.OwnerHwnd = ownerWindow
dlg.FindWhat = "old"
dlg.ReplaceWith = "new"
Debug.Print dlg.ShowReplace()
End Sub
ToDescription
Returns the search as a line of text.
Syntax: object.ToDescription ( ) As String
Find: "text", then, when Mode is ReplaceMode, , Replace: "text", then , Match Case and , Whole Word for each that is True, and last , Down or , Up.
Dim dlg As New cDialogFindReplace
dlg.FindWhat = "alpha"
dlg.MatchCase = True
dlg.Direction = SearchDirection.Up
Debug.Print dlg.ToDescription()
dlg.Mode = DialogMode.ReplaceMode
dlg.ReplaceWith = "beta"
dlg.WholeWord = True
Debug.Print dlg.ToDescription()
' Output:
' Find: "alpha", Match Case, Up
' Find: "alpha", Replace: "beta", Match Case, Whole Word, Up
UpdateFromFlags
Reads the flags that the dialog sent, and updates the properties.
Syntax: object.UpdateFromFlags ( flags )
- flags
- required A Long: the
Flagsmember of theFINDREPLACEstructure atlParamof the registered message. See How the dialog reports.
The method sets MatchCase from FR_MATCHCASE, WholeWord from FR_WHOLEWORD and Direction from FR_DOWN, and sets LastAction from the first of these that is set, in this order: FR_DIALOGTERM gives DialogClosed (and sets hWnd to 0), then FR_FINDNEXT FindNext, FR_REPLACE Replace, FR_REPLACEALL ReplaceAll; with none of them it is None. Then it reads FindWhat and ReplaceWith from the two text buffers of the object.
Call it only from the handler of the message. Called at another time, it sets the options from whatever flags it is given and replaces both texts with the content of the buffers. The buffers are empty before the first dialog, so FindWhat and ReplaceWith then become empty strings. The buffer of the replacement text is filled only by ShowReplace, so after a ShowFind alone ReplaceWith becomes empty.
Dim dlg As New cDialogFindReplace
dlg.FindWhat = "alpha"
dlg.UpdateFromFlags &H8 Or &H1 Or &H4 ' FR_FINDNEXT, FR_DOWN, FR_MATCHCASE
Debug.Print dlg.LastAction = FindAction.FindNext
Debug.Print dlg.MatchCase
Debug.Print "[" & dlg.FindWhat & "]"
dlg.UpdateFromFlags &H40 ' FR_DIALOGTERM
Debug.Print dlg.LastAction = FindAction.DialogClosed
Debug.Print dlg.Direction = SearchDirection.Up
' Output:
' True
' True
' []
' True
' True
Enumerations
The enumerations are declared inside the class. A program names their members with the name of the enumeration in front, as in the samples, such as FindAction.FindNext. The name of the class cannot be put in front: cDialogFindReplace.FindAction.FindNext is refused with TB5079, Unrecognized symbol ‘cDialogFindReplace’.
DialogMode
Which dialog an object shows, the type of Mode.
| Member | Value | Meaning |
|---|---|---|
| FindMode | 0 | The Find dialog. |
| ReplaceMode | 1 | The Replace dialog. |
FindAction
What the user did in the dialog, the type of LastAction.
| Member | Value | Meaning |
|---|---|---|
| None | 0 | Nothing yet. |
| FindNext | 1 | The user chose Find Next. |
| Replace | 2 | The user chose Replace. |
| ReplaceAll | 3 | The user chose Replace All. |
| DialogClosed | 4 | The dialog was closed. |
SearchDirection
The direction of a search, the type of Direction.
| Member | Value | Meaning |
|---|---|---|
| Up | 0 | Toward the start of the text. |
| Down | 1 | Toward the end of the text. |
See Also
- cDialogPrint class – shows the Print dialog
- cDialogPageSetup class – shows the Page Setup dialog
- cDialog class – shows the Open, Save As and Browse For Folder dialogs
- TBMANLIB_Dialog package – overview