首页 > 代码库 > 自定义 Action

自定义 Action

Action开发篇
在讨论Action的开发前,我想先讨论一下为什么要使用TActionList及TAction。从Delphi 4
开始Borland提供了TActionList控件,ActionList提供了一种全新的设计用户界面交互模式
的方法。传统的事件模式无法解决命令状态更新的问题,因为任何情况下命令都是有效的。
Delphi 4通过使用ActionList及Action提供了新的方法来处理命令的实现,即命令的有效性问题。
ActionList是一个非可视的控件里面包含了一组TAction对象。两者的关系有点像菜单项同菜
单的关系。

图3.18

一个TAction对象提供一个命令,比如删除一个目标的选项(例如删除列表框的一个列表项),
当Action控制的控件相应某些用户的输入会激发相应的Action命令,通常是鼠标键盘点击
等动作。Action通常用来控制按钮和菜单项这类控件,通过设定这类控件的Action属性可以把
两者关联起来。
图3.18
显示了一个关联Action和控件的例子,EditCut1 Action被指定给SpeedButton1的Action属性。
当关联完成后,Speedbutton的属性会根据对应的EditCut1的属性作出相应的变化,比如按钮
的Caption会自动变成“Cu&t.”,当用户点击Cut按钮时,由EditCut1 Action实现的相应命令
就会被调用。当Memo1中有文本被选中的时候,Cut按钮才是有效的,这是因为Delphi内置的
EditCut Action实现了Action的OnUpdate事件,在那里对相关联的Memo1进行了判断,只有当Memo1中
有文本被选择了,Action对应剪切操作才有效。

参看前面在OTA部分实现的Winamp专家中,大量使用TAction对系统进行了管理,可以发现只
需要在Action的OnUpdate事件中写很少的代码甚至不写代码(对系统内置的Action而言),
就可以对一些基本界面操作的有效性进行判断,同时使用ActionList容器类使得命令更容易维护。
另外,除了以上功能,还可以利用Action的OnHint事件对关联控件的飞跃提示的显示进行控制。

虽然Action的使用是非常方便的,但如果想得到最佳的性能和表现,我们还是需要更深入地
了解Action的工作原理。比如注意到TActionList和TAction都定义了OnExecute和OnUpdate事
件。两者有什么区
别呢?在Borland的文档中并没有很清楚的说明,所以还是需要研究一下Action工作的内部机
制。
当在程序中使用了ActionList和Action后,Delphi 中的Application对象会在系统空闲的
时候产生OnUpdate事件。对于每一个ActionList,TActionList.OnUpdate事件会最先生成,然
后系统传递给事件两个参数。Action参数代表正在更新状态的Action,Handled参数用来控制相
应的Action的OnUpdate事件是否被调用。如果不想相应的Action的Update事件被调用,需要设定
Handled参数为真。

TActionList.OnUpdate事件对每一个列表中的Action相关联的每一个控件都要产生一遍。换
句话,假设ActionList1包含Action1和Action2。现在假定Button1和SpeedButton1的 Action
属性设定为Action1,同时SpeedButton2的Action属性设定为Action2。在这种情况下,每个
循环下来,ActionList1.OnUpdate事件将会产生1+2=3次。

什么时候用Action.OnUpdate事件,什么时候用ActionList.OnUpdate事件没有什么绝对的准
则。但一般来说,TActionList.OnUpdate事件更容易控制,因为把全部的状态控制代码写在
一个地方更清楚,而且写起来更简洁,当然这只是我的看法。

另外,大家可能会注意到可以在一个窗体上放多个ActionList。比如Delphi带的RichEdit的
演示程序中就使用了两个ActionList,为什么使用两个ActionList呢?其实这是出于运行效率
的考虑,因为按照ActionList更新状态的方式,如果你有一组Action并不需要进行有效性校验。
这时就应该把它们放到单独的ActionLis中去,这样就不需要生成OnUpdate事件了。这是因为不管Action是
否生成了OnUpdate事件,只要ActionList中有一个Action定义了OnUpdate事件,其他Action的OnUpdate都会被
调用。
同时由于OnUpdate事件会产生很多次,所以不要在OnUpdate事件处理函数中写耗时很长
的代码,这样会严重影响应用程序的运行效率。

