运行时分配的操作的ShortCut不会在自定义组件中触发

时间:2014-11-21 03:46:40

标签: delphi components action hotkeys

在将代码完全在运行时创建时(即没有表单设计器组件),我将问题分配给自定义组件的继承Action属性时遇到问题。如果我在表单设计器中使用ActionList然后使用相同的代码,那么工作正常。

这是我从TCustomControl派生的组件的构造函数:

  self.FButtonSCActionList := TActionList.Create( self.Parent );
  self.FButtonSCActionList.Name := 'ButtonSCActionList';
  self.FButtonSCAction := TAction.Create( self.FButtonSCActionList );
  self.FButtonSCAction.Name := 'ClickShortcutAction';
  self.FButtonSCAction.OnExecute := self.ExecuteButtonShortcut;
  self.FButtonSCAction.ShortCut := TextToShortCut('CTRL+K');
  self.FButtonSCAction.Enabled := TRUE;
  self.FButtonSCAction.Visible := TRUE;
  self.FButtonSCAction.ActionList := self.FButtonSCActionList;
  self.Action := FButtonSCAction;

如果我使用此代码创建自定义控件,将其添加到工具栏,将其放在新VCL Forms应用程序中的表单上,然后运行应用程序,当我按下快捷键时没有任何反应。如果我在没有此代码的情况下创建控件,则将其放在表单上并为表单指定一个Actionlist,然后将代码行放在一起,仅涉及创建一个操作并将其分配给组件的Action属性为该按钮的onclick事件处理程序,然后它正确响应快捷键。对于我的生活,我看不出有什么不同,但希望你的动作德尔福大师可以......

此操作的目的是允许开发人员通过属性为Object Inspector中的按钮指定自定义快捷方式。我想直接分配给“内置”操作,但无法找到如何访问其快捷方式属性。 (显然我可以通过其他HotKey delphi功能实现这一点,如果我必须这样做,但我也想了解动作,这似乎是一个好的开始......)

3 个答案:

答案 0 :(得分:3)

您无需在设计时创建ActionList。在Create方法中使用以下代码:

  FButtonSCAction := TAction.Create(Self);
  FButtonSCAction.SetSubComponent(true);
  FButtonSCAction.OnExecute := ExecuteButtonShortcut;
  FButtonSCAction.ShortCut := TextToShortCut('CTRL+K');
  FButtonSCAction.Enabled := TRUE;
  FButtonSCAction.Visible := TRUE;
  Action := FButtonSCAction;
  if not (csDesigning in ComponentState) then
    begin
      FButtonSCActionList := TActionList.Create(aOwner);
      FButtonSCAction.ActionList := FButtonSCActionList;
    end;

在运行时创建控件期间,您可能会遇到传递给控件的aOwner不会自身形成的情况,而是另一个控件。在这种情况下,您不必使用aOwner创建动作列表,而是必须调用将从aOwner参数中为您提供表单的函数。

function GetOwnerForm(Component: TComponent): TComponent;
begin
  Result := Component;
  while (Result <> nil) and (not (Result is TCustomForm)) do
    begin
      Result := Result.Owner;
    end;
end;

FButtonSCActionList := TActionList.Create(GetOwnerForm(aOwner));

答案 1 :(得分:2)

摘要

TControl中没有内置的Action 组件。它是默认情况下未分配的Action 属性。控件的用户可以为属性分配所需的操作。控件的设计者(你)不必提供Action或ActionList。

实际问题

  

我想直接分配给“内置”操作,但无法找到如何访问其快捷方式属性。

内置操作默认情况下只是一个未分配的TAction属性。如果未分配属性,即属性未指向Action组件,则其ShortCut属性不存在。

  

此操作的目的是允许开发人员(红色。组件/控件的用户)通过属性为Object Inspector中的按钮分配自定义快捷方式。

如果这是您的唯一目标,那么只需发布Action属性,不做任何进一步的操作:

type
  TMyControl = class(TCustomControl)
  published
    property Action;
  end;

这将导致开发人员的Object Inspector中出现该属性。开发人员只需要为其分配一个自己的操作,并设置该操作的ShortCut属性。因此,实际的解决方案是摆脱所有当前的代码。

为什么您当前的代码不起作用

self.FButtonSCActionList := TActionList.Create( self.Parent );
构造函数中

