Visual C++設計超強仿QQ自動伸縮窗口

摘要:

某天在論壇上看到有人發帖詢問QQ自動伸縮窗口是怎麽實現的,我也好想知道,于是到百度一搜索,結果不多,來來去去都是那幾篇,下載那些demo運行一下,發覺效果與QQ相差很大,于是決定自己動手做個,要求要近乎完美地模擬這個功能。由于是些效果的東西,貼圖也看不出來,所以文章裏就不截圖了,想看效果的就直接運行源代碼的demo吧。

一、觀察

模擬前最重要的一步就是觀察,經過半天對QQ的擺弄和摸索,總結出了以下一些特點:

1、窗口開始粘附時,檢測的是鼠標坐標與桌面邊界的距離,非凡地,粘附在下面的時候,檢測的是與任務欄的距離;

2、在向上移動窗口時,窗口邊界永遠不會超出桌面上面邊界;

3、窗口是個 TopMost 風格;

4、當窗口粘附在上面、左邊或右邊並顯示時,你把鼠標移動到最頂端,光標變成改變窗口大小的圖標,而單單是把窗口的top坐標設置爲0是不行的;

5、粘附在下面的時候,當處于移動狀態,那麽窗口的底邊是與任務欄頂邊對齊的,但從隱藏到顯示的時候,窗口的底端是與屏幕底邊對齊的;

6、隱藏後顯露出來的那條線可能是一個Border,但肯定的是絕不包含Client區域;

7、關于響應鼠標的進入與移出窗口,絕對不是WM_MOUSEMOVE、WM_MOUSELEAVE。證實:你以及其慢的速度接觸隱藏狀態的QQ邊界,你會發現幾乎是「一觸即發」,你又以及其慢的速度移出顯示狀態的QQ,你會發現它的收縮反而不是「一觸即發」的,而是離邊緣10象素左右。而WM_MOUSEMOVE,WM_MOUSELEAVE,只有在進入、移出Client區域才響應,明顯和QQ不同,其實從第6點也可以知道;

8、粘附在兩邊的時候,高度會調整爲桌面上邊界到任務欄下邊界的距離;

9、在「拖動時顯示窗口內容」模式下(桌面屬性-外觀-效果),粘附在兩邊的拖動出來時;假如收縮之前高度比收縮後小則回複原來高度,在非「拖動時顯示窗口內容」模式下,光柵會回複原來高度,但釋放左鍵時,高度卻是收縮時調整後的高度,一開始我以爲這是個BUG,但我編寫時同樣出現這個問題,發現這兩種模式會影響WM_MOVING參數的意義;

10、粘附在兩邊的時候當你設置任務欄自動隱藏,QQ窗口會自動調整高度布滿屏幕高度;

11、窗口顯示或隱藏不是一瞬間的,這點在第9點提到的兩種模式下,會有所不同;

12、任務欄並不顯示QQ窗口;

二、編寫代碼

觀察完畢,就開始編寫了。

首先新建一個基于對話框的MFC程序,命名爲QQHideWnd,在對話框屬性的styles頁把border改爲Resizing,你也可同時把Entended styles 的 tool window 鈎上,對于這點我在程序了動態修改了。

在QQHideWndDlg.h頭文件添加以下成員函數:

protected:

//修正移動時窗口的大小

void FixMoving(UINT fwSide, LPRECT pRect);

//從收縮狀態顯示窗口

void DoShow();

//從顯示狀態收縮窗口

void DoHide();

//重載函數,只是爲了方便調用,實際調用CWnd的SetWindowPos(…)

BOOL SetWindowPos(const CWnd* pWndInsertAfter,LPCRECT pCRect, UINT nFlags = SWP_SHOWWINDOW);

繼續添加成員變量:

private::BOOL m_isSizeChanged;//窗口大小是否改變了

BOOL m_isSetTimer;//是否設置了檢測鼠標的Timer

INTm_oldWndHeight;//舊的窗口寬度INTm_taskBarHeight;//任務欄高度INTm_edgeHeight;//邊緣高度

INTm_edgeWidth;//邊緣寬度

INTm_hideMode;//隱藏模式

BOOL m_hsFinished;//隱藏或顯示過程是否完成

BOOL m_hiding;//該參數只有在!m_hsFinished才有效

//真:正在隱藏,假:正在顯示

增加消息響應,需要注重的是有些消息你只有把右下角的 Filter for message設置爲window才能看到。

