:: MVP ::
|
|
:: RSS ::
|
|
|
Продолжая тему автоматизации процесса разработки, начатую в статье "Автоматизация
процесса создания дочерних форм", на этот раз поговорим про автоматизацию настроек параметров фильтрации (например, при работе
с базами данных). Приложения, активно работающие с базами данных (например, АБС), могут насчитывать десятки (а то и сотни) форм.
Данные, в том виде, в котором они хранятся в базе, практически никогда не бывают нужны, поэтому для их выборки применяется фильтры.
Фильтрация – способ поиска подмножества данных в соответствии с заданными условиями. Количество условий в фильтре
носит произвольный характер, и может варьироваться от однако, до нескольких десятков (с очень большими фильтрами
работать неудобно, и в своей практике я их не встречал).
В приложении фильтр может выглядеть примерно так:
Логика работы такого фильтра очевидна - изменяя состояние того или иного чекбокса мы включаем или отключаем те или
иные условия фильтрации. При этом для повышения комфортности пользователя при работе с таким фильтром, элементы
интерфейса, связанные с тем или иным чекбоксом нужно переводить в состояние enabled/disabled, в зависимости от
состояния соответствующего чекбокса. В итоге за визуальную часть работы такого фильтра отвечает примерно такой код:
procedure TForm1.CheckBox1Click(Sender: TObject);
begin
Edit1.Enabled := CheckBox1.Checked;
end;
procedure TForm1.CheckBox2Click(Sender: TObject);
begin
Edit2.Enabled := CheckBox2.Checked;
end;
procedure TForm1.CheckBox3Click(Sender: TObject);
begin
Edit3.Enabled := CheckBox3.Checked;
end;
procedure TForm1.CheckBox4Click(Sender: TObject);
begin
CheckBox1.Enabled := CheckBox4.Checked;
end;
procedure TForm1.CheckBox5Click(Sender: TObject);
begin
CheckBox2.Enabled := CheckBox5.Checked;
end;
procedure TForm1.CheckBox6Click(Sender: TObject);
begin
DateTimePicker1.Enabled := CheckBox6.Checked;
end;
procedure TForm1.CheckBox7Click(Sender: TObject);
begin
DateTimePicker2.Enabled := CheckBox7.Checked;
end;
procedure TForm1.CheckBox8Click(Sender: TObject);
begin
DateTimePicker3.Enabled := CheckBox8.Checked;
end;
|
Всего 8 условий, а уже как громоздко! И ведь нужно не забыть о первоначальной ручной синхронизации в дизайнере между
свойством Checked чекбокса и свойством Enabled соответствующего контрола. Конечно, можно сделать это и кодом, но тогда
объем кода, который нужно поддерживать, раздуется еще больше... Ужас.
Конечно, выше приведенный код можно оптимизировать:
procedure TForm1.CheckBox1Click(Sender: TObject);
begin
case (Sender as TCheckBox).Tag of
0: Edit1.Enabled := (Sender as TCheckBox).Checked;
1: Edit2.Enabled := (Sender as TCheckBox).Checked;
2: Edit3.Enabled := (Sender as TCheckBox).Checked;
3: CheckBox1.Enabled := (Sender as TCheckBox).Checked;
4: CheckBox2.Enabled := (Sender as TCheckBox).Checked;
5: DateTimePicker1.Enabled := (Sender as TCheckBox).Checked;
6: DateTimePicker2.Enabled := (Sender as TCheckBox).Checked;
7: DateTimePicker3.Enabled := (Sender as TCheckBox).Checked;
end;
end;
|
Уже лучше, но нужно не забывать правильно расставлять теги у чекбоксов. Но все усложняется, если
фильтр имеет многоуровневую структуру.
А ведь таких форм десятки, кому захочется поддерживать столько никчемного кода?
Попробуем это исправить. В условиях реальной разработки наверное лучше сделать компонент, унаследованный
от TCheckBox, но в демонстрационном примере я воспользуюсь грязным хаком с
подменой класса.
Интерфейсная часть выглядит следующим образом:
type
TCheckBox = class(Vcl.StdCtrls.TCheckBox)
private
[weak] FLinkedObject: TControl;
FLinkedObjectsList: TList<TControl>;
procedure SetLinkedObject(const Value: TControl);
procedure NotifyEvent(Sender: TObject; const Item: TControl; Action: TCollectionNotification);
protected
procedure Click; override;
procedure Toggle; override;
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
property LinkedObject: TControl read FLinkedObject write SetLinkedObject;
property LinkedObjectsList: TList<TControl> read FLinkedObjectsList;
end;
|
Два свойства LinkedObject и LinkedObjectsList предназначены для связи нашего чекбокса с "контролируемыми"
компонентами. Если чекбокс контролирует один компонент, его можно "положить" в свойство LinkedObject, если
объектов несколько, тогда LinkedObjectsList вам в помощь. Хотя никто не мешает распределить "контролируемые"
компоненты по двум свойствам сразу.
Когда нужно контролировать несколько объектов? Во-первых, это может быть несколько радиокнопок (вполне логичный
вариант). Во-вторых, это может быть несколько чекбоксов при многоуровневой организации фильтра.
В момент связывания с чекбоксом компонент должен синхронизировать свое состояние (enabled/disabled), это
происходит в событии NotifyEvent.
procedure TCheckBox.NotifyEvent(Sender: TObject; const Item: TControl;
Action: TCollectionNotification);
begin
if Action = cnAdded then
begin
Item.Enabled := Self.Checked;
if Item is TCheckBox then
(Item as TCheckBox).Click;
end;
end;
|
Перегруженный метод Notification используется для корректного освобождения ссылок на объекты,
которые по каким либо причинам прекратили свое существование во время работы приложения.
procedure TCheckBox.Notification(AComponent: TComponent; Operation: TOperation);
var
i: Integer;
begin
inherited;
if (Operation = opRemove) and (not Application.Terminated) then
begin
if AComponent = FLinkedObject then
FLinkedObject := nil;
for i := FLinkedObjectsList.Count-1 downto 0 do
if FLinkedObjectsList[i] = AComponent then
begin
FLinkedObjectsList.Delete(i);
Break;
end;
end;
end;
|
Два перегруженных метода Click и Toggle реагируют на изменение состояния чекбокса.
procedure TCheckBox.Click;
var
i: Integer;
begin
inherited;
if Assigned(FLinkedObject) then
begin
FLinkedObject.Enabled := Self.Checked and Self.Enabled;
if FLinkedObject is TCheckBox then
(FLinkedObject as TCheckBox).Click;
end;
for i := FLinkedObjectsList.Count-1 downto 0 do
begin
FLinkedObjectsList[i].Enabled := Self.Checked and Self.Enabled;
if FLinkedObjectsList[i] is TCheckBox then
(FLinkedObjectsList[i] as TCheckBox).Click;
end;
end;
procedure TCheckBox.Toggle;
begin
inherited;
Click;
end;
|
Наличие в строке Self.Checked and Self.Enabled проверки на Enabled не случайно. Переводя чекбокс в
состояние Checked = False, мы деактивируем "контролируемые" компоненты, среди которых также могут быть
чекбоксы со своими "контролируемыми" компонентами (многоуровневая организация). При этом чекбокс может
быть включен (Checked = True), но его "контролируемые" компоненты все равно должны быть отключены.
Именно тут и играет определяющую роль and Self.Enabled.
Теперь все, что осталось сделать, соединить "контролируемые" компоненты со своими чекбоксами (в случае
с многоуровневой организацией делать это нужно с самых нижних уровней, от частного к общему). В итоге
в приложении останется совсем немного простого кода, который очень легко поддерживать.
procedure TForm1.FormCreate(Sender: TObject);
begin
// Настройка фильтров идет от частного к общему
// Сначала настройка вложенных CheckBox'ов:
// "Edit", "Button", "ComboBox", "RadioButtons", "DateTimePicker"
cbEdit.LinkedObject := Edit1;
cbButton.LinkedObject := Button3;
cbComboBox.LinkedObject := ComboBox1;
cbDateTimePicker.LinkedObject := DateTimePicker1;
cbRadioButtons.LinkedObjectsList.Add(RadioButton1);
cbRadioButtons.LinkedObjectsList.Add(RadioButton2);
cbRadioButtons.LinkedObjectsList.Add(RadioButton3);
cbRadioButtons.LinkedObjectsList.Add(RadioButton4);
// Затем настройка общего CheckBox'а:
// "Включить фильтры"
cbFilters.LinkedObjectsList.Add(cbEdit);
cbFilters.LinkedObjectsList.Add(cbButton);
cbFilters.LinkedObjectsList.Add(cbComboBox);
cbFilters.LinkedObjectsList.Add(cbRadioButtons);
cbFilters.LinkedObjectsList.Add(cbDateTimePicker);
end;
|
На этом все, удобного вам программирования!
.: Пример к данной статье :.
|
При использовании материала - ссылка на сайт обязательна
|
|