图3.19

同OnUpdate事件相反,我推荐为每一个Action对象生成一个OnExecute的事件,而不是全部写
到TActionList.OnExecute事件中去,它不同于OnUpdate事件之处在于只有当相应操作需要执
行时,事件才会被调用,而OnUpdate事件是只要系统空闲就会被调用。

在Delphi的在线帮助中,关于Action执行过程中事件产生的顺序有点模糊不清。图3.19显示
了一个更加清晰的顺序图。注意Action的OnExecute事件发生在ActionList和Application
对象获得一个机会去处理Action之后。

除了OnUpdate和OnExecute事件外,TActionList还定义了OnChange事件,这是一个非常奇怪
的事件,只有当Action的Category属性改变的时候或者是当ActionList的Images属性改变的
时候才会被调用,我不清楚有什么必要生成这么样一个事件,因为Category只是在设计时才有效,
Images也极少在运行时改变,所以我觉得这个事件定义的好像没有必要。相比之下,OnHint事件更有用
些,生成一个OnHint事件处理过程使我们可以比较容易定制要显示的飞跃提示。OnHint事件有两个参数:
第一个是HintStr,用它来返回要显示的提示字符串;第二个参数CanShow用来确定是否显示
提示。这个事件处理有点问题,在无焦点的控件如SpeedButton定义的OnHint事件中,CanShow参数只在Action
的Hint属性设置为空字符串的情况下才有效,如果Hint属性指定了一个字符串,那么不管CanShow如何设置,控
件只会显示缺省的Hint字符串。

Action还具有给菜单项或SpeedButton添加图标的功能。但要注意仅仅设定TActiongList.Images
属性是不够的,还需要同时设定Menu的Images属性为相同的图像列表。另外假设我们要修改同Action关联
的图像,仅仅修改对应的图像列表是不够的,不像Action的其他属性(如:Caption或ShortCut),
只要修改了,就会通知相应控件作出改变。我们必须先清除对应控件的Action属性,在重置Action属性
达到刷新控件的Glyph属性的目的。

如果不想让Action的图像出现在对应的SpeedButton上时,我们必须在运行时(如在窗体的OnCreate事件中)
清除SpeedButton的Glyph属性,在设计时清空Glyph属性是无效的。

==================================================================================================

下一节将探讨如何重用现有Action源代码来实现新的Action类。
设计新武器 ——Action的开发

这节将探讨如何编写新的Action。
首先,必须清楚Action是控件,我们可以像写其它VCL控件一样,编写并能注册它。

其次,编写新的Action是有一定前提条件的,要编写的Action必须是对应于比较普遍的操作
,可重用性要强,比如从一个列表框中删除列表项的操作以及上下移动列表项都是在界面交
互时会经常碰到的
需求。当然通过在普通的TAction的OnExecute和OnUpdate事件中也可以实现这类操作,但如
果能通过编写对应于这样操作的Action,就可以省去重复编写OnUpdate和OnExecute事件处理
过程的工作。

图3.20

另外要弄清,要编写的Action不同于一般意义上的Action的,我们定制的Action是用户可以
不需要写OnExecute事件处理过程的,它提供了一个内置的缺省功能,就好像TEditCutAction
一样,只要把它同编辑框相关联,无需写一行代码它就可以正确处理剪切操作了。同样对于OnUpdate事件,用
户定制的Action也提供了内置的命令有效性校验机制,用户可以无需修改直接使用。但最重
要的区别恐怕是用
户定制的Action可以用于很多不同的控件并能在不同程序中重用。
1. 预定义的Action
在开始编写定制的Action前,先来看看Delphi已经实现了的定制的Action。图3.20中列
出了Delphi自带的标准Action,Edit Action处理剪贴板操作,Window Action处理多文档界面(MDI)
的子窗体的管理操作,DataSet Action则处理数据库导航命令。
预定义的Action提供了很强大的功能。比如假定把一个SpeedButton的Action属性同一个
TEditCut Action相关联,当任何编辑框中的文本被选择后,SpeedButton就处于有效状态,这时点击
SpeedButton,被选的文本将被删除并复制到剪贴板上。所有这些功能不需要写任何代码。