Self.Parentnil。关于这一点的两件事:

  • 除非您在de destructor中自行销毁ActionList,否则会出现内存泄漏。
  • 对于默认的ShortCut处理,应用程序遍历所有由当前焦点表单或MainForm (间接)拥有的ActionLists 。您的ActionList没有所有者,因此永远不会评估其ShortCuts。

当前代码的解决方案

首先,对您的代码进行一些善意的评论:

  • Self是隐含的,不需要,也不是惯例。
  • 运行时制作的组件不需要Name属性集。
  • 默认情况下,操作的VisibleEnabled属性为True。

其次,正如Dalija Prasnikar已经说过的那样,在设计时不需要ActionList。 ActionList必须由控件拥有的表单间接拥有。因此控件也可以拥有ActionList(XE2)。

constructor TMyControl.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FButtonSCAction := TAction.Create(Self);
  FButtonSCAction.OnExecute := ExecuteButtonShortcut;
  FButtonSCAction.ShortCut := TextToShortCut('CTRL+K');
  Action := FButtonSCAction;
  if not (csDesigning in ComponentState) then
  begin
    FButtonSCActionList := TActionList.Create(Self);
    FButtonSCAction.ActionList := FButtonSCActionList;
  end;
end;

在XE2之前的某个地方,至少仍然在D7中,ActionList必须通过控件拥有的表单进行注册。 (还有更多内容,但由于控件不太可能是另一个表单的父级,也不是在关注另一个表单时调用该操作,因此可以进行简化)。可以通过使表单成为ActionList的所有者来完成注册。由于您将ActionList的所有权赋予控制权以外的权限,因此让ActionList通过FreeNotification将其可能的销毁通知给控件。 (好吧,这是牵强附会的,因为通常控件也会被销毁,但这是严格应该如何完成的。)

type
  TMyControl = class(TCustomControl)
  private
    FButtonSCActionList: TActionList;
    FButtonSCAction: TAction;
  protected
    procedure ExecuteButtonShortcut(Sender: TObject);
    procedure Notification(AComponent: TComponent; Operation: TOperation);
      override;
  public
    constructor Create(AOwner: TComponent); override;
  end;

constructor TMyControl.Create(AOwner: TComponent);
var
  Form: TCustomForm;

  function GetOwningForm(Component: TComponent): TCustomForm;
  begin
    repeat
      if Component is TCustomForm then
        Result := TCustomForm(Component);
      Component := Component.Owner;
    until Component = nil;
  end;

begin
  inherited Create(AOwner);
  FButtonSCAction := TAction.Create(Self);
  FButtonSCAction.OnExecute := ExecuteButtonShortcut;
  FButtonSCAction.ShortCut := TextToShortCut('CTRL+K');
  Action := FButtonSCAction;
  if not (csDesigning in ComponentState) then
  begin
    Form := GetOwningForm(Self);
    if Form <> nil then
    begin
      FButtonSCActionList := TActionList.Create(Form);
      FButtonSCActionList.FreeNotification(Self);
      FButtonSCAction.ActionList := FButtonSCActionList;
    end;
  end;
end;

procedure TMyControl.ExecuteButtonShortcut(Sender: TObject);
begin
  //
end;

procedure TMyControl.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  inherited Notification(AComponent, Operation);
  if (AComponent = FButtonSCActionList) and (Operation = opRemove) then
    FButtonSCActionList := nil;
end;

请注意,当GetOwningForm返回False时(当开发人员创建没有所有者的控件时),不会创建ActionList,因为它无法解析拥有的表单。重写SetParent可以解决这个问题。

因为将所有权转移到另一个组件是不必要的(并且在csDesigning in ComponentState运行代码时可能会给IDE的流系统带来问题),还有另一种注册 ActionList的方法将表单添加到受保护的FActionLists字段中的表单:

type
  TCustomFormAccess = class(TCustomForm);

constructor TMyControl.Create(AOwner: TComponent);
var
  Form: TCustomForm;

  function GetOwningForm(Component: TComponent): TCustomForm;
  begin
    repeat
      if Component is TCustomForm then
        Result := TCustomForm(Component);
      Component := Component.Owner;
    until Component = nil;
  end;

