如何在设计时使子组件TAction-s可用?

时间:2011-12-28 05:06:03

标签: delphi custom-component taction

在我的自定义组件中,我创建了一些TAction-s作为子组件。它们都已发布,但我无法在设计时分配它们,因为它们无法通过对象检查器获得。

如何让对象检查员“迭代”它们?我试图将操作的所有者设置为自定义组件的所有者(托管表单),但没有成功。

编辑:看起来Embarcadero改变了与此问题相关的Delphi IDE行为。如果你在XE之前使用Delphi版本,你应该使用我自己答案的解决方案。对于XE及以上版本,您应该使用Craig Peterson的解决方案。

编辑:我添加了自己的答案来解决问题,即在我的自定义组件中创建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 个答案:

答案 0 :(得分:2)

据我所知,你不应该这样做。

执行所需操作的简便方法是创建可以与任何TVrlFormCore组件一起使用的新独立操作,并在HandlesTarget回调中设置目标对象。请查看 StdActns.pas 中的示例。当sommeone将组件放在表单上时,这些操作将无法自动使用,但是他们可以使用新标准操作... 命令手动将它们添加到操作列表中。有一篇关于注册标准行动的文章here

如果您确实想要自动创建操作,则需要将操作Owner属性设置为表单,并且需要设置Name属性。这就是必要的,但它确实引入了一些你需要解决的问题:

  • 表单拥有这些操作,因此它会将其声明的已发布部分添加到它们中,并将自动创建它们作为流式处理过程的一部分。要解决此问题,您可以通过覆盖操作的WriteState方法来禁用流式传输,并跳过继承的行为。
  • 由于您没有编写状态,因此不会保留任何属性。为避免让用户感到困惑,您应该切换操作从TCustomAction而不是TAction下降,因此不会暴露任何内容。可能有办法正确地制作动作流,但你没有说是否有必要。
  • 如果表单在您可以之前释放操作,您需要注册免费通知。
  • 如果有人在动作名称上删除了多个组件,则会发生冲突。有多种方法可以处理,但最干净的可能是覆盖组件的SetName方法并使用其名称作为操作名称的前缀。如果你这样做,你需要将RegisterNoIcon与新类一起使用,这样它们就不会出现在表单上。
  • 在IDE的“结构”窗格中,操作将直接显示在表单下,而不是像ActionList显示的那样嵌套。我没有找到解决办法; SetSubComponentGetParentComponent / HasParentGetChildren都不会产生任何影响,因此这可能是硬编码行为。您也可以从结构窗格中删除该操作,也可以从组件中删除。

我确信它可以改进,但这没有任何自定义属性编辑器:

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 :(得分:1)

编辑:在Delphi XE之前使用此解决方案用于Delphi版本。对于XE及更高版本,请使用Craig Peterson answer(不需要冗余的TCustomActionList实例)。

在干涉并使用来自Craig Peterson's answer的信息之后,我决定在我的自定义组件中实例化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;

答案 2 :(得分:0)

您无法分配它们,因为它们只能通过设计阅读:

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; 

或为每个动作编写适当的setter。

修改

那么你需要的是

  1. 将您的3个自定义操作实施为Predefined Actions(有关示例,请参阅StdActns.pas)。

  2. 通过调用ActnList.RegisterActions来注册它们。 (见RAD Studio documentation

  3. TActionList和/或TActionManager添加到表单中,以允许您的Predefined Actions出现在每个TControl后代的操作列表编辑器中的预定义操作列表中。

  4. 您可以在Google上对该主题进行大量搜索,并找到一些具体的示例。