虽然预定义的Action功能很强大,但还是有一些应用上的限制。为了这些限制存在的原因,
我们需要了解定制的Action是如何定位操作目标控件的。比如当一个TEditCut Action 被激发的时候,
它是如何知道操作是在Memo1上而不是在Edit2上的,也就是如何区分需要剪切的控件的?

2. 定位目标
回忆一下前面讲过的,当一个Action被激发时,有4种可能的响应会发生:第一,Action List
在它的OnExecute事件中处理Action;第二,Application对象会处理Action;第三,Action调用本
身的OnExecute事件处理过程;如果这时Action还没有被处理,一个cm_ActionExecute
消息被发送到Application对象。
当Application对象接收到这个消息,它首先把消息发到Screen对象管理的当前激活的窗
体,如果当前没有活动的窗体,消息就发给应用程序的主窗体。

图3.21
消息由TCustomForm.CM-ActionExecute
消息处理过程进行处理。首先,处理过程检查窗体的ActiveControl属性。如果不为nil,当
前活动控件的ExecuteAction方法就会被调用,如果ActiveControl属性为nil,窗体的ExecuteAction
方法会被调用
(见图3.21)。

ExecuteAction是一个布尔函数,定义在TComponent类中。如果控件对Action做出了响应,ExecuteAction
函数就返回真值。ExecuteAction函数会调用Action的HandlesTarget方法,并把它的引用参考传给控件。
HandlesTarget方法决定控件是否是Action的一个有效的操作对象。比如TEditAction对象
HandlesTarget只有当目标控件是从TCustomEdit继承下来的时候才返回真值。

如果控件是一个有效的操作对象,Action的ExecuteTarget方法将被调用,同HandlesTarget
类似,它接收一个目标控件的引用参考作为一个参数,ExecuteTarget方法对目标控件执行相
应的Action。
图3.21举例说明了这一处理流程。

如果ActiveControl和窗体都不是一个有效的操作目标,这种情况下,窗体的CM-ActionExecute方法会遍历
窗体上的全部控件并对每一个控件都调用ExecuteAction方法直到一个有效的目标控件被找到或是找不到满足要求的控件为止。


3. 定制Action的局限性

理解了定制的Action如何定位它的目标控件,就可以讨论它们的局限性了。第一个局限来自
于对ActiveControl的依赖性。因为当激发Action的控件改变了输入焦点的时候,定制的Action可能无法正常工
作,这对于SpeedButton和菜单项是没有影响的,因为它们没有输入焦点,不会改变ActiveControl。然而普通的按钮却
会改变输入焦点。

假定Button1的Action属性设定为EditCut1。同时假定Edit1当前获得了焦点,并且其中的文
本被选择了。当用户点击了Button1,发生的第一件事是输入焦点切换到了按钮上。这时,
EditCut1.OnUpdate事件被自动调用。因为现在Button1成了ActiveControl,而它并不是TCustomEdit的子类,
EditCut1.Enabled的属性将设为False。结果Action变成无效的了, Button1也同样失效了。当用户松开鼠标后,
由于Button1无效了,结果OnClick事件就无法调用了,也就无法激发EditCut1的操作了。
这里还有一些其他限制,特别当这些编辑操作只能工作在TCustomEdit子类上时,它无法
支持很多支持剪贴板操作的其他控件。比如,Edit Action不能工作在csDropDown样式的组合列表框,而且在设计时,
无法控制Edit Action只对某个编辑框起作用。它只对所有的控件起作用(不过在运行时倒是可以通过TeditAction的Control属性进行控制)。
指出这些限制并不会影响这些Action的重要性,这只是为了理解整个Action的工作流程。