WM_ NCHITTEST

WM_MOVING

WM_CREATE

WM_TIMER

然後來到對應的cpp文件,在頭部定義一些宏:

//收縮模式#define HM_NONE0//不收縮

#define HM_TOP1//向上收縮

#define HM_BOTTOM2//向下收縮

#define HM_LEFT3//向左收縮

#define HM_RIGHT4//向右收縮

#define CM_ELAPSE200 //檢測鼠標是否離開窗口的時間間隔

#define HS_ELAPSE5//伸縮過程每步的時間間隔

#define HS_STEPS10//伸縮過程分成多少步完成

#define INTERVAL20//觸發粘附時鼠標與屏幕邊界的最小間隔,單位爲象素

#define INFALTE10//觸發收縮時鼠標與窗口邊界的最小間隔,單位爲象素

然後在構造函數初始化成員變量:

m_isSizeChanged = FALSE;

m_isSetTimer = FALSE;m_hsFinished = TRUE;

m_hiding = FALSE;m_oldWndHeight = MINCY;

m_taskBarHeight = 30;

m_edgeHeight = 0;

m_edgeWidth=0;

m_hideMode = HM_NONE;

完成了一些初始的工作,那麽就開始進入要害的函數實現了。首先是在OnCreate做些窗口的初始化和獲得一些系統信息。

【代碼一】

int CQQHideWndDlg::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

if (CDialog::OnCreate(lpCreateStruct) == -1)

return -1;

// TODO: Add your specialized creation code here

//獲得任務欄高度

CWnd* p;

p = this->FindWindow("Shell_TrayWnd",NULL);

if(p != NULL)

{

CRect tRect;

p->GetWindowRect(tRect);

m_taskBarHeight = tRect.Height();

}

//修改風格使得他不在任務欄顯示

ModifyStyleEx(WS_EX_APPWINDOW, WS_EX_TOOLWINDOW);

//去掉關閉按鍵(假如想畫3個按鍵的話)

//ModifyStyle(WS_SYSMENU,NULL);

//獲得邊緣高度和寬度

m_edgeHeight = GetSystemMetrics(SM_CYEDGE);

m_edgeWidth = GetSystemMetrics(SM_CXFRAME);

return 0;

}

接著如何知道鼠標進入或移出窗口呢?在前面我已經證實了WM_MOUSEMOVE和WM_MOUSELEAVE不符合我們的要求,于是我用了WM_ NCHITTEST這個消息,你可以看到我在這個消息響應函數中用了兩個SetTimer,一個用于檢測鼠標是否離開,一個用于伸縮過程,不管你喜歡不喜歡,要達到第7點和第11點,這個是必須的,考慮的效率問題,在不需要的時候關閉這些Timer就好了。

【代碼二】

UINT CQQHideWndDlg::OnNcHitTest(CPoint point)

{

// TODO: Add your message handler code here and/or call default

CString str;

str.Format("Mouse (%d,%d)",point.x,point.y);

GetDlgItem(IDC_CURSOR)->SetWindowText(str);

if(m_hideMode != HM_NONE && !m_isSetTimer &&

//防止鼠標超出屏幕右邊時向右邊收縮造成閃爍

point.x < GetSystemMetrics(SM_CXSCREEN) + INFALTE)

{ //鼠標進入時,假如是從收縮狀態到顯示狀態則開啓Timer

SetTimer(1,CM_ELAPSE,NULL);

m_isSetTimer = TRUE;

m_hsFinished = FALSE;

m_hiding = FALSE;

SetTimer(2,HS_ELAPSE,NULL); //開啓顯示過程

}

return CDialog::OnNcHitTest(point);

}

然後在OnTimer中

【代碼三】

void CQQHideWndDlg::OnTimer(UINT nIDEvent)

