騰訊QQ是當前流行的網絡聊天工具之一,由於它在應用設計上有很多獨特之處,所以也吸引了很多程序員對之進行研究和模仿。在這裡,我將利用Delphi對QQ的窗體自動隱藏效果提出自己的實現方法。
一、問題的提出
熟悉QQ使用的朋友都知道,當QQ窗體區域超出屏幕四邊時,窗體就會自動“消失”,只留下窗體一邊的小部分顯露在桌面上。當用鼠標移動到顯露部分之上,窗體就會在隱藏位置重新完整顯示;但當鼠標離開窗體區域後,窗體便會重新進入隱藏狀態。
對隱藏的全過程進行分析,可以得出兩點推測:第一,窗體隱藏的處理是與窗體移動過程有關;第二,窗體隱藏的觸發條件。
對第一點推測,可以通過對窗體移動時產生的Windows消息進行攔截處理加以實現。對第二點推測,如何去表示“窗體區域已經超出屏幕可視范圍”這一條件為實現的關鍵。
二、基本的分析
讓我們先留意一下Windows環境下窗體移動的過程與效果。當使用鼠標移動窗體的時候,窗體本身並沒有立刻隨鼠標的移動而發生位置的改變;相反,鼠標正在拖動的是一個大小與窗體一致的透明區域(確切的說一個虛線邊框的矩形)。當鼠標釋放矩形後,窗體本身才會在矩形最後停留的地方出現,從而完成整個移動的過程。(注意:在Windows 2000及XP環境下,如果在顯示屬性中選中“拖動時顯示窗體內容”的顯示效果選項,則上述過程無法觀察。)
對QQ窗體,其移動過程與上述無異,但卻有一處不同。當我們把矩形移動到屏幕四邊且已有部分超出時,矩形就會自動地停留在超出位置上並完整顯示。此時不論我們怎樣試圖把矩形再向超出方向上移動,矩形也只保持在該位置。當釋放鼠標之後,窗體的隱藏效果也就出現了。
從上述過程可以推斷,觸發隱藏條件後,即使仍處於移動過程但矩形本身卻已經被鎖定,因此對窗體位置的判斷是發生在移動過程中,也就是說我們要攔截處理的Windows消息是WM_MOVING。其次,在移動過程中首先發生位置變化的是矩形而不是窗體本身,因此實現隱藏的關鍵是對矩形參數的判斷與設置。
我們可以先留意一下WM_MOVING消息的語法結構:
WM_MOVING
WPARAM wParam
LPARAM lParam,
其中,WPARAM不被使用,而LPARAM則是一個指針,所指向的是一個RECT結構。RECT結構中包含了Left、Top、Right、Bottom四個參數,分別用於描述矩形的左上角與右下角,“該RECT記錄了窗體相對於屏幕的當前位置;當要改變拖動矩形的位置時,程序本身必須改變RECT結構中各成員變量的相關值”。由此可知,我們要處理的矩形其實已經在WM_MOVING消息中被提到,我們要處理的也就是LPARAM所指向的RECT結構的有關參數。
接下來我們要設置一個由隱藏條件激活的計時器,目的是監控鼠標相對窗體的位置。因為窗體隱藏後的隱現是靠鼠標激活的,所以若檢測到鼠標位於窗體之上,則說明窗體在顯示狀態;反之,窗體在隱藏狀態。我們只需在相關的判斷下加入對窗體Top和Left屬性的賦值即可實現隱現效果。
至此,有關自動隱藏效果的實現分析就基本完成了。不過還要注意一點,因為我們是在WM_MOVING消息的攔截處理中判斷隱藏條件,而通過計時器的OnTimer事件處理隱現效果。在此隱藏條件是否滿足在兩個過程中的傳遞將成為關鍵。同時我們要知道的不僅是隱藏條件是否滿足,還必須知道窗體是在屏幕的那一邊上發生隱藏。為此,我們需要定義一個集合去描述窗體隱藏的位置,例如:
type
HidePosKind = (hpTop,hpLeft,hPBottom,hpRight);
type
THidePos = set of HidePosKind;
不過,類似的集合在Delphi本身就已經存在,譬如TAnchors集合。TAnchors集合原來是用於指明一個控件如何錨定於其父類控件的位置,我們在這裡則借用來描述窗體對屏幕的隱藏位置。
在TAnchors集合中也包含了四個值,其定義如下:
type TAnchorKind = (akTop, akLeft, akRight, akBottom);
type TAnchors = set of TAnchorKind;
在代碼的實現中,我們將定義一個TAnchors類型的全局變量FAnchors去描述窗體隱藏的位置。
三、初步的實現
首先我們定義一個過程對WM_MOVING消息進行攔截處理,代碼如下:
.. private FAnchors: TAnchors; procedure WMMOVING(var Msg: TMessage); message WM_MOVING; .. uses Math,type; procedure TForm1.WMMOVING(var Msg: TMessage); begin inherited; with PRect(Msg.LParam)^ do begin Left := Min(Max(0, Left), Screen.Width - Width); Top := Min(Max(0, Top), Screen.Height - Height); Right := Min(Max(Width, Right), Screen.Width); Bottom := Min(Max(Height, Bottom), Screen.Height); FAnchors := []; if Left = 0 then Include(FAnchors, akLeft); if Right = Screen.Width then Include(FAnchors, akRight); if Top = 0 then Include(FAnchors, akTop); if Bottom = Screen.Height then Include(FAnchors, akBottom); Timer1.Enabled := FAnchors <> []; end; end;
在該過程中,我們通過對矩形參數Left、Top、Right、Bottom的判斷確定窗體所處位置是否符合隱藏條件,判斷結果存放在全局變量Fanchors之中。當觸發隱藏時,在Fanchors中將至少有一個值而不多於兩個值。(為什麼呢?)
判斷條件的設置似乎和我們一般的理解有點不同。以Left參數的判斷為例,在判斷了Max(0, Left)之後還為什麼一定要與Screen.Width-Width 的值再作比較呢?這其實是為了對一些較為極端的情況(例如窗體的寬度大於屏幕寬度)所作的偽處理,大家如果有興趣的可自己試驗一下這些極端的效果。當然,如果我們的窗體限制了寬、高的最大值,那麼判斷也就可以簡化為我們最初的理解。
最後需要注意的是,代碼中出現的Left、Top、Right、Bottom都是RECT的參數,而Width和Height才是窗體Form1的屬性。
接下來我們要處理TTimer的OnTimer事件了。在WMMOVING過程中,當Fanchors不為空時,TTimer啟動;反之,TTimer關閉。OnTimer事件的代碼如下:
procedure TForm1.Timer1Timer(Sender: TObject); const cOffset = 2; begin if WindowFromPoint(Mouse.CursorPos) = Handle then begin if akLeft in FAnchors then Left := 0; if akTop in FAnchors then Top := 0; if akRight in FAnchors then Left := Screen.Width - Width; if akBottom in FAnchors then Top := Screen.Height - Height; end else begin if akLeft in FAnchors then Left := -Width + cOffset; if akTop in FAnchors then Top := -Height + cOffset; if akRight in FAnchors then Left := Screen.Width - cOffset; if akBottom in FAnchors then Top := Screen.Height - cOffset; end; end;
在這裡,我們首先定義