4. 创建用户定制的Action

创建Action同创建VCL控件非常类似,其实这并不奇怪,因为Action实际上就是控件的一种。
编写控件的规则同样适用于Action。不过编写Action之前,先看一下Action类的继承关系。
图3.22是非数据库Action的继承关系。编写新的Action,通常是从TAction开始的,但也
应该了解一下全部的4个Action基类之间的关系:
TBasicAction、TContainedAction、TcustomAction和TAction。
TBasicAction是最低层的类,如果想要创建一个同非菜单或控件相关的Action,可以由
它开始继承。
TContainedAction是直接从TbasicAction继承的类,增加了分类的功能,支持TActionL
ist中的分类(Category)。
TCustomAction直接从TContainedAction继承下来,增加了针对菜单项和控件的功能,
TcustomAction没有公开它的属性,仅仅是实现了它们。

TAction仅仅是公开了TcustomAction实现了的属性。它没有引进新的功能,除非需要使特定
的属性隐藏,通常Action都是从TAction继承的。
l
图3.22
5. 定制Action
现在就可以开始编写Action了,创建一个定制的Action包括以下几个步骤:
(1) 需要创建一个新单元,在单元中要从一个基类(比如TAction)继承我们的Action。
(2) 然后,需要重载一些关键的方法像HandlesTarget、UpdateTarget和ExecuteTarget
方法。
(3) 最后注册新的Action把它安装到Delphi中去。

在线帮助建议使用StdActns单元作为创建定制的Action的一个指导。但是如果按照StdActns
单元来写的话,生成的Action同Delphi预定义的Action工作的方式无法完全一样。比如,用
Action List Editor创建一个TEditPaste Action,然后选择这个Action,对象编辑器显示的Caption已经初始化为“&Paste”,提示初
始化为“Paste”、ImageIndex为2以及ShortCut为Ctrl+V。而生成的Action却无法做到对缺
省属性值的初始化。

如果创建一个基于StdActns单元中代码的Action,唯一能够初始化的属性是Caption,而且这
个Caption被初始化为同Name一样的无意义的值,像“MyAction1”这样无聊的名字。造成这
种情况的根本原因将在后面的Action的安装部分讲到,这里通过提供一个Constructor来初始化缺省的属性值。

另外,StdActns单元比较糟糕的地方是在这些单元中并没有如何注册和安装Action的内容。
Action虽然是一种控件,但它的注册同普通控件是不同的。更离谱的是在线帮助中关于如何
注册Action是错误的。
6. 列表框Action

虽然StdActns单元不是一个很好的指导文件,但只能从它开始研究。下面将创建一组Action
用来提供对列表框的操作。后面的程序清单1显示了实现这组Action的源代码。
类似标准的Edit
Action,从一个TListBoxAction基类开始处理目标控件的确定过程。这个类重载了HandlesT
arget和UpdateTarget方法。如果目标控件是TCustomListBox的子类,HandlesTarget方法将
返回真值,如果列
表框不是空的,有列表项存在,UpdateTarget方法设定Action的Enabled属性为真。相应代码
如下:
function TListBoxAction.HandlesTarget( Target: TObject ): Boolean;
begin
//当Target为TCustomListbox的子类时,相应的命令才有效
Result := ( ( Control <> nil ) and ( Target = Control ) or
( Control = nil ) and ( Target is TCustomListBox ) ) and
TCustomListBox( Target ).Focused;
end;
procedure TListBoxAction.UpdateTarget( Target: TObject );
begin
//当相应的列表框不为空,包含列表项时,命令才有效
Enabled := GetControl( Target ).Items.Count > 0;
end;
TlistBoxAction剩下的部分用来支持Control属性。 TeditAction同样实现了一个Control
属性,可以使Action只对某一控件起作用,不过它的缺点是它声明为public属性,这样只能
在运行时才能对它进行设置。这里声明Control为published属性,这样在设计时就可以方便
地进行设置了。不设定Control属性的话,Action将作用于窗体上全部的列表框。

