воскресенье, 3 октября 2010 г.

Каждому списку - по фильтру.

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

Одним из вариантов такого поиска является динамическая фильтрация списка по ключевым словам. Динамическая - значит налету, прямо в процессе ввода ключевых слов пользователем. Скажем, у вас есть список сотрудников, в котором отображаются: идентификатор, ФИО, год рождения, адрес. Вы набираете "77 кр". Список сокращается. В нем остаются только те сотрудники, в данных у которых встречаются число 77 и/или слово "кр". Например, с сотрудники с фамилиями "Кравцов", "Окрошкин", проживающие на улице "Крапивина", с почтовым индексом 667770, с 1977 годом рождения, c идентификаторами 77, 777, 6772 и т.п. Поиск может осуществляться по принципу '77 ИЛИ кр' или '77 И кр' - как удобнее.

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

Конечно, можно сделать специальную форму поиска с четырьмя полями: ФИО, адрес, год рождения, идентификатор. Да еще поместить ее в отдельное окно. Можно.. Но зачем? Такие формы оправданы только в случае больших массивов данных, когда поиск ведется с помощью SQL-запросов и для ускорения поиска используются индексы по полям таблиц базы данных. Если же список целиком сидит в памяти (десяток, сотня, несколько тысяч строк), то достаточно одной строки поиска, которая легко помещается рядом со списком. Согласитесь, что место для одного дополнительного поля ввода над/под списком выделить можно всегда.

Теперь о реализации такого фильтра на Delphi. Здесь два важных момента. Во-первых, фильтр должен прикручиваться к любой форме буквально одной-двумя строками кода. Тогда его будет удобно использовать. Во-вторых, фильтр должен быть ориентирован на работу TDataSet, т.к. именно этот класс (и его наследники) используется для хранения списков в клиентских приложениях для работы с базами данных.

Для себя я реализовал такой фильтр в виде модуля FastFilter. Делать отдельный компонент я не стал - фильтр должен годится и для приложений, использующих не только стандартный TEdit, но и сторонние компоненты ввода.

Чтобы прикрутить фильтр к форме достаточно создать экземпляр класса
TFastFilterCommon:
TForm1 = class(TForm)  
  ...  
  private    
   m_F: TFastFilterCommon;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin 
  m_F := TFastFilterCommon.Create(  
    editFilter //поле ввода фильтра  
    , dataSetToFilter //фильтруемый DataSet  
    , false //'a b' означает 'a ИЛИ b' (а не 'a И b') 
  ); 
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin  
  m_F.Free;
end;
Фильтрация будет вестись по всем полям DataSet. Если нужна фильтрация только по выбранным полям, то нужно перечислить эти поля в конструкторе:
m_F := TFastFilterCommon.Create(  
  editFilter //поле ввода фильтра  
  , dataSetToFilter //фильтруемый DataSet  
  , ['Name', 'Surname', 'Address'] //поля для фильтрации
  , false //'a b' означает 'a ИЛИ b' (а не 'a И b') 
); 
Класс содержит внутренний таймер, определяющий задержку между завершением ввода и началом фильтрации. Пользователь набрал слово, прошло 0.2 сек, если пользователь ничего больше не нажимает, начинается фильтрация. Если пользователь не хочет ждать, он может нажать Ctrl+Enter и фильтрация начнется мгновенно.

В фильтре работают клавиши Up и Down. Они позволяют перемещаться по датасету не выходя из поля ввода.

Модуль FastFilter содержит два класса: TFastFilter и TFastFilterCommon. Первый содержит только статические функции, которые реализуют необходимые обработчики в общем виде. Второй заточен под стандартный TEdit. Если используется нестандартный вариант TEdit, то потребуется создать дополнительный класс TFastFilterXXX (или вызывать функции TFastFilter прямо из формы).

Важное ограничение: FastFilter предполагает, что все данные, по которым нужно вести фильтрацию, содержатся в отображаемом на экране DataSet. Но так бывает далеко не всегда. Пример - фильтрация списка клиентов по номерам телефонов. Номера телефонов хранятся, естественно, в другом DataSet, отдельно от списка клиентов, т.к. у каждого клиента может быть по несколько телефонов. В подобных случаях стандартный TFastFilterCommon потребует доработки.

Исходные коды FastFilter и демонстрационного приложения.

Browse source codes,
download source codes, view short description in English.

Комментариев нет:

Отправить комментарий