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繼承的類,增加了分類的功能,支持TActionList中的分類(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基類開始處理目標控件的確定過程。這個類重載了HandlesTarget和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(提示事件分發方法)在Delphi 4中聲明為靜態方法,而在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。到此為止,我們才算真的大功告成了。