所有的派生类在结构上都比较类似,他们从同一个基类继承并且都重载了Constructor来初始
化自身属性,还都重载了ExecuteTarget方法来实现内置的执行功能。下面是TListBoxDelete的ExecuteTarget
方法的实现,用来删除当前被选的列表项:
procedure TListBoxDelete.ExecuteTarget(Target: TObject);
var
Idx: Integer;
begin
Idx := GetControl( Target ).ItemIndex;
if Idx <> -1 then
GetControl( Target ).Items.Delete( Idx );
end;

一部分子类还重载了UpdateTarget方法,因为确定命令是否有效的规则对不同操作是不同的,
同在TListBoxAction中实现的有可能不同。例如,TListBoxMoveUp.UpdateTarget方法
判断Enabled属性是否为真是以被选的列表项是否是列表中的第一项为依据的,如果为第一项
则上移的操作是无效的。代码示意如下:
procedure TListBoxMoveUp.UpdateTarget( Target: TObject );
begin
Enabled := GetControl( Target ).ItemIndex > 0;
end;
7. 注册和安装Action
完成Action的定义和编码后,需要注册安装Action到Delphi。这里使用一个单独的单元
来进行注册。清单2列出了注册单元代码。

同一般控件不同,注册Action我们需要使用RegisterActions过程,而不是RegisterComponents过程。
RegisterActions过程需要三个参数:第一个是一个描述Action分类的字符串,由于Action是专门针
对列表框的,所以设定这个参数为 “ListBox.”;第二个参数是一组要注册的Action的类;最后一
个参数称为Resource参数,这个参数在在线帮助里没有提到,它的类型是TComponentClass,稍后会详
细研究这个参数的具体用意,这里先不管它,把它直接设成TlistBox就可以。

图3.23

