Action高級開發

Action高級開發

作者:DELPHI技術  來源:博客園  發佈時間:2005-07-10 14:36  閱讀:1711 次  原文鏈接 [收藏]

Action高級開發

Action開發篇

    在討論Action的開發前,我想先討論一下爲什麼要使用TActionList及TAction。從Delphi 4開始Borland提供了TActionList控件,ActionList提供了一種全新的設計用戶界面交互模式的方法。傳統的事件模式無法解決命令狀態更新的問題,因爲任何情況下命令都是有效的。Delphi 4通過使用ActionList及Action提供了新的方法來處理命令的實現,即命令的有效性問題。ActionList是一個非可視的控件裏面包含了一組TAction對象。兩者的關係有點像菜單項同菜單的關係。

image001.jpg

圖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事件處理函數中寫耗時很長的代碼,這樣會嚴重影響應用程序的運行效率。

image002.gif

圖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事件處理過程的工作。

image003.jpg

圖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對象管理的當前激活的窗體,如果當前沒有活動的窗體,消息就發給應用程序的主窗體。

image004.jpg

圖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                       image005.jpg

圖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就可以。

image006.jpg

圖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

image007.jpg

圖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。到此爲止,我們纔算真的大功告成了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章