【问题标题】:How do I add support for actions in my component如何在我的组件中添加对操作的支持
【发布时间】:2011-10-11 23:33:22
【问题描述】:

我需要做些什么来为我的组件添加操作支持。它是一个按钮组件,但我想它对于任何组件类型都是相同的。任何信息或如何提供帮助。

【问题讨论】:

    标签: delphi components vcl taction


    【解决方案1】:

    这取决于您如何定义动作支持。有两种:

    • 组件的可能自定义的 Action 属性,可由 Action 组件分配
    • Action 组件本身。

    动作属性

    每个 TControl 后代都有一个 Action 属性,默认情况下,该属性的执行链接到鼠标左键单击。此链接由 ActionLink 管理。默认的 ActionLink 是 TControlActionLink 类型,它负责同步操作和控件的标题、提示、启用状态等。如果这个基础功能是你想要的,那么只需在你的组件类型声明中发布 Action 属性,Delphi 框架会处理所有的事情,比如 SergLU RD 已经回答了。

    如果您希望自己的 Action 属性链接到其他条件或事件(即 Click 除外),或者如果您想为组件的特定子元素(不是 TControl 后代)实现 Action 属性),然后您可以通过定义和实现自定义 ActionLink 类来实现自己的自定义 Action 属性。

    假设您的组件是某种具有列的网格,并且您希望每列都有一个动作属性,当用户单击列的标题时应该调用该属性。由于此类列可能属于 TCollectionItem 类型,因此该列类型默认没有操作属性。所以你必须自己实现一个。考虑下一个示例,它将操作的标题链接到列的标题,将操作的启用状态反向链接到列的只读属性等等......:

    unit Unit1;
    
    interface
    
    uses
      Classes, ActnList, SysUtils;
    
    type
      TColumn = class;
    
      TColumnActionLink = class(TActionLink)
      protected
        FClient: TColumn;
        procedure AssignClient(AClient: TObject); override;
        function IsCaptionLinked: Boolean; override;
        function IsEnabledLinked: Boolean; override;
        function IsOnExecuteLinked: Boolean; override;
        function IsVisibleLinked: Boolean; override;
        procedure SetCaption(const Value: String); override;
        procedure SetEnabled(Value: Boolean); override;
        procedure SetOnExecute(Value: TNotifyEvent); override;
        procedure SetVisible(Value: Boolean); override;
      end;
    
      TColumnActionLinkClass = class of TColumnActionLink;
    
      TColumn = class(TCollectionItem)
      private
        FActionLink: TColumnActionLink;
        FGrid: TComponent;
        FOnTitleClick: TNotifyEvent;
        FReadOnly: Boolean;
        FTitle: String;
        FVisible: Boolean;
        function DefaultTitleCaption: String;
        procedure DoActionChange(Sender: TObject);
        function GetAction: TBasicAction;
        function IsOnTitleClickStored: Boolean;
        function IsReadOnlyStored: Boolean;
        function IsVisibleStored: Boolean;
        procedure SetAction(Value: TBasicAction);
      protected
        procedure ActionChanged(Sender: TObject; CheckDefaults: Boolean); dynamic;
        procedure DoTitleClick; virtual;
        function GetActionLinkClass: TColumnActionLinkClass; virtual;
        property ActionLink: TColumnActionLink read FActionLink write FActionLink;
      public
        destructor Destroy; override;
        procedure InitiateAction; virtual;
      published
        property Action: TBasicAction read GetAction write SetAction;
        property OnTitleClick: TNotifyEvent read FOnTitleClick write FOnTitleClick
          stored IsOnTitleClickStored;
        property ReadOnly: Boolean read FReadOnly write FReadOnly
          stored IsReadOnlyStored;
        property Title: String read FTitle write FTitle;
        property Visible: Boolean read FVisible write FVisible
          stored IsVisibleStored;
      end;
    
    implementation
    
    { TColumnActionLink }
    
    procedure TColumnActionLink.AssignClient(AClient: TObject);
    begin
      FClient := TColumn(AClient);
    end;
    
    function TColumnActionLink.IsCaptionLinked: Boolean;
    begin
      Result := inherited IsCaptionLinked and (Action is TCustomAction) and
        (FClient.Title = TCustomAction(Action).Caption);
    end;
    
    function TColumnActionLink.IsEnabledLinked: Boolean;
    begin
      Result := inherited IsEnabledLinked and (Action is TCustomAction) and
        (FClient.ReadOnly <> TCustomAction(Action).Enabled);
    end;
    
    function TColumnActionLink.IsOnExecuteLinked: Boolean;
    begin
      Result := inherited IsOnExecuteLinked and
        (@FClient.OnTitleClick = @Action.OnExecute);
    end;
    
    function TColumnActionLink.IsVisibleLinked: Boolean;
    begin
      Result := inherited IsVisibleLinked and (Action is TCustomAction) and
        (FClient.Visible = TCustomAction(Action).Visible);
    end;
    
    procedure TColumnActionLink.SetCaption(const Value: string);
    begin
      if IsCaptionLinked then
        FClient.Title := Value;
    end;
    
    procedure TColumnActionLink.SetEnabled(Value: Boolean);
    begin
      if IsEnabledLinked then
        FClient.ReadOnly := not Value;
    end;
    
    procedure TColumnActionLink.SetOnExecute(Value: TNotifyEvent);
    begin
      if IsOnExecuteLinked then
        FClient.OnTitleClick := Value;
    end;
    
    procedure TColumnActionLink.SetVisible(Value: Boolean);
    begin
      if IsVisibleLinked then
        FClient.Visible := Value;
    end;
    
    { TColumn }
    
    procedure TColumn.ActionChanged(Sender: TObject; CheckDefaults: Boolean);
    begin
      if Sender is TCustomAction then
        with TCustomAction(Sender) do
        begin
          if not CheckDefaults or (Caption = DefaultTitleCaption) then
            FTitle := Caption;
          if not CheckDefaults or (not ReadOnly) then
            ReadOnly := not Enabled;
          if not CheckDefaults or not Assigned(FOnTitleClick) then
            FOnTitleClick := OnExecute;
          if not CheckDefaults or (Self.Visible = True) then
            Self.Visible := Visible;
          Changed(False);
        end;
    end;
    
    function TColumn.DefaultTitleCaption: String;
    begin
      Result := 'Column' + IntToStr(Index);
    end;
    
    destructor TColumn.Destroy;
    begin
      FreeAndNil(FActionLink);
      inherited Destroy;
    end;
    
    procedure TColumn.DoActionChange(Sender: TObject);
    begin
      if Sender = Action then
        ActionChanged(Sender, False);
    end;
    
    procedure TColumn.DoTitleClick;
    begin
      if Assigned(FOnTitleClick) then
        if (Action <> nil) and (@FOnTitleClick <> @Action.OnExecute) then
          FOnTitleClick(Self)
        else if FActionLink = nil then
          FOnTitleClick(Self)
        else if FActionLink <> nil then
          if (FGrid <> nil) and not (csDesigning in FGrid.ComponentState) then
          begin
            if not FActionLink.Execute(FGrid) then
              FOnTitleClick(Self);
          end
          else
            if not FActionLink.Execute(nil) then
              FOnTitleClick(Self);
    end;
    
    function TColumn.GetAction: TBasicAction;
    begin
      if FActionLink <> nil then
        Result := FActionLink.Action
      else
        Result := nil;
    end;
    
    function TColumn.GetActionLinkClass: TColumnActionLinkClass;
    begin
      Result := TColumnActionLink;
    end;
    
    procedure TColumn.InitiateAction;
    begin
      if FActionLink <> nil then
        FActionLink.Update;
    end;
    
    function TColumn.IsOnTitleClickStored: Boolean;
    begin
      Result := (FActionLink = nil) or not ActionLink.IsOnExecuteLinked;
    end;
    
    function TColumn.IsReadOnlyStored: Boolean;
    begin
      Result := (FActionLink = nil) or not FActionLink.IsEnabledLinked;
      if Result then
        Result := FReadOnly;
    end;
    
    function TColumn.IsVisibleStored: Boolean;
    begin
      Result := (FActionLink = nil) or not FActionLink.IsVisibleLinked;
      if Result then
        Result := not Visible;
    end;
    
    procedure TColumn.SetAction(Value: TBasicAction);
    begin
      if Value = nil then
        FreeAndNil(FActionLink)
      else
      begin
        if FActionLink = nil then
          FActionLink := GetActionLinkClass.Create(Self);
        FActionLink.Action := Value;
        FActionLink.OnChange := DoActionChange;
        ActionChanged(Value, csLoading in Value.ComponentState);
        if FGrid <> nil then
          Value.FreeNotification(FGrid);
      end;
      Changed(False);
    end;
    
    end.
    

    请注意,此代码仅剥离到适用的操作部分。

    来源:www.nldelphi.com.

    一个动作组件

    动作组件可以分配给任意组件的动作属性。但是由于解释编写这样一个动作组件所涉及的所有内容非常全面,因此我将在下面提供示例以方便自己。

    假设您想要制作一个提供缩放功能的控件,并且您还想要可以分配给工具栏按钮的相应 ZoomIn 和 ZoomOut 操作。

    unit Zoomer;
    
    interface
    
    uses
      Classes, Controls, ActnList, Forms, Menus, Windows;
    
    type
      TZoomer = class;
    
      TZoomAction = class(TCustomAction)
      private
        FZoomer: TZoomer;
        procedure SetZoomer(Value: TZoomer);
      protected
        function GetZoomer(Target: TObject): TZoomer;
        procedure Notification(AComponent: TComponent; Operation: TOperation);
          override;
      public
        destructor Destroy; override;
        function HandlesTarget(Target: TObject): Boolean; override;
        procedure UpdateTarget(Target: TObject); override;
      published
        property Caption;
        property Enabled;
        property HelpContext;
        property HelpKeyword;
        property HelpType;
        property Hint;
        property ImageIndex;
        property ShortCut;
        property SecondaryShortCuts;
        property Visible;
        property OnExecute; { This property could be omitted. But if you want to be
                              able to override the default behavior of this action
                              (zooming in on a TZoomer component), then you need to
                              assign this event. From within the event handler
                              you could invoke the default behavior manually. }
        property OnHint;
        property OnUpdate;
        property Zoomer: TZoomer read FZoomer write SetZoomer;
      end;
    
      TZoomInAction = class(TZoomAction)
      public
        constructor Create(AOwner: TComponent); override;
        procedure ExecuteTarget(Target: TObject); override;
      end;
    
      TZoomer = class(TCustomControl)
      public
        procedure ZoomIn;
      end;
    
    procedure Register;
    
    implementation
    
    procedure Register;
    begin
      RegisterComponents('RoyMKlever', [TZoomer]);
      RegisterActions('Zoomer', [TZoomInAction], nil);
    end;
    
    { TZoomAction }
    
    destructor TZoomAction.Destroy;
    begin
      if FZoomer <> nil then
        FZoomer.RemoveFreeNotification(Self);
      inherited Destroy;
    end;
    
    function TZoomAction.GetZoomer(Target: TObject): TZoomer;
    begin
      if FZoomer <> nil then
        Result := FZoomer
      else if (Target is TZoomer) and TZoomer(Target).Focused then
        Result := TZoomer(Target)
      else if Screen.ActiveControl is TZoomer then
        Result := TZoomer(Screen.ActiveControl)
      else
        { This should not happen! HandlesTarget is called before ExecuteTarget,
          or the action is disabled }
        Result := nil;
    end;
    
    function TZoomAction.HandlesTarget(Target: TObject): Boolean;
    begin
      Result := ((FZoomer <> nil) and FZoomer.Enabled) or
        ((FZoomer = nil) and (Target is TZoomer) and TZoomer(Target).Focused) or
        ((Screen.ActiveControl is TZoomer) and Screen.ActiveControl.Enabled);
    end;
    
    procedure TZoomAction.Notification(AComponent: TComponent;
      Operation: TOperation);
    begin
      inherited Notification(AComponent, Operation);
      if (Operation = opRemove) and (AComponent = FZoomer) then
        FZoomer := nil;
    end;
    
    procedure TZoomAction.SetZoomer(Value: TZoomer);
    begin
      if FZoomer <> Value then
      begin
        if FZoomer <> nil then
          FZoomer.RemoveFreeNotification(Self);
        FZoomer := Value;
        if FZoomer <> nil then
          FZoomer.FreeNotification(Self);
      end;
    end;
    
    procedure TZoomAction.UpdateTarget(Target: TObject);
    begin
      Enabled := HandlesTarget(Target);
    end;
    
    { TZoomInAction }
    
    constructor TZoomInAction.Create(AOwner: TComponent);
    begin
      inherited Create(AOwner);
      Caption := 'Zoom in';
      Hint := 'Zoom in|Zooms in on the selected zoomer control';
      ShortCut := Menus.ShortCut(VK_ADD, [ssCtrl]);
    end;
    
    procedure TZoomInAction.ExecuteTarget(Target: TObject);
    begin
      GetZoomer(Target).ZoomIn;
      { For safety, you cóuld check if GetZoomer <> nil. See remark in GetZoomer. }
    end;
    
    { TZoomer }
    
    procedure TZoomer.ZoomIn;
    begin
      { implementation of zooming in }
    end;
    
    end.
    

    激活此操作(通过单击工具栏按钮或选择菜单项)按以下优先级调用 ZoomIn 例程:

    1. 您在操作的相关属性中手动设置的 Zoomer 控件,如果这样做,并且如果操作已启用,否则:
    2. 应用程序请求的目标,但前提是该目标是聚焦的 Zoomer 控件,否则:
    3. 整个应用程序中的活动控件,但前提是它是启用的 Zoomer 控件。

    随后,简单地添加了 ZoomOut 操作:

    type
      TZoomOutAction = class(TZoomAction)
      public
        constructor Create(AOwner: TComponent); override;
        procedure ExecuteTarget(Target: TObject); override;
      end;
    
    { TZoomOutAction }
    
    constructor TZoomOutAction.Create(AOwner: TComponent);
    begin
      inherited Create(AOwner);
      Caption := 'Zoom out';
      Hint := 'Zoom out|Zooms out on the selected zoomer control';
      ShortCut := Menus.ShortCut(VK_SUBTRACT, [ssCtrl]);
    end;
    
    procedure TZoomOutAction.ExecuteTarget(Target: TObject);
    begin
      GetZoomer(Target).ZoomOut;
    end;
    

    请注意,动作组件需要在 IDE 中注册才能在设计时使用它们。

    Delphi 帮助中的适用阅读食物:

    来源:www.nldelphi.com.

    【讨论】:

      【解决方案2】:

      基本的动作支持是在 TControl 类中实现的,所以在最简单的情况下,您所要做的就是从 TControl 后代继承您的组件并将Action 属性声明为已发布,例如:

      type
        TMyGraphicControl = class(TGraphicControl)
        published
          property Action;
        end;
      

      如果您的组件具有应链接到 TAction 属性的其他属性,您还应覆盖 ActionChange 方法。

      【讨论】:

      • 这个答案(由于更短并且确切地告诉你该怎么做)对于OP的问题可能比被接受的大长问题更好的答案。由于 SO 现在几乎变成了编程维基百科,我希望文章不要成为规范而不是答案。
      【解决方案3】:

      如果您的组件已经是 TButton 的后代,则继承动作支持。 您需要做的就是将 action 属性声明为已发布。

      【讨论】:

      • 请随时恢复我的编辑。我无法在不编辑的情况下撤回我的反对票。我希望您认为我的修改可以改善您的答案,否则请随时恢复它们。
      猜你喜欢
      • 2013-07-24
      • 2021-12-07
      • 1970-01-01
      • 2011-08-11
      • 1970-01-01
      • 2014-12-31
      • 2014-03-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多