Как сделать подкомпоненты TAction доступными во время разработки?

В своем пользовательском компоненте я создал несколько TAction-ов в качестве подкомпонентов. Все они опубликованы, но я не мог назначить их во время разработки, так как они не были доступны через инспектор объектов.

Как сделать их "повторяемыми" инспектором объектов? Я пытался установить владельца действий для владельца пользовательского компонента (который является формой хостинга), но безуспешно.

РЕДАКТИРОВАТЬ: похоже, Embarcadero изменил поведение Delphi IDE, связанные с этой проблемой. Если вы используете версии Delphi до XE, вы должны использовать решение из моего собственного ответа. Для XE и выше, вы должны использовать решение от Крейга Петерсона.

РЕДАКТИРОВАТЬ: я добавил свой собственный ответ, который решает проблему, т. Е. Путем создания экземпляра TCustomActionList в моем пользовательском компоненте и установки его владельца на форму размещения (владелец пользовательского компонента). Однако я не слишком доволен этим решением, так как я думаю, что экземпляр TCustomActionList отчасти избыточен. Поэтому я все еще надеюсь получить лучшее решение.

РЕДАКТИРОВАТЬ: Добавить пример кода

uses
  .., ActnList, ..;

type
  TVrlFormCore = class(TComponent)
  private
    FCancelAction: TBasicAction;
    FDefaultAction: TBasicAction;
    FEditAction: TBasicAction;
  protected
    procedure DefaultActionExecute(ASender: TObject); virtual;
    procedure CancelActionExecute(ASender: TObject); virtual;
    procedure EditActionExecute(ASender: TObject); virtual;
  public
    constructor Create(AOwner: TComponent); override;
  published
    property DefaultAction: TBasicAction read FDefaultAction;
    property CancelAction : TBasicAction read FCancelAction;
    property EditAction   : TBasicAction read FEditAction;
  end;

implementation

constructor TVrlFormCore.Create(AOwner: TComponent);
begin
  inherited;
  FDefaultAction := TAction.Create(Self);
  with FDefaultAction as TAction do
  begin
    SetSubComponent(True);
    Caption := 'OK';
    OnExecute := DefaultActionExecute;
  end;

  FCancelAction := TAction.Create(Self);
  with FCancelAction as TAction do
  begin
    SetSubComponent(True);
    Caption := 'Cancel';
    OnExecute := Self.CancelActionExecute;
  end;

  FEditAction := TAction.Create(Self);
  with FEditAction as TAction do
  begin
    SetSubComponent(True);
    Caption := 'Edit';
    OnExecute := Self.EditActionExecute;
  end;
end;

3 ответа

Решение

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

Самый простой способ сделать то, что вы хотите, это создать новые автономные действия, которые могут работать с любым TVrlFormCore компонент и установить целевой объект в HandlesTarget Перезвоните. Посмотрите в StdActns.pas для примеров. Действия не будут доступны автоматически, когда sommeone удалит ваш компонент в форме, но они могут добавить их в свой список действий вручную с помощью команды New Standard Actions.... Здесь есть хорошая статья о регистрации стандартных действий.

Если вы действительно хотите автоматически создавать действия, вам нужно установить действие Owner свойство формы, и вам нужно установить Name имущество. Это все, что необходимо, но это создает кучу проблем, которые нужно обойти:

  • Форма владеет действиями, поэтому она добавит их в опубликованный раздел своей декларации и автоматически создаст их как часть процесса потоковой передачи. Чтобы обойти это, вы можете просто отключить потоковую передачу, переписав действие WriteState метод и пропустить унаследованное поведение.
  • Поскольку вы не пишете состояние, ни одно из свойств не будет сохранено. Чтобы не вводить пользователей в заблуждение, вы должны переключиться, чтобы действия происходили из TCustomAction вместо TAction так что ничего не разоблачает. Может быть способ правильно создать поток действий, но вы не сказали, было ли это необходимо.
  • Вам нужно зарегистрироваться для бесплатных уведомлений на случай, если форма освободит действие, прежде чем вы сможете.
  • Если кто-то добавит более одного вашего компонента в имена действий, произойдет конфликт. Есть несколько способов справиться с этим, но самым чистым, вероятно, было бы переопределение метода SetName компонента и использование его имени в качестве префикса для имен действий. Если вы сделаете это, вам нужно использовать RegisterNoIcon с новым классом, чтобы они не отображались в форме.
  • В панели структуры IDE действия будут отображаться непосредственно под формой, а не вложенными, как показано в ActionList. Я не нашел способ обойти это; ни один из SetSubComponent, GetParentComponent / HasParent, или же GetChildren иметь какой-либо эффект, так что это может быть жестко запрограммированное поведение. Вы можете удалить действие из панели структуры, отдельно от компонента.

Я уверен, что это может быть улучшено, но это работает без каких-либо пользовательских редакторов свойств:

type
  TVrlAction = class(TCustomAction)
  protected
    procedure WriteState(Writer: TWriter); override;
  end;

  TVrlFormCore = class(TComponent)
  private
    FDefaultAction: TVrlAction;
  protected
    procedure DefaultActionExecute(ASender: TObject); virtual;
    procedure Notification(AComponent: TComponent;
      Operation: TOperation); override;
    procedure SetName(const NewName: TComponentName); override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  public
    property DefaultAction: TVrlAction read FDefaultAction;
  end;

procedure Register;

implementation

// TVrlAction

