.
Delphi. Урок 4.3. Продолжаем писать текстовый редактор (локальные переменные в Delphi).
Автор megabax   
22.06.2009 г.
В предыдущей статье я рассказал о том

Продолжаем писать текстовый редактор (локальные переменные в Delphi).

 

 

В предыдущей статье я описал, как сохранить отредактированный текст в файла, а так же рассказал о некоторых возможностях TForm. Теперь пришло время наполнить содержимым ветку меню «Редактирование». Занесем туда новый пункт меню «Поиск и замена» идентификатором itReplace. Теперь положим на форму диалог поиска и замены*.

 

компоненты Delphi книги скачать бесплатно

 

В обработчик OnClick пункта меню itReplace добавим строку:

 

  rdReplaceDialog.Execute;

 

Все, теперь при выборе этого пункта будет запускаться вот такой диалог*:

 

компоненты Delphi книги скачать бесплатно

 

Правда, пока он у нас еще ничего не дает. Можно хоть сколько жать на «Найти далее» или «Заменить», ничего не произойдет. Поэтому, нам нужно написать обработчики события для диалога поиска и замены.

Начнем с OnFind.  Вместо Пары begin end нам нужно поместить вот такой текст:

 

var

  I, J, PosReturn, SkipChars: Integer;

begin

  for I := 0 to moText.Lines.Count do

  begin

    PosReturn := Pos(rdReplaceDialog.FindText,moText.Lines[I]);

    if PosReturn <> 0 then {found!}

    begin

      SkipChars := 0;

      for J := 0 to I - 1 do

        SkipChars := SkipChars + Length(moText.Lines[J]);

       

      SkipChars := SkipChars + (I*2);

      SkipChars := SkipChars + PosReturn - 1;

 

      moText.SetFocus;

      moText.SelStart := SkipChars;

      moText.SelLength := Length(rdReplaceDialog.FindText);

      Break;

    end;

  end;

end;

 

Несколько пояснений к тексту программы. Оператором «var» мы объявляем локальные переменные, которые действуют только в пределах данной процедуры.  Есть еще глобальные переменные, которые действуют в пределах всей программы. Их изменение в любом месте приведет к изменению именно этих переменных. А вот если в теле подпрограммы объявить локальную переменную с таким же именем, как и глобальная, то, изменяя эту переменную, мы глобальную не меняем.

В качестве аналогии возьмем имена людей. Пусть в некотором классе учиться мальчик Вова. В другом классе тоже учится мальчик Вова. Но это два совершенно разных человека (локальные переменные). А еще есть в этой школе трудный ученик Вова, которого знают все. Этот общеизвестной Вова – глобальная переменна. Поэтому, когда в классе нету Вовы, то если кто нибудь заведет речь о Вове, то все будут знать, кого имеют в виду. А как быть в том случае, когда речь о Вове идет в классе, где уже есть Вова? Правильно, все будут думать про того Вову, который уже учиться в этом классе. Точно так же и с локальным переменными.

Сразу возникает вопрос, как быть, когда нужно сказать не про того Вову, который учиться в нашем классе, а про другого, которого знает вся школа? Очень просто, нужно просто указать в разговоре, что речь идет о Вове, которого знает вся школа.

Точно так же мы поступим и в программе. Укажем перед переменной имя модуля, в котором она объявлена. Но пока мы не будем заворачиваться над этим вопросов, у нас еще программа не протестирована. Давайте запустим ее и проверим, действительно ли работает поиск.

 компоненты Delphi книги скачать бесплатно

 

Как выяснилось, поиск у нас работает только на  первое вхождение. А вот дальше, сколько не жми «Найти далее», все равно выделенным остается только первым найденный фрагмент, хотя, как мы видим из скриншота, слов «Регистр», который, собственно говоря мы ищем, в тексте несколько.

Что бы исправить эту ошибку, разберемся для начала, как вообще работает программа.

И так, что такое begin, вы наверное уже догадались, это оператор означает начало блока программы. Далее идет for. Это цикл со счетным количество повторений. В данном случае после каждого прохода цикла переменная I увеличивается на единицу, начиная от 0 кончая значением moText.Lines.Count

Поясню подробно конструкцию moText.Lines.Count. Сначала у нас идет имя объекта moText. Через точку мы обращаемся к его свойству «Lines». Самое это свойство так же является объектом и имеет свойства. К ним мы тоже обращается через точку.

После конструкции fordo у нас идет пара beginend. Между ними блок кода, который будет выполнятся столько раз, сколько будет бежать цикл. Разберем этот блок подробно:

 

PosReturn := Pos(rdReplaceDialog.FindText,moText.Lines[I]);

 

Этой командой (Pos) мы ищем вхождение куска текста в другой текст. В частности, искомая строка rdReplaceDialog.FindText – это текст, который мы ввели в диалоге. Текст, в котором ищем – очередная строка текстового поля moText.

Если мы ничего не нашли, то функция вернет 0. Поэтому следующая команда это структура ifthen – оператор условия (ветвления).  Если результат поиска у нас не нулевой (знак «<>» означает «не равно»), то выполняем блок, идущий после then и заключенный между begin end.  

 

Этот блок делает следующее:

 

1. Вычисляет количество символов до строки, в которой найдена искомая комбинация символов.  Это делают операторы

 

      SkipChars := 0;

      for J := 0 to I - 1 do

        SkipChars := SkipChars + Length(moText.Lines[J]);

 