{

// TODO: Add your message handler code here and/or call default

if(nIDEvent == 1 )

{

POINT curPos;

GetCursorPos(&curPos);

CString str;

str.Format("Timer On(%d,%d)",curPos.x,curPos.y);

GetDlgItem(IDC_TIMER)->SetWindowText(str);

CRect tRect;

//獲取此時窗口大小

GetWindowRect(tRect);

//膨脹tRect,以達到鼠標離開窗口邊沿一定距離才觸發事件

tRect.InflateRect(INFALTE,INFALTE);

if(!tRect.PtInRect(curPos)) //假如鼠標離開了這個區域

{

KillTimer(1); //關閉檢測鼠標Timer

m_isSetTimer = FALSE;

GetDlgItem(IDC_TIMER)->SetWindowText("Timer Off");

m_hsFinished = FALSE;

m_hiding = TRUE;

SetTimer(2,HS_ELAPSE,NULL); //開啓收縮過程

}

}

if(nIDEvent == 2)

{

if(m_hsFinished) //假如收縮或顯示過程完畢則關閉Timer

KillTimer(2);

else

m_hiding ? DoHide() : DoShow();

}

CDialog::OnTimer(nIDEvent);

}

暫時不管OnTimer中的DoHide(); DoShow();

先來看看核心的函數之一的 FixMoving,該函數在OnMoving中被調用,FixMoving通過檢測鼠標位置和窗口位置來決定窗口的收縮模式,並修正粘附邊界時窗口的位置,從而達到像移動QQ時出現的效果。

【代碼四】

void CQQHideWndDlg::FixMoving(UINT fwSide, LPRECT pRect)

{

POINT curPos;

GetCursorPos(&curPos);

INT screenHeight = GetSystemMetrics(SM_CYSCREEN);

INT screenWidth = GetSystemMetrics(SM_CXSCREEN);

INT height = pRect->bottom - pRect->top;

INT width = pRect->right - pRect->left;

if (curPos.y <= INTERVAL)

{ //粘附在上邊

pRect->bottom = height - m_edgeHeight;

pRect->top = -m_edgeHeight;

m_hideMode = HM_TOP;

}

else if(curPos.y >= (screenHeight - INTERVAL - m_taskBarHeight))

{ //粘附在下邊

pRect->top = screenHeight - m_taskBarHeight - height;

pRect->bottom = screenHeight - m_taskBarHeight;

m_hideMode = HM_BOTTOM;

}

else if (curPos.x < INTERVAL)

{ //粘附在左邊

if(!m_isSizeChanged)

{

CRect tRect;

GetWindowRect(tRect);

m_oldWndHeight = tRect.Height();

}

pRect->right = width;

pRect->left = 0;

pRect->top = -m_edgeHeight;

pRect->bottom = screenHeight - m_taskBarHeight;

m_isSizeChanged = TRUE;

m_hideMode = HM_LEFT;

}

else if(curPos.x >= (screenWidth - INTERVAL))

{ //粘附在右邊

if(!m_isSizeChanged)

{

CRect tRect;

GetWindowRect(tRect);

m_oldWndHeight = tRect.Height();

}

pRect->left = screenWidth - width;

pRect->right = screenWidth;

pRect->top = -m_edgeHeight;

pRect->bottom = screenHeight - m_taskBarHeight;

m_isSizeChanged = TRUE;

m_hideMode = HM_RIGHT;

}

else

{ //不粘附

if(m_isSizeChanged)

{ //假如收縮到兩邊,則拖出來後會變回原來大小

//在"拖動不顯示窗口內容下"只有光柵變回原來大小

pRect->bottom = pRect->top + m_oldWndHeight;

m_isSizeChanged = FALSE;

}

if(m_isSetTimer)

{ //假如Timer開啓了,則關閉之

if(KillTimer(1) == 1)

m_isSetTimer = FALSE;

}

m_hideMode = HM_NONE;

GetDlgItem(IDC_TIMER)->SetWindowText("Timer off");

}

}

收縮模式和位置決定後,剩下的工作就由最後兩個核心函數完成了:實現收縮的DoHide(),實現伸展的DoShow()。在這兩個過程中m_hsFinished,m_hiding 這兩個變量起到很重要的控制作用。由于伸縮過程沒完成時,hsFinished始終爲FALSE,所以Timer 2 不會關閉,于是在OnTimer中會重複調用這兩個函數之一,在這兩個函數體內,窗口位置有規律地遞減或遞增就可以達到QQ的「抽屜」效果了,有趣的是即使伸縮過程還沒完成,你也可以在這個過程中改變m_hiding這個值來決定他是伸還是縮,正如QQ一樣。你可以把Timer 2 的事件間隔調大一點,然後在窗口伸縮時,鼠標往返地進出窗口就會很輕易看到這樣有趣的效果(還沒縮進去又被拉了出來,或者還沒拉出來又縮進去了)。

【代碼五】

void CQQHideWndDlg::DoHide()

