delphicustom-componenttaction

How to make subcomponent TAction-s available at design time?


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.

EDIT: 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.

EDIT: 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.

EDIT: Add code sample

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;

Solution

  • As far as I can tell you're not supposed to do it that way.

    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:

    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;