总结
TControl 中没有内置的操作组件。它是一个默认未分配的 Action property。控件的用户可以为属性分配所需的任何操作。控件的设计者(您)不必提供 Action 或 ActionList。
实际问题
我想直接分配给“内置”操作,但不知道如何访问其快捷方式属性。
内置 Action 默认情况下只是一个未分配的TAction 属性。而如果该属性没有被赋值,即该属性不指向一个Action组件,那么它的ShortCut属性就不存在了。
此操作的目的是允许开发人员(红色。您的组件/控件的用户)通过属性为对象检查器中的按钮分配自定义快捷方式。
如果这是您的唯一目标,那么只需发布 Action 属性,无需进一步操作:
type
TMyControl = class(TCustomControl)
published
property Action;
end;
这将导致属性出现在开发人员的对象检查器中。开发人员只需为其分配一个自己的操作,并设置该操作的 ShortCut 属性。因此,实际的解决方案是摆脱所有当前代码。
为什么您当前的代码不起作用
self.FButtonSCActionList := TActionList.Create( self.Parent );
Self.Parent 在构造函数期间是nil。有两点:
- 除非您自己在析构函数中销毁 ActionList,否则会发生内存泄漏。
- 对于默认的 ShortCut 处理,应用程序遍历所有 ActionLists,这些 ActionLists(间接)由当前聚焦的表单或 MainForm 拥有。您的 ActionList 没有所有者,因此它的快捷方式永远不会被评估。
当前代码的解决方案
首先,在您的代码中添加一些善意的 cmets:
-
Self 是隐含的,既不需要,也不需要。
- 运行时制作的组件不需要
Name 属性集。
- 动作的
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 的流系统带来问题),还有另一种方法可以将 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;
对此解决方案的反思:
- 这种方法不可取。您不应在自定义控件中创建操作组件。如果必须,请单独提供它们,以便控件的用户可以决定将自定义操作添加到哪个 ActionList。另见:How do I add support for actions in my component?
-
TControl.Action 是公共属性,TControl.SetAction 不是虚拟的。这意味着控件的用户可以分配一个不同的动作,使这个动作无用,你不能做任何事情,也不能反对它。 (不发表是不够的)。相反,声明另一个 Action 属性,或者 - 再次 - 提供一个单独的 Action 组件。