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

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

Как говорится, любую проблему можно решить добавлением еще одного уровня абстракции. И такая возможность есть.

Как правило всю огромную кучу действий можно логически разделить на несколько категорий с более-менее похожим поведением. И грех этим не воспользоваться, благо Delphi позволяет создавать собственные типы «стандартных» действий.

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

На примере будет понятно. Представим, что часть действий необходимы для работы с данными и их свойство Enabled жестко привязано к тому, открыт соответствующий датасет или нет. Здесь можно использовать обычные TAction и писать каждый раз SomeAction.Enabled := DataSet.Active в событии OnUpdate, можно придумать что-то более изощренное, а можно просто написать соответствующий «стандартный» наследник TAction.

unit uDataAction;

interface

uses
  System.SysUtils, System.Classes, System.Actions, Vcl.ActnList, Data.DB;

type
  TDbAction = class(TAction)
  private
    FDataSet: TDataSet;
  public
    function Update: Boolean; override;
  published
    property DataSet: TDataSet read FDataSet write FDataSet;
  end;

procedure Register;

implementation

{ TDbAction }

function TDbAction.Update: Boolean;
begin
  if not (csDesigning in ComponentState) then
    self.Enabled := Assigned(FDataSet) and FDataSet.Active;

  Result := inherited Update;
end;

procedure Register;
begin
  RegisterActions('Custom Actions', [TDbAction], nil);
end;

end.

Все до очевидного просто. Этот класс унаследован от обычного TAction, в него добавлено поле DataSet и переопределена функция Update, где значение свойства Enabled по умолчанию привязывается к значению свойства DataSet.Active. Ключевой момент — процедура Register, которая позволит увидеть TDbAction в IDE. Этот модуль нужно добавить в пакет и установить его. И тогда, выбрав в диалоге редактирования TActionList пункт «New Standard Action…», можно увидеть новый TDbAction.

Далее с этим TDbAction можно работать точно так же, как и с привычными действиями, добавляя весь необходимый код в события OnUpdate и OnExecute. С той лишь разницей, что с состоянием датасета оно уже умеет работать.

Пара слов в заключение.

Если на форме используется только «чистый» TAction, то для действий с похожим поведением неизбежно в той или иной степени возникнет дублирование кода. Это влечет за собой самые разные негативные последствия. Данный подход этого недостатка лишен. Общий код вынесен в общий класс, благодаря чему он куда более управляем.