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