procedure TVrlAction.WriteState(Writer: TWriter);
begin
  // No-op
end;

// TVrlFormCore

constructor TVrlFormCore.Create(AOwner: TComponent);
begin
  inherited;
  FDefaultAction := TVrlAction.Create(AOwner);
  with FDefaultAction do
  begin
    FreeNotification(Self);
    Name := 'DefaultAction';
    Caption := 'OK';
    OnExecute := DefaultActionExecute;
  end;
end;

destructor TVrlFormCore.Destroy;
begin
  FDefaultAction.Free;
  inherited;
end;

procedure TVrlFormCore.DefaultActionExecute(ASender: TObject);
begin

end;

procedure TVrlFormCore.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  inherited;
  if Operation = opRemove then
    if AComponent = FDefaultAction then
      FDefaultAction := nil;
end;

procedure TVrlFormCore.SetName(const NewName: TComponentName);
begin
  inherited;
  if FDefaultAction <> nil then
    FDefaultAction.Name := NewName + '_DefaultAction';
end;

procedure Register;
begin
  RegisterComponents('Samples', [TVrlFormCore]);
  RegisterNoIcon([TVrlAction]);
end;

РЕДАКТИРОВАТЬ: Используйте это решение для версий Delphi до Delphi XE. Для XE и более поздних версий используйте ответ Крейга Петерсона (для которого не требуется избыточный экземпляр TCustomActionList).

После вмешательства и использования информации из ответа Крейга Петерсона я решил создать TCustomActionList в своем пользовательском компоненте. Пока что это единственный способ получить список действий в Object Inspector.

Вот код:

uses
  ..., ActnList, ...;

type
  TVrlAction=class(TCustomAction)
  protected
    procedure WriteState(Writer: TWriter); override;
  published
    property Caption;
  end;

  TVrlActionList=class(TCustomActionList)
  protected
    procedure WriteState(Writer: TWriter); override;
  end;

  TVrlFormCore = class(TVrlItemSource)
  protected
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
    procedure SetName(const NewName: TComponentName); override;
  public
    constructor Create(AOwner: TComponent); override;
  end;

implementation

{ TVrlAction }

procedure TVrlAction.WriteState(Writer: TWriter);
begin
end;

{ TVrlActionList }

procedure TVrlActionList.WriteState(Writer: TWriter);
begin
end;

{ TVrlFormCore }

constructor TVrlFormCore.Create(AOwner: TComponent);
begin
  inherited;
  FActions := TVrlActionList.Create(AOwner);

  FDefaultAction := TVrlAction.Create(AOwner);
  with FDefaultAction as TVrlAction do
  begin
    FreeNotification(Self);
    Caption := 'OK';
    OnExecute := DefaultActionExecute;
  end;
  FActions.AddAction(TContainedAction(FDefaultAction));

  FCancelAction := TVrlAction.Create(AOwner);
  with FCancelAction as TVrlAction do
  begin
    FreeNotification(Self);
    Caption := 'Cancel';
    OnExecute := Self.CancelActionExecute;
  end;
  FActions.AddAction(TContainedAction(FCancelAction));

  FEditAction := TVrlAction.Create(AOwner);
  with FEditAction as TVrlAction do
  begin
    FreeNotification(Self);
    Caption := 'Edit';
    OnExecute := Self.EditActionExecute;
  end;
  FActions.AddAction(TContainedAction(FEditAction));
end;

procedure TVrlFormCore.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  inherited;
  if Operation=opRemove then
  begin
    if AComponent = FMaster then
      FMaster := nil
    else if (AComponent is TVrlFormCore) then
      FDetails.Remove(TVrlFormCore(AComponent))
    else if AComponent=FDefaultAction then
      FDefaultAction := nil
    else if AComponent=FCancelAction then
      FCancelAction := nil
    else if AComponent=FEditAction then
      FEditAction := nil;
  end;
end;

procedure TVrlFormCore.SetName(const NewName: TComponentName);
begin
  inherited;
  if FActions<>nil then
    FActions.Name := NewName + '_Actions';

  if FDefaultAction <> nil then
    FDefaultAction.Name := NewName + '_DefaultAction';
  if FCancelAction <> nil then
    FCancelAction.Name := NewName + '_CancelAction';
  if FEditAction <> nil then
    FEditAction.Name := NewName + '_EditAction';
end;

Вы не можете назначить их, потому что они предназначены только для чтения:

property DefaultAction: TBasicAction read FDefaultAction; 
property CancelAction : TBasicAction read FCancelAction; 
property EditAction   : TBasicAction read FEditAction; 

Вы должны изменить интерфейс вашего класса на:

property DefaultAction: TBasicAction read FDefaultAction write FDefaultAction; 
property CancelAction : TBasicAction read FCancelAction write FCancelAction; 
property EditAction   : TBasicAction read FEditAction write FEditAction; 

или напишите соответствующий сеттер для каждого действия.

Редактировать:

То, что вам нужно, то

  1. реализовать ваши 3 пользовательских действия в качестве предопределенных действий (см. StdActns.pas для образцов).

  2. зарегистрировать их по телефону ActnList.RegisterActions, (См. Документацию RAD Studio)

  3. добавить в форму TActionList и / или TActionManager чтобы ваш Predefined Actions появляются в списке предопределенных действий в редакторе списка действий каждого потомка TControl.

Вы можете сделать обширный поиск в Google по этой теме и найти конкретный пример.

Другие вопросы по тегам