8.7 Диалоги поиска и замены текста — компоненты FindDialog и ReplaceDialog

Компоненты FindDialog и ReplaceDialog, вызывающие диалоги поиска и замены фрагментов текста (рис. 8.12 и 8.13), очень похожи и имеют одинаковые свойства, кроме одного, задающего заменяющий текст в компоненте ReplaceDialog. Такое сходство не удивительно, поскольку ReplaceDialog — производный класс от FindDialog.

Рис. 8.12
Диалоговое окно поиска фрагмента текста


Рис. 8.13
Диалоговое окно замены фрагмента текста

Компоненты имеют следующие основные свойства:


FindTextТекст, заданный пользователем для поиска или замены. Программно может быть установлен как начальное значение, предлагаемое пользователю
ReplaceTextТолько в компоненте ReplaceDialog — текст, который должен заменять FindText
PositionПозиция левого верхнего угла диалогового окна, заданная типом TPoint — записью, содержащей поля X (экранная координата по горизонтали) и Y (экранная координата по вертикали)
LeftКоордината левого края диалогового окна, то же, что Position.X
TopКоордината верхнего края диалогового окна, то же, что Position.Y
OptionsМножество опций

Последний параметр Options — может содержать следующие свойства:


frDisableMatchCaseДелает недоступным индикатор С учетом регистра в диалоговом окне
frDisableUpDownДелает недоступными в диалоговом окне кнопки Вверх и Вниз группы Направление, определяющие направление поиска
frDisableWholeWordДелает недоступным индикатор Только слово целиком в диалоговом окне
frDownВыбирает кнопку Вниз группы Направление при открытии диалогового окна. Если эта опция не установлена, то выбирается кнопка Вверх
frFindNextЭта опция включается автоматически, когда пользователь в диалоговом окне щелкает на кнопке Найти далее, и выключается при закрытии диалога
frHideMatchCaseУдаляет индикатор С учетом регистра из диалогового окна
frHideWholeWordУдаляет индикатор Только слово целиком из диалогового окна
frHideUpDownУдаляет кнопки Вверх и Вниз из диалогового окна
frMatchCaseЭтот флаг включается и выключается, если пользователь включает и выключает опцию С учетом регистра в диалоговом окне. Можно установить эту опцию по умолчанию во время проектирования, чтобы при открытии диалога она была включена
frReplaceПрименяется только для ReplaceDialog. Этот флаг устанавливается системой, чтобы показать, что текущее (и только текущее) найденное значение FindText должно быть заменено значением ReplaceText
frReplaceAllПрименяется только для ReplaceDialog. Этот флаг устанавливается системой, чтобы показать, что все найденные значения FindText должны быть заменены значениями ReplaceText
frShowHelpЗадает отображение кнопки Справка в диалоговом окне
frWholeWordЭтот флаг включается и выключается, если пользователь включает и выключает опцию Только слово целиком в диалоговом окне. Можно установить эту опцию по умолчанию во время проектирования, чтобы при открытии диалога она была включена

Сами по себе компоненты FindDialog и ReplaceDialog не осуществляют ни поиска, ни замены. Они только обеспечивают интерфейс с пользователем. А поиск и замену надо осуществлять программно. Для этого можно пользоваться событием OnFind, происходящим, когда пользователь нажал в диалоге кнопку Найти далее, и событием OnReplace, возникающим, если пользователь нажал кнопку Заменить или Заменить все. В событии OnReplace узнать, какую именно кнопку нажал пользователь, можно но значениям флагов frReplace и frReplaceAll.

Поиск заданного фрагмента легко проводить, пользуясь функцией Object Pascal Pos, которая определена в модуле System следующим образом:

function Pos(Substr: string; S: string): Byte;
где S — строка, в которой ищется фрагмент текста, a Substr — искомый фрагмент. Функция возвращает позицию первого символа первого вхождения искомого фрагмента в строку. Если Substr в S не найден, возвращается 0.

Для организации поиска нам потребуется еще две функции: Сору и AnsiLowerCase. Первая из них определена как:

function Copy(S: string; Index, Count: Integer): string;

Она возвращает фрагмент строки S, начинающийся с позиции Index и содержащий число символов, не превышающее Count. Функция AnsiLowerCase, определенная как

function AnsiLowerCase(const S: string): string;
возвращает строку символов S, переведенную в нижний регистр.

Теперь мы можем рассмотреть пример организации поиска. Пусть в вашем приложении имеется компонент Memo1 и при выборе раздела меню MFind вы хотите организовать поиск в тексте, содержащемся в Memo1. Для упрощения задачи исключим опцию поиска только целых слов и опцию поиска вверх от положения курсора.

Программа, реализующая поиск, может иметь следующий вид:

var SPos: integer;
procedure TForm1.MFindClick(Sender: TObject);
begin
{запоминание позиции курсора}
SPos := Memo1.SelStart;
with FindDialog1 do
begin
{начальное значение текста поиска —
текст, выделенный в Memo1}
FindText := Memo1.SelText;
{позиционирование окна диалога внизу Memo1}
Position := Point(Form1.Left, Form1.Top +
Memo1.Top + Memo1.Height);
{удаление из диалога кнопок «Вверх», «Вниз»,
«Только слово целиком»}
Options := Options + [frHideUpDown, frHideWholeWord];
{выполнение}
Execute;
end;
end;
procedure TForm1.FindDialog1Find(Sender: TObject);
begin
with FindDialog1 do
begin
if frMatchCase in Options
{поиск с учетом регистра}
then Memo1.SelStart := Pos(FindText,
Copy(Memo1.Lines.Text, SPos + 1,
Length(Memo1.Lines.Text))) + Spos — 1
{поиск без учета регистра}
else Memo1.SelStart := Pos(AnsiLowerCase(FindText),
AnsiLowerCase(Copy(Memo1.Lines.Text, SPos + 1,
Length(Memo1.Lines.Text)))) + Spos — 1;
if Memo1.SelStart >= Spos
then
begin
{выделение найденного текста}
Memo1.SelLength := Length(FindText);
{изменение начальной позиции поиска}
SPos := Memo1.SelStart + Memo1.SelLength + 1;
end
else if MessageDlg(
'Текст "'+FindText+'" не найден. Продолжать диалог?',
mtConfirmation, mbYesNoCancel, 0) <> mrYes
then CloseDialog;
end;
Memo1.SetFocus;
end;

В программе вводится переменная SPos, сохраняющая позицию, начиная с которой надо проводить поиск.

Процедура MFindClick вызывает диалог, процедура FindDialog1Find обеспечивает поиск с учетом или без учета регистра в зависимости от флага frMatchCase. После нахождения очередного вхождения искомого текста этот текст выделяется в окне Memo1 и управление передается этому окну редактирования. Затем при нажатии пользователем в диалоговом окне кнопки Найти далее, поиск продолжается в оставшейся части текста. Если искомый текст не найден, делается запрос пользователю о продолжении диалога. Если пользователь не ответил на этот запрос положительно, то диалог закрывается методом CloseDialog.

В дополнение к приведенному тексту полезно в обработчики событий OnClick и OnKeyUp компонента Memo1 ввести операторы

SPos := Memo1.SelStart;

Это позволяет пользователю во время диалога изменить положение курсора в окне Memo1. Это новое положение сохранится в переменной SPos и будет использовано при продолжении поиска.

При реализации команды Заменить приведенные выше процедуры можно оставить теми же самыми, заменив в них FindDialog1 на ReplaceDialog1. Дополнительно можно написать процедуру обработки события OnReplace компонента ReplaceDialog1:

procedure TForm1.ReplaceDialog1Replace(Sender: TObject);
begin
if Memo1.SelText <> ''
then Memo1.SelText := ReplaceDialog1.ReplaceText;
if frReplaceAll in ReplaceDialog1.Options
then ReplaceDialog1Find(Self);
end;

Этот код производит замену выделенного текста и, если пользователь нажал кнопку Заменить все, то продолжается поиск вызовом уже имеющейся процедуры поиска ReplaceDialog1Find*. Если же пользователь нажал кнопку Заменить, то производится только одна замена и для продолжения поиска пользователь должен нажать кнопку Найти далее.


* Предлагаемый автором алгоритм принажатии на кнопку Заменить все заменяет только одно значение и находит следующее. На наш взгляд такия действия более логично было бы задать кнопке Заменить, а для Заменить все организовать цикл. Причем такой цикл проще осуществить в процедуре ReplaceDialog1Find. В приведенном ниже коде кроме того введена локальная переменная ss, так как свойству SelStart нельзя присваивать отрицательные значения.
procedure TForm1.ReplaceDialog1Find(Sender: TObject);
var ss: integer;
last: Boolean;
st: string;
begin
with ReplaceDialog1 do begin
if (frFindNext in Options) then
{изменение начальной позиции поиска}
SPos := Memo1.SelStart + Memo1.SelLength + 1;
last := not (frReplaceAll in Options);
repeat
if frMatchCase in Options
{поиск с учетом регистра}
then ss := Pos(FindText,
Copy(Memo1.Lines.Text, SPos + 1,
Length(Memo1.Lines.Text))) + Spos — 1
{поиск без учета регистра}
else ss := Pos(AnsiLowerCase(FindText),
AnsiLowerCase(Copy(Memo1.Lines.Text, SPos + 1,
Length(Memo1.Lines.Text)))) + Spos — 1;
if ss >= Spos then
begin
{выделение найденного текста}
Memo1.SelStart := ss;
Memo1.SelLength := Length(FindText);
if (frReplaceAll in Options) then begin
{замена}
Memo1.SelText := ReplaceDialog1.ReplaceText;
{изменение начальной позиции поиска}
SPos := Memo1.SelStart + Memo1.SelLength + 1;
end;
end
else
begin
if (frReplaceAll in Options) or (frReplace in Options) then
st := 'Замена "' + FindText + '" на "' + ReplaceText + '" закончена'
else st := 'Текст "' + FindText + '" не найден';
if MessageDlg(st + '. Продолжать диалог?',
mtConfirmation, mbYesNoCancel, 0) <> mrYes
then CloseDialog;
last:=true;
end;
until last;
end;
end;
procedure TForm1.ReplaceDialog1Replace(Sender: TObject);
begin
if (frReplace in ReplaceDialog1.Options) and (Memo1.SelText <> '')
then Memo1.SelText := ReplaceDialog1.ReplaceText;
ReplaceDialog1Find(Self);
end;
 — Примечание разработчика электронной версии.
к началу страницы