В частности, после до не стоит begin end, следовательно, в цикле выполняется только одна команда SkipChars := SkipChars + Length(moText.Lines[J]);, которая просто увеличивает содержимое переменной SkipChars на длину очередной строки (функция Length).

 

2. Затем мы добавляем к содержимому этой переменной количество байтов, содержащиеся в маркерах конца строки. Это делают вот такие строки.

 

      SkipChars := SkipChars + (I*2);

 

Байт соответствует одному символу из 256-ти. Этим символом может быть буква, цифра, а так же специальный, невидимый символ, обозначающий определенной действие, например. В многострочной тексте каждая строка заканчивается одним двумя такими символами, а именно, возврат каретки и перевод строк.

 

3. Далее, мы прибавляем количество символов с начала строки до искомой комбинации

 

      SkipChars := SkipChars + PosReturn - 1;

 

4. И, уже после того, как вычислили номер символа,  с которого начинается искомый фрагмент текста, помещаем этот фрагмент текста как выделенный:

 

      moText.SetFocus;

      moText.SelStart := SkipChars;

      moText.SelLength := Length(rdReplaceDialog.FindText);

 

5. Прерываем цикл командой break. Иными словами, как только мы нашли искомую комбинацию, нам больше не нужно крутить цикл, и он завершается, даже если не проделано заданное число повторений.

 

Соответственно, если ни в одной строке искомая комбинация не выполнена, то и вышеперечисленные действия так же не будут выполнятся.

 

Каким образом мы сделаем продолжение поиска? Ясно дело, нам нужно как то запоминать позицию поиска, что бы в следующий раз поиск начать с нее.

 

Немного модифицируем объявление класса формы (добавленные строки выделены красным)

 

  TfrmMain = class(TForm)

    moText: TMemo;

    mmMainMenu: TMainMenu;

    itFile: TMenuItem;

    itEdit: TMenuItem;

    itOpen: TMenuItem;

    odOpenDialog: TOpenDialog;

    sdSaveDialog: TSaveDialog;

    itSave: TMenuItem;

    itReplace: TMenuItem;

    rdReplaceDialog: TReplaceDialog;

    procedure itOpenClick(Sender: TObject);

    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);

    procedure itSaveClick(Sender: TObject);

    procedure itReplaceClick(Sender: TObject);

    procedure rdReplaceDialogFind(Sender: TObject);

    procedure FormCreate(Sender: TObject);

  private

    { Private declarations }

    FI, FPosReturn:integer;

  public

    { Public declarations }

  end;

 

Текст, который мы добавили

 

FI, FPosReturn:integer;

 

Означает объявления дополнительных полей класса формы. Это еще не свойства, а всего лишь внутренние переменные. В программе мы можем менять их значения. В них то как раз мы и будем запоминать позицию поиска.

В обработчик события FormCreate нашей формы добавим строки:

  FI:=0;

  FPosReturn:=0;

 

Которые обнуляют наш счетчик поиска.

Ну, а теперь изменим обработчик события OnFind нашего диалога поиска и замены:

 

procedure TfrmMain.rdReplaceDialogFind(Sender: TObject);

var

  I, J, PosReturn, SkipChars: Integer; S:string;

begin

  for I := FI to moText.Lines.Count do

  begin

    if FPosReturn=0 then s:=moText.Lines[I] else s:=copy(moText.Lines[I],FPosReturn+1,Length(moText.Lines[I])-FPosReturn+1);

    PosReturn := Pos(rdReplaceDialog.FindText,s);

    if PosReturn <> 0 then {found!}

    begin

      SkipChars := 0;

      for J := 0 to I - 1 do

        SkipChars := SkipChars + Length(moText.Lines[J]);

 

      SkipChars := SkipChars + (I*2);

      SkipChars := SkipChars + PosReturn - 1 + FPosReturn;

      FPosReturn:=FPosReturn+PosReturn;

 

      moText.SetFocus;

      moText.SelStart := SkipChars;

      moText.SelLength := Length(rdReplaceDialog.FindText);

      FI:=I;

      Break;

    end else FPosReturn:=0;

  end;

 

end;

 

И так, что же у нас поменялось. Поиск мы осуществляем не с нулевой строки, а стой, с которой закончили в прошлый раз:

 

for I := FI to moText.Lines.Count do

 

Если у нас первый поиск, то это как раз и будет нулевая строка.

 

Выражением

 

    if FPosReturn=0 then s:=moText.Lines[I] else s:=copy(moText.Lines[I],FPosReturn+1,Length(moText.Lines[I])-FPosReturn+1);

 

Мы проверяем, первый ли это у нас поиск. Если да то берем для поиска всю строку, если нет – то только ее часть, начиная с символа, следующего за началом найденного в прошлый раз слова.

 

Командой

 

      FPosReturn:=FPosReturn+PosReturn;

 

Мы запоминаем позицию поиска,  а командой

 

      FI:=I;

 

Запоминаем строку поиска.

 

Мы усовершенствовали наш текстовый редактор, заставив его искать не первое попавшее вхождение слов, а каждое. На самом деле его еще можно усовершенствовать, но об этом в следующей статье.

 

 


Скриншоты, помеченные знаком * , являются цитатами и иллюстрациями   программного продукта "Delphi", авторское право на который принадлежит "Borland Software Corporation".


 

 

Последнее обновление ( 01.07.2012 г. )