{

if(m_hideMode == HM_NONE)

return;

CRect tRect;

GetWindowRect(tRect);

INT height = tRect.Height();

INT width = tRect.Width();

INT steps = 0;

switch(m_hideMode)

{

case HM_TOP:

steps = height/HS_STEPS;

tRect.bottom -= steps;

if(tRect.bottom <= m_edgeWidth)

{ //你可以把下面一句替換上面的 ...+=-=steps 達到取消抽屜效果

//更好的辦法是添加個BOOL值來控制,其他case同樣.

tRect.bottom = m_edgeWidth;

m_hsFinished = TRUE; //完成隱藏過程

}

tRect.top = tRect.bottom - height;

break;

case HM_BOTTOM:

steps = height/HS_STEPS;

tRect.top += steps;

if(tRect.top >= (GetSystemMetrics(SM_CYSCREEN) - m_edgeWidth))

{

tRect.top = GetSystemMetrics(SM_CYSCREEN) - m_edgeWidth;

m_hsFinished = TRUE;

}

tRect.bottom = tRect.top + height;

break;

case HM_LEFT:

steps = width/HS_STEPS;

tRect.right -= steps;

if(tRect.right <= m_edgeWidth)

{

tRect.right = m_edgeWidth;

m_hsFinished = TRUE;

}

tRect.left = tRect.right - width;

tRect.top = -m_edgeHeight;

tRect.bottom = GetSystemMetrics(SM_CYSCREEN) - m_taskBarHeight;

break;

case HM_RIGHT:

steps = width/HS_STEPS;

tRect.left += steps;

if(tRect.left >= (GetSystemMetrics(SM_CXSCREEN) - m_edgeWidth))

{

tRect.left = GetSystemMetrics(SM_CXSCREEN) - m_edgeWidth;

m_hsFinished = TRUE;

}

tRect.right = tRect.left + width;

tRect.top = -m_edgeHeight;

tRect.bottom = GetSystemMetrics(SM_CYSCREEN) - m_taskBarHeight;

break;

default:

break;

}

SetWindowPos(&wndTopMost,tRect);

}

【代碼六】

void CQQHideWndDlg::DoShow()

{

if(m_hideMode == HM_NONE)

return;

CRect tRect;

GetWindowRect(tRect);

INT height = tRect.Height();

INT width = tRect.Width();

INT steps = 0;

switch(m_hideMode)

{

case HM_TOP:

steps = height/HS_STEPS;

tRect.top += steps;

if(tRect.top >= -m_edgeHeight)

{ //你可以把下面一句替換上面的 ...+=-=steps 達到取消抽屜效果

//更好的辦法是添加個BOOL值來控制,其他case同樣.

tRect.top = -m_edgeHeight;

m_hsFinished = TRUE; //完成顯示過程

}

tRect.bottom = tRect.top + height;

break;

case HM_BOTTOM:

steps = height/HS_STEPS;

tRect.top -= steps;

if(tRect.top <= (GetSystemMetrics(SM_CYSCREEN) - height))

{

tRect.top = GetSystemMetrics(SM_CYSCREEN) - height;

m_hsFinished = TRUE;

}

tRect.bottom = tRect.top + height;

break;

case HM_LEFT:

steps = width/HS_STEPS;

tRect.right += steps;

if(tRect.right >= width)

{

tRect.right = width;

m_hsFinished = TRUE;

}

tRect.left = tRect.right - width;

tRect.top = -m_edgeHeight;

tRect.bottom = GetSystemMetrics(SM_CYSCREEN) - m_taskBarHeight;

break;

case HM_RIGHT:

steps = width/HS_STEPS;

tRect.left -= steps;

if(tRect.left <= (GetSystemMetrics(SM_CXSCREEN) - width))

{

tRect.left = GetSystemMetrics(SM_CXSCREEN) - width;

m_hsFinished = TRUE;

}

tRect.right = tRect.left + width;

tRect.top = -m_edgeHeight;

tRect.bottom = GetSystemMetrics(SM_CYSCREEN) - m_taskBarHeight;

break;

default:

break;

}

SetWindowPos(&wndTopMost,tRect);

}

BOOL CQQHideWndDlg::SetWindowPos(const CWnd* pWndInsertAfter, LPCRECT pCRect, UINT nFlags)

{

return CDialog::SetWindowPos(pWndInsertAfter,pCRect->left, pCRect->top,

pCRect->right - pCRect->left, pCRect->bottom - pCRect->top, nFlags);

}

