Диалоги поиска и замены текста — компоненты 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;
Hosted by uCoz



Проект Delphi Space © Выпуск 2009- 2010