3

在我的自定义组件中,我创建了一些 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;
4

3 回答 3

2

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

做你想做的最简单的方法是创建可以与任何组件一起使用的新的独立操作,并在回调TVrlFormCore中设置目标对象。HandlesTarget查看StdActns.pas中的示例。当有人将您的组件放到表单上时,这些操作不会自动可用,但他们可以使用“新标准操作...”命令手动将它们添加到他们的操作列表中。这里有一篇关于注册标准动作的好文章。

如果您真的想自动创建您需要将 actionOwner属性设置为表单的操作,并且您需要设置该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;
于 2012-01-03T22:59:29.293 回答
1

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

在干预并使用Craig Peterson 的回答中的信息之后,我决定在我的自定义组件中实例化一个 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;
于 2012-01-05T08:35:55.697 回答
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; 

或为每个动作编写适当的设置器。

编辑:

那么你需要的是

  1. 将您的 3 个自定义操作实现为预定义操作(参见StdActns.pas示例)。

  2. 通过调用注册它们ActnList.RegisterActions。(参见RAD Studio 文档

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

您可以在 google 上广泛搜索该主题并找到一些具体示例。

于 2011-12-31T08:21:58.487 回答