到此,程序終于完成了。在我的源代碼中還有對WM_SIZING的處理和定義了與之相關的宏,這些主要是控制窗口在調整大小時不能超過最小的寬度和高度,與QQ的自動伸縮無關,所以不在這裏提及了。

三、結束語

雖然還不能算是完美的模擬,但效果已經非常非常的接近了。也許有人會希奇爲什麽要用Tool Window 風格,這是因爲,這樣在任務欄中不會顯示窗口。從QQ的標題欄高度也可以判定出他也是這種風格,但這樣一來就不能擁有最小化、最大化按鍵了。實際上QQ的最大化、最小化和關閉按鍵都是用DC畫上去的。如何在Caption上增加按鍵,外國一些開源網站有源代碼,我下載並看了一下,發現裏面有個知識點很有趣,那就是更改消息路由,有愛好的可以去下載來學習一下。

QQ的成功很大部分在于他的界面比較人性化(用了MSN後深有感受),而這些界面實現起來原理也許很簡單,難的是觀察東西心要細、設計東西心要密、開發東西心要異。

查閱關于VC的全部文檔

 
QQ尾巴病毒的Visual C++實現探討
作者: yes555 出處: BLOG   自2003起,QQ尾巴病毒可以算是風光了一陣子。它利用IE的郵件頭漏洞在QQ上瘋狂傳播。中毒者在給別人發信息時,病毒會自動在信息文本的後邊添上一句話,話的內容多種多樣,總之就是希...查看完整版>>QQ尾巴病毒的Visual C++實現探討
 
Visual C++.NET應用教程(高等學校計算機語言應用教程)(配光盤)|報價¥29.20|圖書,工業技術,自動化、計算機技術,計算技術、計算機技術,計算機軟件,程序語言、算法語言,唐大仕
目錄:圖書,工業技術,自動化、計算機技術,計算技術、計算機技術,計算機軟件,程序語言、算法語言,品牌:唐大仕基本信息·出版社:清華大學出版社,北京交通大學出版社·頁碼:355 頁碼·出版日:2006年·ISBN:7810826...查看完整版>>Visual C++.NET應用教程(高等學校計算機語言應用教程)(配光盤)|報價¥29.20|圖書,工業技術,自動化、計算機技術,計算技術、計算機技術,計算機軟件,程序語言、算法語言,唐大仕
 
Visual C++/Turbo C串口通信編程實踐(第2版)(附盤)|報價¥48.70|圖書,工業技術,自動化、計算機技術,計算技術、計算機技術,計算機軟件,程序語言、算法語言,龔建偉
目錄:圖書,工業技術,自動化、計算機技術,計算技術、計算機技術,計算機軟件,程序語言、算法語言,品牌:龔建偉基本信息·出版社:電子工業出版社·頁碼:557 頁碼·出版日:2007年·ISBN:9787121049422·條碼:97871...查看完整版>>Visual C++/Turbo C串口通信編程實踐(第2版)(附盤)|報價¥48.70|圖書,工業技術,自動化、計算機技術,計算技術、計算機技術,計算機軟件,程序語言、算法語言,龔建偉
 
Visual C++.NET應用教程(高等學校計算機語言應用教程)(配光盤)|報價¥29.20|圖書,工業技術,自動化、計算機技術,計算技術、計算機技術,計算機軟件,程序語言、算法語言,唐大仕
目錄:圖書,工業技術,自動化、計算機技術,計算技術、計算機技術,計算機軟件,程序語言、算法語言,品牌:唐大仕基本信息·出版社:清華大學出版社,北京交通大學出版社·頁碼:355 頁碼·出版日:2006年·ISBN:7810826...查看完整版>>Visual C++.NET應用教程(高等學校計算機語言應用教程)(配光盤)|報價¥29.20|圖書,工業技術,自動化、計算機技術,計算技術、計算機技術,計算機軟件,程序語言、算法語言,唐大仕
 
Visual C++設計UDP協議通訊示例
一、緒言   UDP是一種面向非連接,不可靠的通訊協議,相對于TCP來說,雖然可靠性不及,但傳輸效率較高。所以在網絡上仍有很大的用途。這幾日需要了解下udp通訊的過程,上網發現這方面的資料還挺少。于是仔細的翻找...查看完整版>>Visual C++設計UDP協議通訊示例