在设计时如何使子组件可用? [英] How to make subcomponent TAction-s available at design time?

查看:169
本文介绍了在设计时如何使子组件可用?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

In my custom component I created some TAction-s as subcomponents. They're all published, but I could not assign them at design time since they were not available through object inspector.

如何使它们被对象可迭代检查员?我已经尝试将所有操作的所有者设置为自定义组件(这是托管表单)的所有者,但没有成功。

How do you make them "iterable" by the object inspector? I have tried to set the Owner of the actions to the Owner of the custom component (which is the hosting Form) to no success.

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

It looks like Embarcadero changed Delphi IDE behaviour related with this problem. If you are using Delphi versions prior XE, you should use solution from my own answer. For XE and above, you should use solution from Craig Peterson.

编辑:我已经添加了自己的解决问题的答案,即在我的...中创建一个TCustomActionList实例自定义组件,并将其所有者设置为托管表单(自定义组件的所有者)。然而,我对这个解决方案并不满意,因为我认为TCustomActionList的实例是多余的。所以我仍然希望得到更好的解决方案。

I've added my own answer that solves the problem, i.e. by creating a TCustomActionList instance in my custom component and setting its Owner to the hosting form (owner of the custom component). However I am not too happy with this solution, since I think the instance of TCustomActionList is kind of redundant. So I am still hoping to get better solution.

编辑:添加代码示例

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;


推荐答案

据我所知,这样做。

简单的方法来做你想要的是创建新的独立操作,可以使用任何 TVrlFormCore 组件,并在 HandlesTarget 回调中设置目标对象。看看 StdActns.pas 的例子。当sommeone将您的组件删除在表单上时,这些操作将无法自动使用,但可以使用新建标准操作... 命令手动将其添加到操作列表中。有一个关于注册标准操作的好文章 here 。

The easy way to do what you want is to create new standalone actions that can work with any TVrlFormCore component and set the target object in the HandlesTarget callback. Take a look in StdActns.pas for examples. The actions won't be available automatically when sommeone drops your component on the form, but they can add them to their action list manually using the New Standard Actions... command. There's a good article on registering standard actions here.

如果您真的想自动创建您需要为表单设置操作所有者属性的操作,您需要设置名称属性。这一切都是必要的,但它确实引入了一些您需要解决的问题:

If you really want to auto-create the actions you need to set the action Owner property to the form and you need to set the Name property. That's all that's necessary, but it does introduce a bunch of issues you need to work around:


  • 表单拥有的操作,因此它将添加它们是其声明的发布部分,并将自动创建它们作为流过程的一部分。要解决这个问题,您可以通过覆盖动作的 WriteState 方法来禁用流,并跳过继承的行为。

  • 由于您没有写入状态,所以不会保留任何属性。为避免混淆您的用户,您应该切换,使操作从 TCustomAction 而不是 TAction 下降,因此它不会公开任何事情可能有办法使动作流正确,但您没有说是否需要。

  • 您需要注册免费通知,以防在表单释放该动作之前,您可以。

  • 如果有人丢失了多个组件,操作名称将会冲突。有多种方法来处理,但最干净的方法可能是覆盖组件的SetName方法,并将其名称用作动作名称的前缀。如果你这样做,你需要使用RegisterNoIcon与新的类,所以它们不会出现在表单上。

  • 在IDE的结构窗格中,操作将直接显示在窗体下,而不是像ActionList一样嵌套。我没有找到办法没有一个 SetSubComponent GetParentComponent / HasParent GetChildren 有任何效果,所以这可能是硬编码的行为。您可以从结构窗格中删除与组件分开的操作。

  • The form owns the actions so it will add them its declaration's published section and will auto-create them as part of the streaming process. To work around that you can just disable streaming by overwriting the action's WriteState method and skip the inherited behavior.
  • Since you aren't writing the state, none of the properties will be persisted. To avoid confusing your users you should switch make the actions descend from TCustomAction instead of TAction, so it doesn't expose anything. There may be way to make the action stream properly, but you didn't say whether it was necessary.
  • You need to register for free notifications in case the form frees the action before you can.
  • If someone drops more than one of your component on the action names will conflict. There's multiple ways to handle that, but the cleanest would probably be to override the component's SetName method and use its name as a prefix for the actions' names. If you do that you need to use RegisterNoIcon with the new class so they don't show up on the form.
  • In the IDE's Structure pane the actions will show up directly under the form, rather than nested like ActionList shows. I haven't found a way around that; none of SetSubComponent, GetParentComponent/HasParent, or GetChildren have any effect, so this may be hard-coded behavior. You can delete the action from the structure pane, separate from the component, too.

我确定可以改进,但是这个工作没有任何自定义属性编辑器:

I'm sure it can be improved, but this works without any custom property editors:

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;

这篇关于在设计时如何使子组件可用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