begin
  inherited Create(AOwner);
  FButtonSCAction := TAction.Create(Self);
  FButtonSCAction.OnExecute := ExecuteButtonShortcut;
  FButtonSCAction.ShortCut := TextToShortCut('CTRL+K');
  Action := FButtonSCAction;
  if not (csDesigning in ComponentState) then
  begin
    Form := GetOwningForm(Self);
    if Form <> nil then
    begin
      FButtonSCActionList := TActionList.Create(Self);
      FButtonSCAction.ActionList := FButtonSCActionList;
      if TCustomFormAccess(Form).FActionLists = nil then
        TCustomFormAccess(Form).FActionLists := TList.Create;
      TCustomFormAccess(Form).FActionLists.Add(FButtonSCActionList)
    end;
  end;
end;

对此解决方案的反思:

  • 这种方法不可取。您不应在自定义控件中创建操作组件。如果必须,单独提供它们,以便控件的用户可以决定添加自定义Action的ActionList。另见:How do I add support for actions in my component?
  • TControl.Action是公共属性,TControl.SetAction不是虚拟的。这意味着控件的用户可以分配不同的Action,使此Action无用,并且您无法对其执行任何操作。 (不发布是不够的)。相反,声明另一个Action属性,或者 - 再次 - 提供一个单独的Action组件。

答案 2 :(得分:0)

非常感谢所有的帮助!对于那些将稍后使用这个问题google-fu的人(这些日子里我不是在Delphi IDE中的 live )这里是自定义组件的最终完整功能代码:

unit ActionTester;

interface

uses

  Winapi.windows,
  Vcl.ExtCtrls,
  System.Types,
  System.SysUtils ,
  System.Classes,
  Vcl.Controls,
  Vcl.Forms,
  Vcl.Graphics,
  Messages,
  Vcl.Buttons,
  System.Variants,
  System.UITypes,
  Dialogs,
  Vcl.ExtDlgs,
  Generics.Collections,
  System.Actions,
  Vcl.ActnList,
  Clipbrd,
  TypInfo,
  Rtti,
  Menus;

type
  TActionTester = class(TCustomControl)
  private
    { Private declarations }
  protected
    { Protected declarations }
    FButtonSCActionList: TActionList;
    FButtonSCAction: TAction;
    procedure ExecuteButtonShortcut(Sender: TObject);
    procedure Notification(AComponent: TComponent; Operation: TOperation);
      override;
  public
    { Public declarations }
    constructor Create(AOwner: TComponent); override;
    Procedure Paint; override;
    Destructor Destroy; Override;
  published
    { Published declarations }
    Property OnClick;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Samples', [TActionTester]);
end;

{ TActionTester }

constructor TActionTester.Create(AOwner: TComponent);
var
  Form: TCustomForm;

  function GetOwningForm(Component: TComponent): TCustomForm;
  begin
    result := NIL;
    repeat
      if Component is TCustomForm then
        Result := TCustomForm(Component);
      Component := Component.Owner;
    until Component = nil;
  end;

begin
  inherited Create(AOwner);
  FButtonSCAction := TAction.Create(Self);
  FButtonSCAction.OnExecute := ExecuteButtonShortcut;
  FButtonSCAction.ShortCut := TextToShortCut('CTRL+K');
  FButtonSCAction.SetSubComponent(true);
  if not (csDesigning in ComponentState) then
  begin
    Form := GetOwningForm(Self);
    if Form <> nil then
    begin
      FButtonSCActionList := TActionList.Create(Form);
      FButtonSCActionList.FreeNotification(Self);
      FButtonSCAction.ActionList := FButtonSCActionList;
    end;
  end;
end;

destructor TActionTester.Destroy;
begin
  FreeAndNil( self.FButtonSCAction );
  inherited;
end;

procedure TActionTester.ExecuteButtonShortcut(Sender: TObject);
begin
  if assigned( self.OnClick ) then self.OnClick( self );
end;

procedure TActionTester.Notification(AComponent: TComponent; Operation: TOperation);
begin
  inherited Notification(AComponent, Operation);
  if (AComponent = FButtonSCActionList) and (Operation = opRemove) then
    FButtonSCActionList := nil;
end;

procedure TActionTester.Paint;
begin
  inherited;
  self.Canvas.Brush.Color := clGreen;
  self.Canvas.Brush.Style := bsSolid;
  self.Canvas.FillRect( self.GetClientRect );
end;

end.

就像一个魅力! NGLN,David和Dalija的主要荣誉!