因为Action实际上是一种控件,要安装的话,必须把实现的单元放到包中然后进行注册。一
旦安装到了Delphi里,新定制的Action就会出现在标准Action对话框中,如图3.23所示。
最后还剩下一点问题那就是因为TCustomAction.DoHint(提示事件分发方法)在Delphi4中声明为静态方法,
而在Delphi 5声明为动态方法。这样在Delphi 5中就可以对提示处理进行重载,提供一个用户定制的提示处理,
而在Delphi 4这是办不到的。本文的例子没有实现提示的定制部分,这个问题留给读者去实现。
程序清单1 – ListActn.pas如下:
unit ListActn;
interface
uses
Classes, ActnList, StdCtrls;
type
TListBoxAction = class( TAction )
private
FControl: TCustomListBox;
procedure SetControl( Value: TCustomListBox );
protected
function GetControl( Target: TObject ): TCustomListBox; virtual;
procedure Notification( AComponent: TComponent; Operation: TOperation ); override;
public
function HandlesTarget( Target: TObject ): Boolean; override;
procedure UpdateTarget( Target: TObject ); override;
published
property Control: TCustomListBox
read FControl
write SetControl;
end;
TListBoxDelete = class( TListBoxAction )
public
constructor Create( AOwner: TComponent ); override;
procedure UpdateTarget( Target: TObject ); override;
procedure ExecuteTarget( Target: TObject ); override;
end;
TListBoxClear = class( TListBoxAction )
public
constructor Create( AOwner: TComponent ); override;
procedure ExecuteTarget( Target: TObject ); override;
end;
TListBoxMoveUp = class( TListBoxAction )
public
constructor Create( AOwner: TComponent ); override;
procedure UpdateTarget( Target: TObject ); override;
procedure ExecuteTarget( Target: TObject ); override;
end;
TListBoxMoveDown = class( TListBoxAction )
public
constructor Create( AOwner: TComponent ); override;
procedure UpdateTarget( Target: TObject ); override;
procedure ExecuteTarget( Target: TObject ); override;
end;
TListBoxSelectAll = class( TListBoxAction )
public
constructor Create( AOwner: TComponent ); override;
procedure ExecuteTarget( Target: TObject ); override;
end;
TListBoxUnselectAll = class( TListBoxAction )
public
constructor Create( AOwner: TComponent ); override;
procedure ExecuteTarget( Target: TObject ); override;
end;
implementation
uses
Windows, Messages;
{== TListBoxAction Methods ==}
function TListBoxAction.GetControl( Target: TObject ): TCustomListBox;
begin
Result := Target as TCustomListBox;
end;
function TListBoxAction.HandlesTarget( Target: TObject ): Boolean;
begin
Result := ( ( Control <> nil ) and ( Target = Control ) or
( Control = nil ) and ( Target is TCustomListBox ) ) and
TCustomListBox( Target ).Focused;
end;
procedure TListBoxAction.Notification( AComponent: TComponent; Operation:
TOperation );
begin
inherited Notification( AComponent, Operation );
if ( Operation = opRemove ) and ( AComponent = Control ) then
Control := nil;
end;
procedure TListBoxAction.UpdateTarget( Target: TObject );
begin
Enabled := GetControl( Target ).Items.Count > 0;
end;
procedure TListBoxAction.SetControl( Value: TCustomListBox );
begin
if Value <> FControl then
begin
FControl := Value;
if Value <> nil then
Value.FreeNotification( Self );
end;
end;
{== TListBoxDelete Methods ==}
constructor TListBoxDelete.Create( AOwner: TComponent );
begin
inherited Create( AOwner );
Caption := ‘Delete‘;
ImageIndex := 1;
Hint := ‘Delete Item‘;
end;
procedure TListBoxDelete.ExecuteTarget(Target: TObject);
var
Idx: Integer;
begin
Idx := GetControl( Target ).ItemIndex;
if Idx <> -1 then
GetControl( Target ).Items.Delete( Idx );
end;
procedure TListBoxDelete.UpdateTarget( Target: TObject );
begin
Enabled := ( GetControl( Target ).Items.Count > 0 ) and
( GetControl( Target ).ItemIndex <> -1 );
end;
{== TListBoxClear Methods ==}
constructor TListBoxClear.Create( AOwner: TComponent );
begin
inherited Create( AOwner );
Caption := ‘Clear‘;
ImageIndex := 2;
Hint := ‘Clear List‘;
end;
procedure TListBoxClear.ExecuteTarget( Target: TObject );
begin
GetControl( Target ).Clear;
end;
{== TListBoxMoveUp Methods ==}
constructor TListBoxMoveUp.Create( AOwner: TComponent );
begin
inherited Create( AOwner );
Caption := ‘Move Up‘;
ImageIndex := 3;
Hint := ‘Move Item Up‘;
end;
procedure TListBoxMoveUp.ExecuteTarget( Target: TObject );
var
Idx: Integer;
begin
Idx := GetControl( Target ).ItemIndex;
GetControl( Target ).Items.Exchange( Idx, Idx - 1 );
GetControl( Target ).ItemIndex := Idx - 1;
end;
procedure TListBoxMoveUp.UpdateTarget( Target: TObject );
begin
Enabled := GetControl( Target ).ItemIndex > 0;
end;
{== TListBoxMoveDown Methods ==}
constructor TListBoxMoveDown.Create( AOwner: TComponent );
begin
inherited Create( AOwner );
Caption := ‘Move Down‘;
ImageIndex := 4;
Hint := ‘Move Item Down‘;
end;
procedure TListBoxMoveDown.ExecuteTarget( Target: TObject );
var
Idx: Integer;
begin
Idx := GetControl( Target ).ItemIndex;
GetControl( Target ).Items.Exchange( Idx, Idx + 1 );
GetControl( Target ).ItemIndex := Idx + 1;
end;
procedure TListBoxMoveDown.UpdateTarget( Target: TObject );
var
L: TCustomListBox;
begin
L := GetControl( Target );
Enabled := ( L.ItemIndex <> -1 ) and
( L.ItemIndex < L.Items.Count - 1 );
end;
{== TListBoxSelectAll Methods ==}
constructor TListBoxSelectAll.Create( AOwner: TComponent );
begin
inherited Create( AOwner );
Caption := ‘Select All‘;
ImageIndex := 5;
Hint := ‘Select All Items‘;
end;
procedure TListBoxSelectAll.ExecuteTarget( Target: TObject );
begin
SendMessage( GetControl( Target ).Handle, LB_SETSEL, 1, -1 );
end;
{== TListBoxUnselectAll Methods ==}
constructor TListBoxUnselectAll.Create( AOwner: TComponent );
begin
inherited Create( AOwner );
Caption := ‘Unselect All‘;
ImageIndex := 6;
Hint := ‘Unselect All Items‘;
end;
procedure TListBoxUnselectAll.ExecuteTarget( Target: TObject );
begin
SendMessage( GetControl( Target ).Handle, LB_SETSEL, 0, -1 );
end;
end.
程序清单2 - ListActnReg.pas如下:
unit listActnReg;
interface
procedure Register;
implementation
uses
Classes, ActnList, StdCtrls,ListActn;
procedure Register;
begin
RegisterActions( ‘ListBox‘, [ TListBoxDelete, TListBoxClear,
TListBoxMoveUp, TListBoxMoveDown,
TListBoxSelectAll, TListBoxUnselectAll ], TListBox );
end;
end.
Resource 参数:
上面留了一个问题,就是Resource参数到底是干什么用的?现在是该揭开谜底的时候了


