概述
TControl
中没有内置的Action组件。它是一个未分配默认值的Action属性。控件的用户可以使用所需的任何Action来分配该属性。控件的设计者(即你)不必提供Action或ActionList。
实际问题
我想直接分配给“内置”的Action,但找不到如何访问其Shortcut属性。
默认情况下,“内置”的Action只是一个未分配的TAction
属性。如果未分配该属性,即该属性不指向Action组件,则其Shortcut属性不存在。
该Action的目的是允许开发人员(即你组件/控件的用户)通过属性在对象检查器中为按钮分配自定义快捷方式。
如果这是你唯一的目标,那么只需发布Action属性并不再做任何操作:
type
TMyControl = class(TCustomControl)
published
property Action;
end;
这将导致属性出现在开发人员的对象检查器中。开发人员只需要为其中一个自己的操作分配它,并设置该操作的ShortCut属性。因此,实际解决方案是摆脱您当前的所有代码。
为什么您当前的代码无法正常工作
self.FButtonSCActionList := TActionList.Create( self.Parent );
Self.Parent
在构造函数期间为 nil
。关于这个有两件事:
- 除非你在析构函数中自己销毁 ActionList,否则会出现内存泄漏。
- 对于默认的快捷键处理,应用程序会遍历所有由当前聚焦窗体或 MainForm 直接或间接拥有的 ActionList。你的 ActionList 没有所有者,因此它的快捷键永远不会被评估。
当前代码的解决方案
首先,对你的代码提出一些善意的评论:
Self
是隐式的,不需要也不习惯使用。
- 运行时制作的组件不需要设置
Name
属性。
Action
的 Visible
和 Enabled
属性默认为 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流系统出现问题),所以有另一种方法可以通过将其添加到受保护的
FActionLists
字段来向窗体注册ActionList。
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;
对这个解决方案的反思:
- 这种方法并不理想。您不应该在自定义控件中创建动作组件。如果必须这样做,应将它们分别提供,以便控件的用户可以决定将自定义动作添加到哪个ActionList中。另请参见:如何在我的组件中添加动作支持?
TControl.Action
是一个公共属性,而TControl.SetAction
不是虚拟的。这意味着控件的用户可以分配不同的动作,使此动作无效,您无法对其进行任何处理或反对。 (不发布是不够的)。相反,声明另一个动作属性,或者再次提供单独的动作组件。
Action
。在我看来,这似乎是你的根本问题。我希望组件不要这样做。一旦你停止这样做,就不会再有问题了。 - David Heffernan