Как сделать подкомпоненты TAction доступными во время разработки? - PullRequest
3 голосов
/ 28 декабря 2011

В своем пользовательском компоненте я создал несколько 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 ]

2 голосов
/ 04 января 2012

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

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

Если вы действительно хотите автоматически создавать действия, вам нужно установить свойство action 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;
1 голос
/ 05 января 2012

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

После того, как вмешались и использовали информацию из Ответ Крейга Петерсона , I 'я решил создать экземпляр 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;
0 голосов
/ 31 декабря 2011

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

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; 

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

Edit:

Тогда вам нужно

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...