其实Resource参数可以为我们的Action属性初始化,并且可以在Action中嵌入指定的图像。
实际上就是起到了Constructor过程的作用,它还能给Action添加缺省图标。下面就是使用Resource参数的步骤:
(1)给原来的安装包添加一个资源窗体。
(2)重新编译和安装Action。

第一步,添加一个新的窗体,把它命名为TListBoxRes。然后添加一个图像列表控件,一个ActionList并
添加我们先前创建的Action。最后,向ImageList中添加图标,并设定Action的各项缺省属性,包
括图标、Capition等等,最后把它保存为ListRes.Pas。

第二步,因为要使用Resource参数来重新注册,这回系统会根据资源窗体的内容设定Action
的缺省属性。这时就不再需要Action中的Constructor过程了(这回终于明白为什么Borland
的StdActns单元中实现的Action都没有constructor过程),把这些过程去掉。并把先前RegisterAction中
Resource参数的Tlistbox改成TlistBoxRes,然后把TlistBoxRes对应的单元添加到注册单元的Uses列表中,下面是
修改后的注册部分代码:
uses
Classes, ActnList, StdCtrls,ListActn,ListRes;
……………………………………………
procedure Register;
begin

图3.24
RegisterActions( ‘ListBox‘, [ TListBoxDelete, TListBoxClear,
TListBoxMoveUp, TListBoxMoveDown, TListBoxSelectAll, TListBoxUnselectAll ],
TListBoxRes );
end;

最后重新编译包。如果安装成功的话,让我们来测试一下。新建一个窗体,添加一个ActionList,
再放上一个TimageList控件,指定ActionList的Images属性为Imagelist1。然后添加Listbox Action,
如图3.24所示,你会发现Action的右边都有一个漂亮的图标,并且在没有Contructor的情况下,Action的
属性都设定了正确的缺省属性值。除此之外,即使ImageList中已经有了图像,Delphi也
会自动复制Action资源中的图标到Imagelist,并能很智能地调节Action的ImageIndex。到此
为止,我们才算真的大功告成了。

自定义 Action