成人午夜激情影院,小视频免费在线观看,国产精品夜夜嗨,欧美日韩精品一区二区在线播放

[粗]技巧:自動隱藏QQ窗體

2010-08-28 10:46:24來源:西部e網作者:

    騰訊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;

  在這里,我們首先定義一個常量cOffset去表示窗體隱藏后顯露部分的大小。然后我們利用WindowFromPoint這個Windows API函數檢測鼠標是否位于窗體之上。接下來的判斷就是處理在顯示和隱藏狀態下窗體Left和Top屬性值的設置。注意,針對Fanchors中存在不同值的情況,窗體Left和Top的設置是各不相同的,但是這些設置只有順序上的差異而并沒有優先級別的差異。(為什么要提到這一點呢?)

  最后需要注意的是:在本事件中Top、Left、Width和Height都是窗體Form1的屬性值。

  好了,有關窗體隱藏的核心代碼已經介紹完畢了,不過要達到預期效果,窗體Form1在創建時還必須做一些準備工作,代碼如下:

procedure TForm1.FormCreate(Sender: TObject);
begin 
  Timer1.Enabled := False; 
  Timer1.Interval := 200; 
  FormStyle := fsStayOnTop;
end;

  這里的代碼相對簡單,不過值得指出的是對Form1的FormStyle屬性的設置。FormStyle為fsStayOnTop時可保證了Form1始終位于最前顯示。從效果角度看,當系統工具欄為“總在最前顯示”時是最為明顯的,因為若窗體移動到系統工具欄上時也不會被其所遮蓋。

    四、進一步完善

上面的代碼已經基本實現了窗體的自動隱藏效果,但是我在介紹代碼的時候有兩個問題是被提出但沒有被解答的。

  首先是為什么觸發隱藏時Fanchors中將至少有一個值而不多于兩個值呢?注意代碼中對Fanchors的賦值是通過四個判斷進行的,那么如果觸發隱藏的話,Fanchors中將毫無疑問會有一個值存在,但這種情況是針對隱藏發生在屏幕的四邊而言。當窗體被推入到屏幕的四角時,那么Fanchors中便將會有兩個值存在。那此時窗體會隱藏到什么地方呢?

  實際的效果告訴我們,窗體會被隱藏到屏幕的四角上。此時若我們試圖讓窗體重新顯示,你便會發現窗體在不斷的閃爍。為什么呢?這就是第二個問題提出的原因了。因為對窗體顯示或隱藏的處理是根據Fanchors中的值作出的。當Fanchors中有兩個值的時候,就將會引發對窗體屬性的兩次設置。而因為設置語句只有順序差異而沒有優先級差異,那么OnTimer事件中每次都會對窗體進行兩次的屬性值設置,從而導致我們看到閃爍的顯示效果。

  怎么去解決這個問題呢?我們再觀察一下QQ的處理。在2003 II版的QQ里面,窗體的隱藏效果作了一定的調整:當窗體在屏幕左右兩邊隱藏時,它會自動充滿屏幕的左右兩邊且高度不可改變;當窗體脫離屏幕兩邊的隱藏區域后,窗體的大小會恢復為隱藏前的大小。(注意:窗體并非是完全充滿屏幕的兩邊。QQ在處理這個效果時可能只注意了系統工具欄總在最前顯示且位于屏幕下方的情況,所以其充滿的區域也只是屏幕頂端到系統工具欄上方的一段空間。)這樣的處理可以令窗體即使被推入到屏幕四角,也可以保證只會對其中的一個隱藏方向進行處理,從而避免了前面出現的閃爍現象。

  結合前面的分析,要實現如上的效果還是從攔截WM_MOVING消息入手。重寫后的WMMOVING過程如下:

procedure TForm1.WMMOVING(var Msg: TMessage);
begin 
 inherited; 
 with PRect(Msg.LParam)^ do
 begin 
   if (akLeft in FAnchors) or (akRight in FAnchors) then
   begin
     if (Left > 0) and (Right < Screen.Width) then
     begin
       if rec_Position then
       begin
         Bottom := top + Lst_Height;
         Right := Left + Lst_Width;
         Height := Lst_Height;
         Width := Lst_Width;
       end;
     end else
     begin
       SetBarHeight;
       Top := Cur_Top;
       Bottom := Cur_Bottom;
       exit;
     end;
   end;
   Left := Min(Max(0, Left), Screen.Width - Width);
……
   if not Rec_Position then
   begin
     Lst_Height := form1.Height;
     Lst_Width := form1.width;
   end;
   FAnchors := [];
……
   if (akLeft in FAnchors) or (akRight in FAnchors) then
   begin
     Rec_Position := True;
     SetBarHeight;
     Top := Cur_Top;
     Bottom := Cur_Bottom;
   end else
     Rec_Position := False;
   Timer1.Enabled := FAnchors <> []; 
  end;
end;

在新的代碼中,我們首先使用了三個新定義的全局變量,分別是:

Lst_Height : Integer;   //記錄窗體隱藏前的高度
Lst_Width : Integer;        //記錄窗體隱藏前的寬度
Rec_Position : Boolean; //是否啟動窗體寬高記錄標志

  然后加入了三個判斷代碼塊。

  在第一個判斷中首先判定窗體在移動前是否位于屏幕左右兩邊的隱藏區域。若為真,則判斷窗體是否從隱藏區域向屏幕中央移動(注意,存在此判斷的原因是因為我們還可能將窗體往屏幕兩邊推動)。若再為真,則恢復窗體隱藏前的大;反之,強制設置矩形的Top和Bottom值并退出消息的處理。

  第二個判斷在于記錄窗體的寬高值。Rec_Position是記錄窗體寬高的標志,它的值在第三個判斷中進行設置。若窗體在移動前位于屏幕兩邊的隱藏區域,則Rec_Position為True,此時窗體的高度已經固定,記錄已經無意義。所以只在Rec_Position為False時才需要記錄窗體的寬高。

  第三個判斷位于Fanchors值設置之后。它根據窗體的位置對矩形的顯示效果進行判斷處理。判斷也是基于窗體是否位于屏幕兩邊進行,為True則設置矩形的高度并設置Rec_Position的值為True。

  在第三個判斷中使用了一個新定義的過程SetBarHeight,其代碼如下:

procedure TForm1.SetBarHeight;
var
  AppBarData : TAPPBARDATA;
begin
  AppBarData.cbSize := SIZEOF(AppBarData);
  If SHAppBarMessage(ABM_GETSTATE,AppBarData) AND ABS_AUTOHIDE) <> 0 then
  begin
    Cur_Top := 1;
    Cur_Bottom := Screen.Height - 1;
  end else
  begin
    SHAppBarMessage(ABM_GETTASKBARPOS,AppBarData);
    case AppBarData.uEdge of
      ABE_TOP :    begin
                     Cur_Top := AppBarData.rc.Bottom + 1;
                     Cur_Bottom := Screen.Height - 1;
                   end;
      ABE_LEFT :   begin
                     Cur_Top := 1;
                     Cur_Bottom := Screen.Height - 1;
                   end;
      ABE_RIGHT :  begin
                     Cur_Top := 1;
                     Cur_Bottom := Screen.Height - 1;
                   end;
      ABE_BOTTOM : begin
                    Cur_Top := 1;
                     Cur_Bottom:=Screen.Height -(AppBarData.rc.Bottom - AppBarData.rc.Top) - 1;
                   end;
    end;
  end;
end;

  SetBarHeight用于計算矩形高度,計算后的結果通過Cur_Top和Cur_Bottom兩個全局變量給傳遞矩形的Top和Bottom參數。

  在該過程中使用了一個Windows API函數SHAppBarMessage。SHAppBarMessage的作用是向系統傳遞系統工具欄消息,其函數原型為:

WINSHELLAPI UINT APIENTRY SHAppBarMessage(DWORD dwMessage,   PAPPBARDATA pData);

  其中dwMessage是發送給系統的工具欄消息;pData是指向PAPPBARDATA結構的指針,PAPPBARDATA結構返回的內容依據發出的消息而定。

  在過程中,我們首先傳遞ABM_GETSTATE參數去獲取系統工具欄的狀態是自動隱藏還是總在最前顯示。然后我們再利用ABM_GETTASKBARPOS參數去獲取系統工具欄的位置,此時AppBarData的返回值中將會是系統工具欄的位置ABE_TOP、ABE_LEFT、ABE_RIGHT、ABE_BOTTOM四者之一。最后我們利用系統工具欄自身的拖動矩形參數計算出工具欄的高度。

  使用了SetBarHeight令窗體在屏幕兩邊隨系統工具欄的位置和高度的改動而發生相應的變化。當然,你也可以直接給Cur_Top和Cur_Bottom這兩個變量設置固定值以實現QQ效果。在測試中,Cur_Top可以是1,而Cur_Bottom則是Screen.Width – 30(Windows系統工具欄的高度在默認情況下是30,這是不隨分辨率改變的)。

  由于要使窗體在屏幕兩邊的高度與位置可以隨系統工具欄的位置和高度的改動而發生相應的變化,因此OnTimer事件中的處理也要相應的改動,主要是顯示窗體的時候要注意對窗體Top和Height屬性的設置必須跟隨與系統工具欄的位置和高度相協調,代碼如下:

……
    if akLeft in FAnchors then
    begin
      Left := -Width + cOffset;
      SetBarHeight;
      Top := Cur_Top;
      Height := Cur_Bottom;
    end;
    if akRight in FAnchors then
    begin
      Left := Screen.Width - cOffset;
      SetBarHeight;
      Top := Cur_Top;
      Height := Cur_Bottom;
    end;
……

  最后,為了保證窗體在屏幕兩邊隱藏后高度保持不變,我們再添加一個WMSizing過程對WM_Sizing消息進行攔截處理。WMSizing過程的代碼如下:

procedure TForm1.WMSizing(var Msg: TMessage);
begin
  inherited;
  if (akRight in FAnchors) then
  begin
    with PRect(Msg.LParam)^  do
    begin
      Left := Screen.Width - Width;
      Top := Cur_Top;
      Right := Screen.Width;
      Bottom := Cur_Bottom
    end;
  end else if (akLeft in FAnchors) then
  begin
    with PRect(Msg.LParam)^ do
    begin
      Left := 0;
      Top := Cur_Top;
      Right := Width;
      Bottom := Cur_Bottom;
    end;
  end;
end;

  WM_Sizing消息的語法結構與WM_MOVING消息相似,也包含了一個對矩形的指針。通過該指針我們可以對矩形的Top、Left、Right和Bottom參數進行設置,從而保證矩形高度不受用戶操作影響。

  至此,一個窗體自動隱藏的程序就基本完成了,其實際效果已經和QQ相當接近了。當然,從實際運行效果看還存在著一些小瑕疵,并且代碼中并沒有對窗體在隱藏后的寬度設置上進行處理,或者大家可以考慮繼續進行完善此程序。

關鍵詞:QQ

贊助商鏈接:

主站蜘蛛池模板: 北碚区| 兴城市| 拜泉县| 深水埗区| 依兰县| 洞口县| 麻江县| 万全县| 宜宾市| 赣榆县| 白河县| 锡林郭勒盟| 祁门县| 清水河县| 天台县| 罗田县| 九寨沟县| 太原市| 香港 | 惠安县| 泸定县| 五华县| 酒泉市| 衡阳县| 会同县| 永川市| 宁津县| 环江| 洛宁县| 大荔县| 三穗县| 贵德县| 新野县| 中西区| 兴国县| 福鼎市| 大田县| 建瓯市| 宁南县| 隆安县| 册亨县|