C++Builder provides dozens of components that will make your programming chores easier. VCL even includes one group of components that encapsulate the Win32 common dialog boxes. You'll no doubt use the OpenDialog and SaveDialog components from this group most often. However, other dialog box components are also available, including PrinterSetupDialog, PrintDialog, ColorDialog, and FontDialog. All of these dialog boxes have fairly complete implementations and are reasonably straightforward to use.
Two other components, FindDialog and ReplaceDialog, are less well known. These components exist in relative obscurity partly because Find and Replace dialog boxes aren't appropriate for every type of application. Moreover, these dialog boxes aren't particularly intuitive to use.
In this article, we'll explain how to use the Find and Replace dialog boxes in your C++Builder applications. To do so, we'll examine the properties and methods of the FindDialog and ReplaceDialog components. We'll pay particular attention to the events these dialog boxes generate.
The Find dialog box allows the user to select from three basic options: Match Whole Word Only, Match Case, and Direction (up or down), as shown in Figure A. When you select Match Whole Word Only, you indicate that the search text must be a whole word and not merely part of another word (the in Mother, for example). The Match Case option dictates whether the search will be case sensitive. The Direction option indicates whether the search should move forward through the document (down) or backward (up).
Figure A: The Find dialog box offers three options.
The FindText property contains the text to search for--you can read this property to retrieve the string the user wants to find. If you assign a value to this property prior to displaying the Find dialog box, then the Find What field will contain that value when the dialog box is displayed.
The Options property is a set. By adding values to the set, you can check or uncheck options (such as the Match Case option) or you can hide them altogether. For example, if you don't want to let your users search both up and down, you can hide the Direction option by adding the frHideUpDown value from the Options property. At runtime, you can read the Options property to determine which options the user had set at the time he or she clicked the Find Next button.
If you're writing an application that requires search operations, then the RichEdit component is the only way to go. Not only can this component read, display, and save plain text files, it can also handle rich-text format (RTF) files. In addition, the RichEdit component includes built-in text search features that greatly simplify your work.
As we mentioned, the OnFind event will fire when the user clicks the Find Next button. In your event handler for this event, you'll need to extract the search text and the options the user has set. Once you have that information, you can perform your search.
if (FindDialog-$gt Options.Contains(frMatchCase)) // do somethingBut retrieving the options is only half of the story--you have to actually do something with the options.
Let's look at an example. We're going to use the RichEdit component's FindText() method to find the text. This method requires a parameter, Options, which tells the rich-edit control how to perform the search. The Options parameter is also a set, named TSearchTypes. A picture is still worth a thousand words--so take a look at how all this fits together:
TSearchTypes Options; if (FindDialog-$gtOptions.Contains(frWholeWord)) Options << stWholeWord; if (FindDialog-$gtOptions.Contains(frMatchCase)) Options << stMatchCase; // Deselect any selected text. int Pos = RichEdit-$gtFindText(FindDialog-$gtFindText, StartPos, Length, Options);You can see that we declare an Options variable of type TSearchTypes, read the Find dialog box's options, and add corresponding elements to our Options variable. We then pass the Options set to the FindText() method, which takes over from there.
Let's examine the FindText() method for a moment. The first parameter is the text for which to search, the second parameter is the starting position, the third parameter is the length of text to search, and the fourth parameter holds the search options. FindText() returns the character position of the text if the text was found or -1 if there was no match.
One thing that FindText() won't do for you is search backward. If you're going to allow backward searches, then you'll need to write the necessary code to perform such a search.
int Pos = RichEdit-$gtFindText(FindDialog-$gtFindText, StartPos, Length, Options);
if (Pos != -1) {
RichEdit-$gtSelStart = Pos;
RichEdit-$gtSelLength = FindDialog-$gtFindText.Length();
RichEdit-$gtPerform(EM_SCROLLCARET, 0, 0);
RichEdit-$gtSetFocus();
}
If this code finds the text, it sets the SelStart property to the location of the found text and the SelLength property to the length of the search text. Next, the code sends an EM_SCROLLCARET message to the RichEdit component to scroll the editor window to the caret location. (For some reason, the RichEdit component doesn't have a method to scroll to the caret, so you have to send a Windows message to perform that action--think of it as Windows programming the old-fashioned way.) Finally, the code sets focus to the RichEdit component so the text is highlighted.After a successful search, you should move the SelStart property the entire length of the selected text. If you don't take this step, then you'll find the same occurrence of the text repeated again and again.
Figure B: The Replace dialog box looks like this.
if (dynamic_castYou can then perform special actions based on the result.(Sender)) // it was the find dialog box else // it was the replace dialog box
In addition, the Replace dialog box has an OnReplace event that's generated when the user clicks the Replace or Replace All button. You can read the Options property to determine which button the user clicked.
RichEdit-$gtSetSelTextBuf( ReplaceDialog-$gtReplaceText.c_str());If the user clicked Replace All, then you'll need to search the document for all occurrences of FindText and replace them with ReplaceText.
Listing A shows a program that illustrates the concepts we've discussed in this article. This program contains a RichEdit component, a Find dialog box, a Replace dialog box, and two buttons (Find and Replace). On program startup, the RichEdit component is loaded with the FINDMAIN.CPP file (the source code unit for the program's main form). You can search for text, replace text--even replace all. The OnFind and OnReplace event handlers do all the work--examine them closely to see just how they operate. To create this application, place the components on a form and then enter the code from Listing A. We've marked in color the code you'll need to type. Note that this example doesn't perform a backward search, but it does perform the basic find and replace operations. It should give you a good start on implementing the Find and Replace dialog boxes in your applications.
Listing A: FINDMAIN.CPP
#include <vcl\vcl.h>
#pragma hdrstop
#include "FindMain.h"
//----------------------------------------------------
#pragma resource "*.dfm"
TForm1 *Form1;
//----------------------------------------------------
__fastcall TForm1::TForm1(Tcomponent *Owner)
: TForm(Owner)
{
}
//----------------------------------------------------
void __fastcall TForm1::FindBtnClick(TObject *Sender)
{
FindDialog-$gtExecute();
}
//----------------------------------------------------
void __fastcall TForm1::ReplaceBtnClick(TObject *Sender)
{
ReplaceDialog-$gtExecute();
}
//----------------------------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
RichEdit-$gtLines-$gtLoadFromFile("FindMain.cpp");
}
//----------------------------------------------------
void __fastcall TForm1::FindDialogFind(TObject *Sender)
{
// Dialog that generated the event could be either
// Find or Replace dialog so just cast
// Sender to a TFindDialog.
TFindDialog* Dlg =
dynamic_cast(Sender); // Set up the options.
TSearchTypes Options;
if (Dlg-$gtOptions.Contains(frWholeWord))
Options << stWholeWord;
if (Dlg-$gtOptions.Contains(frMatchCase))
Options << stMatchCase; // Deselect any selected text and advance the cursor
// position. Prevents us from selecting the same
// text over and over again.
if (RichEdit-$gtSelLength) {
RichEdit-$gtSelStart += RichEdit-$gtSelLength;
RichEdit-$gtSelLength = 0;
} // Find the text.
int Pos = RichEdit-$gtFindText(Dlg-$gtFindText,
RichEdit-$gtSelStart, RichEdit-$gtGetTextLen(),
Options); // If text is found, highlight it, scroll window to
// found text, and set focus to RichEdit control.
// If no text is found, let user know.
if (Pos != -1) {
RichEdit-$gtSelStart = Pos;
RichEdit-$gtSelLength = Dlg-$gtFindText.Length();
RichEdit-$gtPerform(EM_SCROLLCARET, 0, 0);
RichEdit-$gtSetFocus();
}
else
MessageBox(Application-$gtHandle,
"Search text not found.", "Find Text", MB_OK);
}
//----------------------------------------------------
void __fastcall TForm1::ReplaceDialogReplace(TObject *Sender)
{
// If user clicked Replace All, then do find/replace
// operation until no more matches are found.
if (ReplaceDialog-$gtOptions.Contains(frReplaceAll)) {
static int Count;
TSearchTypes Options;
if (ReplaceDialog-$gtOptions.Contains(frWholeWord))
Options << stWholeWord;
if (ReplaceDialog-$gtOptions.Contains(frMatchCase))
Options << stMatchCase; // Find the first match.
int Pos = RichEdit-$gtFindText(
ReplaceDialog-$gtFindText, RichEdit-$gtSelStart,
RichEdit-$gtGetTextLen(), Options); // Loop while we are still finding text.
while (Pos != -1)
{
// Same code as in the OnFind event handler.
RichEdit-$gtSelStart = Pos;
RichEdit-$gtSelLength =
ReplaceDialog-$gtFindText.Length(); // Replace the found text with the new text.
RichEdit-$gtSetSelTextBuf(
ReplaceDialog-$gtReplaceText.c_str());
RichEdit-$gtPerform(EM_SCROLLCARET, 0, 0); // Increment the counter so we can tell the user
// how many replacements we made.
Count++; // Do another search.
Pos = RichEdit-$gtFindText(
ReplaceDialog-$gtFindText, RichEdit-$gtSelStart,
RichEdit-$gtGetTextLen(), Options);
}
// Dismiss the Replace dialog.
ReplaceDialog-$gtCloseDialog(); // Tell the user what we did.
char buff[40];
sprintf(buff, "Finished searching text.\r\n%d replacements made.", Count);
MessageBox(Application-$gtHandle, buff, "Replace Text", MB_OK);
// Reset the counter.
Count = 0;
}
// If not doing a Replace All, then just replace the
// found text with the new text.
else {
// No text selected? Then return.
if (RichEdit-$gtSelLength == 0) return;
RichEdit-$gtSetSelTextBuf(
ReplaceDialog-$gtReplaceText.c_str());
RichEdit-$gtSelLength = 0;
}
// Set focus to the RichEdit.
RichEdit-$gtSetFocus();
}