Delphi 完全時尚手冊之 CoolBar 篇
---實現 CoolBar 的新特性 Chevron
我們發現到從 IE 5.0 以後,IE 的工具欄具有了一個新特性:當 IE 窗口縮小,使得工具欄上的按鈕不能完全顯示時,工具欄右邊會出現一個小按鈕(M$ 叫它 Chevron,實際上這是 CoolBar 的新特性),點擊後出現一下拉列表,顯示出被隱藏的按鈕。這大大方便了我們對工具欄的使用。
那我們如何使 Delphi 的 TCoolBar 控件具有這個特性呢?經過我一個通宵的查閱資料(MSDN)、“潛心研究”(啊,誰扔我雞蛋...),終於做出來了!好東西不敢獨吞,拿出來與大家分享。下面就說說怎麼具體實現它。(以下代碼在 Delphi 6 下完成)
第一步:改造 Delphi 的 TCoolBand 類
建議在進行這步前先備份 ComCtrls.pas 文件。如果將 CoolBar 中某個 Band 的 Style 中加上 RBBS_USECHEVRON 這個值,那麼當這個 Band 的寬度小於某個給定的值時它就會顯示一個下拉按鈕(Chevron)。下面來改 ComCtrls.pas 這個文件來實現這個功能。
在 TCoolBand 的 Published 部分增加兩個屬性(Property):
//Modified by Joe Huang property DealWidth: Integer read FDealWidth write SetDealWidth; {用來告訴 Band 當 Band 的寬度小於多少時顯示下拉按鈕(Chevron)} property UseChevron: Boolean read FUseChevron write SetUseChevron; {用來決定 Band 是否使用這個功能} //End
兩個屬性的寫方法如下:
procedure TCoolBand.SetUseChevron(const Value: Boolean); begin FUseChevron := Value; CoolBar.UpdateBands; end; procedure TCoolBand.SetDealWidth(const Value: Integer); begin FDealWidth := Value; CoolBar.UpdateBands; end;
對 TCoolBand 的改動完成,但有一點要提醒的,這兩個屬性雖然在 Publish 部分,但在設計期並不能看到(我也不知道是怎麼搞的)。所以我們只能在運行期間訪問到它們。
第二步:改造 Delphi 的 TCoolBar 控件
在 ComCtrls.pas 中找到 TCoolBar.UpdateItem 方法,先為這個方法加個常數如下:
//Modified by Joe Huang RBBS_USECHEVRON = $00000200; //End
然後在這個方法中找到這一行(第一次出現 fMask := 的地方):
fMask := RBBIM_STYLE or RBBIM_COLORS or RBBIM_SIZE or RBBIM_BACKGROUND or RBBIM_IMAGE or RBBIM_ID;
在這行下加入下段代碼:
//Modified by Joe Huang if (GetComCtlVersion >= ComCtlVersionIE5) and Band.UseChevron then {這個功能只在 IE5 後才有} begin fStyle := fStyle or RBBS_USECHEVRON; if Band.DealWidth > 0 then begin fMask := fMask or RBBIM_IDEALSIZE; cxIdeal := Band.DealWidth; end; end; //End
OK! TCoolBar改造完成。為 ComCtrls.pas 生成 .dcu 文件,方法:把 ComCtrls.pas 文件拷貝到一個現有工程的目錄下,把它加到這個工程中,編譯這個工程就會得到它的 .dcu 文件,將個 .dcu 文件覆蓋(注意備份) Delphi 原來的,在 Delphi6Lib 目錄下。
第三步:在一工程中具體實現。
打開 Delphi6,新建一工程,在 Form1 上放入 CoolBar1,再在 CoolBar1 上面放入 ToolBar1,CoolBar1 會自動產生一 Band。設置 ToolBar1 的 AutoSize 為 True,Wrapable 為 False 並在其上放入你的按鈕。設置 CoolBar1 的 AutoSize 為 True, ShowText 為 False(注意:將這屬性置為 False 可以為我提供一個利用 Band.Text 屬性定位 Band 的方法。在 CoolBar1 上有多個 Band 時定位 Band 是必須的。你也可以采用你自己的定位方式。),設置放有 ToolBar1 的 Band 的 Text 為 MyToolBand。下面開始寫代碼。
在 Form1 單元的 uses 後加入 CommCtrl 單元;在 Implementation 部分寫兩個自定義方法:
{用來取得 ToolBar1 上所有可見按鈕的總寬度。 用來為我們前面給 TCoolBand 增加的屬性 DealWidth 賦值, 即是當 Band 的寬度小於所有按鈕的總寬度時顯示下拉按鈕(Chevron)。} function GetTBButtonsWidth(AToolBar: TToolBar): Cardinal; var ARect, ButtonRect: TRect; TBCount, I: Integer; begin
ARect := Rect(0, 0, 0, 0); TBCount := AToolBar.Perform(TB_BUTTONCOUNT, 0, 0); for I := 0 to TBCount - 1 do begin AToolBar.Perform(TB_GETITEMRECT, I, Integer(@ButtonRect)); ARect.Right := ARect.Right + (ButtonRect.Right - ButtonRect.Left); end; Result := Abs(ARect.Right - ARect.Left); end; {用來定位 Band 參數 BandText 為你所要定位 Band 的 Text 屬性} function GetCoolBand(BandText: string; ACoolBar: TCoolBar): Integer; var I: Integer; begin Result := -1; for I := 0 to ACoolBar.Bands.Count - 1 do begin if ACoolBar.Bands.Items[I].Text = BandText then begin Result := I; Break; end; end; end;
由於 CoolBar1 上 Band 可以改變位置(當有多個 Band 時),所以我們需要一個變量來存儲放有 ToolBar1 的 Band 的當前位置(後面會提到如何捕捉到 Band 的位置變化)。
在 Private 部分定義一變量:
private CoolBandIndex: Integer;
在 Form1 的 OnShow 事件加入如下代碼:
CoolBandIndex := GetCoolBand(MyToolBand, CoolBar1); {定位 Band 的位置} CoolBar1.Bands.Items[CoolBandIndex].UseChevron := True; {我們自己加的屬性} CoolBar1.Bands.Items[CoolBandIndex].DealWidth := GetTBButtonsWidth(ToolBar1); {我們自己加的屬性}
現在大家可以運行一下程序了,然後縮放 Form1 使 ToolBar1 上部分按鈕被遮住,看下拉按鈕(Chevron)是不是出來了!(什麼?沒有!趕快檢查一下前面各步做得是否正確)
大家可能注意到了一個問題:ToolBar1 上按鈕可能被遮住了一半,另一半還顯示在外面,能不能使一個按鈕一旦部分被遮住後,整個按鈕不顯示呢?我發現 Delphi7 中的 ToolBar 中多了一個屬性 HideClippedButtons,就是干這事的,這個屬性只在ME、2000、XP下起作用,但 Delphi6 卻沒有這個屬性。有興趣的可以參照 Delphi7 改一下,很容易的。
各位注意了,重頭戲來了。如何點擊這個下拉按鈕(Chevron)使被遮住的按鈕顯示出來呢?還有我們前面提到的當 Band 改變位置時如何能通知我們呢?答案是消息!當我們點擊下拉按鈕(Chevron)時 CoolBar 會給它的父窗口發送 RBN_CHEVRONPUSHED 消息;當改變 Band 的位置會發送 RBN_LAYOUTCHANGED 消息。實際上這兩個消息是附加在 WM_NOTIFY 消息中的。下面我們就來在 Form1 的窗口函數中攔截這兩個消息(一般來說 Form1 是 CoolBar1 的父,如果你將 CoolBar1 放在其他容器控件中,則要在相應的窗口函數中攔截,原理相同)。
在 Private 部分定義兩個變量及一過程:
private
FClientInstance : TFarProc; FPrevClientProc : TFarProc; procedure NewWindowProc(var message:TMessage);
在將 NewWindowProc 這個過程的實現前先在 Form1 的 OnShow 事件中加入代碼(這段代碼寫在 OnShow 中所有代碼的前面):
procedure TForm1.FormShow(Sender: TObject); begin {$Warnings Off} FClientInstance := MakeObjectInstance(NewWindowProc); FPrevClientProc := Pointer(GetWindowLong(Form1.Handle, GWL_WNDPROC)); SetWindowLong(Form1.Handle, GWL_WNDPROC, LongInt(FClientInstance)); {替換 Form1 的窗口函數} {$Warnings On} {... ...} end;
再在 Form1 上放一 PopupMenu1,設置它的 Alignment 為 paRight。用來顯示被遮住的按鈕。
窗口函數 NewWindowProc 的實現如下:
procedure TForm1.NewWindowProc(var message: TMessage); type {封裝一 TNMREBARCHEVRON 結構。這個結構隨 RBN_CHEVRONPUSHED 消息發送。} PNMREBARCHEVRON = ^TNMREBARCHEVRON; TNMREBARCHEVRON = record hdr: TNMHDR; uBand: UINT; wID: UINT; lParam: LPARAM; rc: TRECT; lParamNM: LPARAM ; end; const RBN_CHEVRONPUSHED = RBN_FIRST - 10; var ANMHDR: PNMHDR; ANMREBARCHEVRON: PNMREBARCHEVRON; ScreenRect: TRect; FirstClipButton, I: Integer; AMenuItem: TMenuItem; {這個函數用來得到 ToolBar1 上被遮住按鈕中最左邊那個的位置(Index)。} function GetFirstClipButton(ACoolBar: TCoolBar; AToolBar: TToolBar): Integer; var ButtonRect: TRect; TBCount, I, TempWidth: Integer; begin Result := -1; TempWidth := 0; TBCount := AToolBar.Perform(TB_BU