Опубликован: 07.05.2010 | Уровень: специалист | Доступ: платный
Лекция 3:

Поиск, фильтрация и индексация таблиц

< Лекция 2 || Лекция 3: 12 || Лекция 4 >

Событие onFilterRecord

Это событие возникает при установке значения True в свойстве Filtered. Применение этого способа имеет большой плюс, и большой минус. Плюс в том что, сгенерировав это событие, программист получает возможность задать гораздо более сложные условия фильтрации. Минус же заключается в том, что проверка осуществляется перебором всех записей таблицы. Если таблица содержит очень много записей, процесс фильтрации может затянуться.

В событие передаются два параметра. Первый параметр - набор данных DataSet. С ним можно обращаться, как с именем фильтруемой таблицы. Второй параметр - логическая переменная Accept. Этой переменной нужно передавать результат проверки условия фильтра. Если переменная Accept возвращает False, то запись не принимается, и не будет отображаться. Соответственно, если возвращается True, то запись принимается. Рассмотрим этот способ на примере. Суть примера в следующем: необходимо отфильтровать записи по начальным (или всем) буквам фамилии, вводимым пользователем в поле Edit1. В предыдущем примере, если бы мы ввели букву "И", то вышли бы фамилии, первой буквой которых были бы "И" - "Я". Это не так удобно. Сделаем так, чтобы если пользователь введет букву "И", то останутся только фамилии, начинающиеся на "И". Если пользователь введет еще букву "в", то останутся только фамилии, начинающиеся на "Ив", и так далее. Поочередно вводя начальные буквы, пользователь доберется до нужных фамилий.

Для начала подготовим модуль данных. В нем нам потребуется создать глобальную переменную ed, чтобы мы могли передавать в нее текст из компонента Edit1:

var
  fDM: TfDM;
  ed: String; //текст из Edit1

Этого действия можно было бы избежать, если бы компонент ADOTable, подключенный к таблице LichData, располагался на главной форме. Но поскольку он находится в модуле данных, то и событие onFilterRecord будет сгенерировано в нем. А в этом событии нам нужно будет знать, что в данный момент находится в поле ввода Edit1. Именно для этого и нужна глобальная переменная ed.

Далее выделяем TLichData, то есть, компонент ADOTable, подключенный к таблице LichData. На вкладке Events (События) инспектора объектов найдите событие onFilterRecord и дважды щелкните по нему, сгенерировав процедуру. Полный листинг процедуры:

{onFilterRecord главной таблицы}
procedure TfDM.TLichDataFilterRecord(DataSet: TDataSet;
  var Accept: Boolean);
var
  s : String; //для значения поля
begin
  //получаем столько начальных букв из поля Фамилия,
  //сколько букв имеется в переменной ed:
  s := Copy(DataSet['Фамилия'], 1, Length(ed));
  //делаем проверку на совпадение значений:
  Accept := s = ed;
end;

Здесь в переменную s попадает столько начальных букв из поля "Фамилия", сколько букв содержит в данный момент компонент Edit1 на главной форме (эти буквы мы передадим в переменную ed чуть позже). Если текст в переменной s совпадает с текстом из поля Edit1, то переменной Accept присваивается True, и запись принимается. Иначе запись отфильтровывается. Не забудьте сохранить проект.

Далее перейдем в главную форму. Нужно удалить весь текст из события onChange компонента Edit1, и вписать новый:

{Изменение поиска по фамилии}
procedure TfMain.Edit1Change(Sender: TObject);
begin
  //если в поле Edit1 есть хоть одна буква,
  if Edit1.Text <> '' then begin
    fDM.TLichData.Filtered := False; //отключаем фильтр
    ed := Edit1.Text; //передаем в fDM новый текст
    fDM.TLichData.Filtered := True; //включаем фильтр
  end
  //если букв нет, фильтрацию отключаем:
  else fDM.TLichData.Filtered := False;
end;

Вот и все. Что же тут у нас происходит? Как только пользователь введет хоть одну букву, срабатывает событие onChange компонента Edit1. Если в Edit1 есть хоть одна буква, то мы вначале отключаем фильтрацию, отменяя прошлый фильтр, если он был. Затем мы передаем в глобальную переменную ed, расположенную в модуле данных, текст из Edit1. Далее снова включаем фильтр. При этом срабатывает событие onFilterRecord нашей таблицы, и в этом событии сравнивается текущее значение переменной ed и записей поля "Фамилия".

Сохраните проект, скомпилируйте и запустите программу. Проверьте, как фильтруются записи. Имея воображение, в событии onFilterRecord можно устраивать сколь угодно сложные проверки. Ведь в этом событии можно сравнивать не одно поле, а несколько, причем поля не обязательно должны быть индексированы. Вы можете проверять на совпадение хоть все поля таблицы, и поскольку фильтрация происходит путем перебора записей, то усложнение условных проверок заметно не замедлит этот процесс.

Использование индексов

Создание индексных полей обеспечивает сортировку данных по этим полям, что также облегчает поиск данных - ведь найти нужную фамилию или имя проще, если они отсортированы по алфавиту. Причем имеется возможность сортировать записи не только по возрастанию, но и по убыванию, хотя в большинстве руководств по Delphi эта возможность не описывается.

При создании в базе данных таблицы LichData мы указали поля "Фамилия" и "Имя", как индексированные. Этим и воспользуемся. Чтобы включить сортировку записей по полю "Фамилия", достаточно указать название поля в свойстве IndexFieldNames таблицы:

fDM.TLichData.IndexFieldNames := 'Фамилия';

Если требуется отключить сортировку, этому свойству присваивается пустая строка:

fDM.TLichData.IndexFieldNames := '';

Существует еще одна хитрость, о которой мало где можно прочитать. При индексировании таблицы к имени поля можно прибавить строку " ASC ", если мы желаем сортировать в возрастающем порядке (по умолчанию), или " DESC ", если сортируем в убывающем порядке. Сортировка " ASC " используется по умолчанию. Добавим возможность сортировки по фамилии и имени в нашу программу. Для этого на главную форму установим компонент TPopupMenu с вкладки Standard палитры компонентов. Дважды щелкните по компоненту, чтобы открыть редактор меню. Создадим следующие пункты:

Сортировать по фамилии
Сортировать по имени
Не сортировать
-
Обратная сортировка

В редакторе меню выделите пункт "Сортировать по фамилии" и измените свойство Name этого пункта на NFam. Пункт "Сортировать по имени" переименуйте в NImya. Пункт "Не сортировать" - в NNet, а пункт "Обратная сортировка" - в NObrat.

Вначале создайте обработчик событий для пункта "Не сортировать" (дважды щелкните по пункту). Тут все просто:

{Не сортировать}
procedure TfMain.NNetClick(Sender: TObject);
begin
  fDM.TLichData.IndexFieldNames := '';
end;

Для обработчика событий пункта "Сортировать по фамилии" код немного сложней:

{Сортировать по фамилии}
procedure TfMain.NFamClick(Sender: TObject);
var
  stype : String;
begin
  //выбираем направление сортировки:
  if NObrat.Checked then stype := ' DESC' //обратная сортировка
  else stype := ' ASC';  //прямая сортировка
  //сортируем
  fDM.TLichData.IndexFieldNames := 'Фамилия' + stype;
end;

Здесь, в зависимости от состояния свойства Checked пункта "Обратная сортировка" мы присваиваем строковой переменной stype либо значение ' ASC ' (прямая сортировка), либо ' DESC ' (обратная сортировка). Обратите внимание, что первым символом строки является пробел, он нужен, чтобы строка не "прилепилась" к названию поля. Далее мы устанавливаем индекс, указывая имя поля и добавляя к нему значение переменной stype. Таким образом, если Checked пункта "Обратная сортировка" имеет значение True (галочка установлена), мы добавляем ' DESC ', или ' ASC ' в противном случае. В результате имя индексного поля может быть либо "Фамилия ASC ", либо "Фамилия DESC ".

Сортировку по имени кодируем аналогичным образом:

{Сортировать по имени}
procedure TfMain.NImyaClick(Sender: TObject);
var
  stype : String;
begin
  //выбираем направление сортировки:
  if NObrat.Checked then stype := ' DESC'
  else stype := ' ASC';
  //сортируем
  fDM.TLichData.IndexFieldNames := 'Имя' + stype;
end;

Нам осталось указать код пункта всплывающего меню "Обратная сортировка". Тут нам нужно не просто установить галочку, если ее не было, но также проверить - есть ли сортировка по какому либо полю? Если таблица отсортирована, требуется ее пересортировать по этому же полю, но уже в обратном порядке. Вот код:

{Команда "Обратная сортировка"}
procedure TfMain.NObratClick(Sender: TObject);
begin
  //изменяем направление сортировки
  NObrat.Checked := not NObrat.Checked;
  //если сортировка по фамилии, пересортируем
  if Pos('Фамилия',fDM.TLichData.IndexFieldNames)>0 then
    fMain.NFamClick(Sender);
  //если сортировка по имени, пересортируем
  if Pos('Имя',fDM.TLichData.IndexFieldNames)>0 then
    fMain.NImyaClick(Sender);
end;

Как видите, мы использовали функцию Pos(), которая возвратит ноль, если в строке не найдено указанной подстроки, или номер символа, с которого эта подстрока начинается, если она есть. Нам нужно определить, не входит ли в имя индексного поля "Фамилия" или "Имя". Ведь к имени поля добавлена строка ' ASC ' или ' DESC ', так что прямая проверка

if fDM.TLichData.IndexFieldNames = 'Фамилия' then

результата не даст, в любом случае результатом было бы False. Ну а для пересортировки мы вызываем соответствующий пункт меню, чтобы не писать код сортировки еще раз, например:

fMain.NFamClick(Sender);

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

В свойстве PopupMenu верхней сетки DBGrid1 выберите созданное только что всплывающее меню, чтобы оно открывалось только над этой сеткой, сохраните проект, скомпилируйте его и опробуйте сортировку данных.

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

< Лекция 2 || Лекция 3: 12 || Лекция 4 >
Евгений Медведев
Евгений Медведев

В лекции №2 вставляю модуль данных. При попытке заменить name на  fDM выдает ошибку: "The project already contains a form or module named fDM!". Что делать? 

Анна Зеленина
Анна Зеленина

При вводе типов успешно сохраняется только 1я строчка. При попытке ввести второй тип вылезает сообщение об ошибке "project mymenu.exe raised exception class EOleException with message 'Microsoft Драйвер ODBC Paradox В операции должен использоваться обновляемый запрос'. 

Денис Попов
Денис Попов
Россия, Оренбург, Оренбургский государственный университет, 2015
Рустам Кадыров
Рустам Кадыров
Россия, Тирлян, Тирлянская школа №5, 2003