某天在論壇上看到有人發(fā)帖詢問QQ自動伸縮窗口是怎么實現(xiàn)的,我也好想知道,于是到百度一搜索,結(jié)果不多,來來去去都是那幾篇,下載那些demo運行一下,發(fā)覺效果與QQ相差很大,于是決定自己動手做個,
要求要近乎完美地模仿這個功能。由于是些效果的東西,貼圖也看不出來,所以文章里就不截圖了,想看效果的就直接運行源代碼的demo吧。
一、觀察
模仿前最重要的一步就是觀察,經(jīng)過半天對QQ的擺弄和摸索,總結(jié)出了以下一些特點:
1、窗口開始粘附時,檢測的是鼠標(biāo)坐標(biāo)與桌面邊界的距離,特別地,粘附在下面的時候,檢測的是與任務(wù)欄的距離;
2、在向上移動窗口時,窗口邊界永遠(yuǎn)不會超出桌面上面邊界;
3、窗口是個 TopMost 風(fēng)格;
4、當(dāng)窗口粘附在上面、左邊或右邊并顯示時,你把鼠標(biāo)移動到最頂端,光標(biāo)變成改變窗口大小的圖標(biāo),而單單是把窗口的top坐標(biāo)設(shè)置為0是不行的;
5、粘附在下面的時候,當(dāng)處于移動狀態(tài),那么窗口的底邊是與任務(wù)欄頂邊對齊的,但從隱藏到顯示的時候,窗口的底端是與屏幕底邊對齊的;
6、隱藏后顯露出來的那條線可能是一個Border,但肯定的是絕不包含Client區(qū)域;
7、關(guān)于響應(yīng)鼠標(biāo)的進(jìn)入與移出窗口,絕對不是WM_MOUSEMOVE、WM_MOUSELEAVE。證明:你以及其慢的速度接觸隱藏狀態(tài)的QQ邊界,你會發(fā)現(xiàn)幾乎是“一觸即發(fā)”,你又以及其慢的速度移出顯示狀態(tài)的QQ,你會發(fā)現(xiàn)它的收縮反而不是“一觸即發(fā)”的,而是離邊緣10象素左右。而WM_MOUSEMOVE,WM_MOUSELEAVE,只有在進(jìn)入、移出Client區(qū)域才響應(yīng),明顯和QQ不同,其實從第6點也可以知道;
8、粘附在兩邊的時候,高度會調(diào)整為桌面上邊界到任務(wù)欄下邊界的距離;
9、在“拖動時顯示窗口內(nèi)容”模式下(桌面屬性-外觀-效果),粘附在兩邊的拖動出來時;如果收縮之前高度比收縮后小則回復(fù)原來高度,在非“拖動時顯示窗口內(nèi)容”模式下,光柵會回復(fù)原來高度,但釋放左鍵時,高度卻是收縮時調(diào)整后的高度,一開始我以為這是個BUG,但我編寫時同樣出現(xiàn)這個問題,發(fā)現(xiàn)這兩種模式會影響WM_MOVING參數(shù)的意義;
10、粘附在兩邊的時候當(dāng)你設(shè)置任務(wù)欄自動隱藏,QQ窗口會自動調(diào)整高度充滿屏幕高度;
11、窗口顯示或隱藏不是一瞬間的,這點在第9點提到的兩種模式下,會有所不同;
12、任務(wù)欄并不顯示QQ窗口;
二、編寫代碼
觀察完畢,就開始編寫了。
首先新建一個基于對話框的MFC程序,命名為QQHideWnd,在對話框?qū)傩缘膕tyles頁把border改為Resizing,你也可同時把Entended styles 的 tool window 鉤上,對于這點我在程序了動態(tài)修改了。
在QQHideWndDlg.h頭文件添加以下成員函數(shù):
protected:
//修正移動時窗口的大小
void FixMoving(UINT fwSide, LPRECT pRect);
//從收縮狀態(tài)顯示窗口
void DoShow();
//從顯示狀態(tài)收縮窗口
void DoHide();
//重載函數(shù),只是為了方便調(diào)用,實際調(diào)用CWnd的SetWindowPos(…)
BOOL SetWindowPos(const CWnd* pWndInsertAfter,LPCRECT pCRect, UINT nFlags = SWP_SHOWWINDOW);
繼續(xù)添加成員變量:
private::BOOL m_isSizeChanged;//窗口大小是否改變了
BOOL m_isSetTimer;//是否設(shè)置了檢測鼠標(biāo)的Timer
INTm_oldWndHeight;//舊的窗口寬度INTm_taskBarHeight;//任務(wù)欄高度INTm_edgeHeight;//邊緣高度
INTm_edgeWidth;//邊緣寬度
INTm_hideMode;//隱藏模式
BOOL m_hsFinished;//隱藏或顯示過程是否完成
BOOL m_hiding;//該參數(shù)只有在!m_hsFinished才有效
//真:正在隱藏,假:正在顯示
增加消息響應(yīng),需要注意的是有些消息你只有把右下角的 Filter for message設(shè)置為window才能看到。
WM_ NCHITTEST
WM_MOVING
WM_CREATE
WM_TIMER
然后來到對應(yīng)的cpp文件,在頭部定義一些宏:
//收縮模式#define HM_NONE0//不收縮
#define HM_1//向上收縮
#define HM_BOTTOM2//向下收縮
#define HM_LEFT3//向左收縮
#define HM_RIGHT4//向右收縮
#define CM_ELAPSE200 //檢測鼠標(biāo)是否離開窗口的時間間隔
#define HS_ELAPSE5//伸縮過程每步的時間間隔
#define HS_STEPS10//伸縮過程分成多少步完成
#define INTERVAL20//觸發(fā)粘附時鼠標(biāo)與屏幕邊界的最小間隔,單位為象素
#define INFALTE10//觸發(fā)收縮時鼠標(biāo)與窗口邊界的最小間隔,單位為象素
要求要近乎完美地模仿這個功能。由于是些效果的東西,貼圖也看不出來,所以文章里就不截圖了,想看效果的就直接運行源代碼的demo吧。
一、觀察
模仿前最重要的一步就是觀察,經(jīng)過半天對QQ的擺弄和摸索,總結(jié)出了以下一些特點:
1、窗口開始粘附時,檢測的是鼠標(biāo)坐標(biāo)與桌面邊界的距離,特別地,粘附在下面的時候,檢測的是與任務(wù)欄的距離;
2、在向上移動窗口時,窗口邊界永遠(yuǎn)不會超出桌面上面邊界;
3、窗口是個 TopMost 風(fēng)格;
4、當(dāng)窗口粘附在上面、左邊或右邊并顯示時,你把鼠標(biāo)移動到最頂端,光標(biāo)變成改變窗口大小的圖標(biāo),而單單是把窗口的top坐標(biāo)設(shè)置為0是不行的;
5、粘附在下面的時候,當(dāng)處于移動狀態(tài),那么窗口的底邊是與任務(wù)欄頂邊對齊的,但從隱藏到顯示的時候,窗口的底端是與屏幕底邊對齊的;
6、隱藏后顯露出來的那條線可能是一個Border,但肯定的是絕不包含Client區(qū)域;
7、關(guān)于響應(yīng)鼠標(biāo)的進(jìn)入與移出窗口,絕對不是WM_MOUSEMOVE、WM_MOUSELEAVE。證明:你以及其慢的速度接觸隱藏狀態(tài)的QQ邊界,你會發(fā)現(xiàn)幾乎是“一觸即發(fā)”,你又以及其慢的速度移出顯示狀態(tài)的QQ,你會發(fā)現(xiàn)它的收縮反而不是“一觸即發(fā)”的,而是離邊緣10象素左右。而WM_MOUSEMOVE,WM_MOUSELEAVE,只有在進(jìn)入、移出Client區(qū)域才響應(yīng),明顯和QQ不同,其實從第6點也可以知道;
8、粘附在兩邊的時候,高度會調(diào)整為桌面上邊界到任務(wù)欄下邊界的距離;
9、在“拖動時顯示窗口內(nèi)容”模式下(桌面屬性-外觀-效果),粘附在兩邊的拖動出來時;如果收縮之前高度比收縮后小則回復(fù)原來高度,在非“拖動時顯示窗口內(nèi)容”模式下,光柵會回復(fù)原來高度,但釋放左鍵時,高度卻是收縮時調(diào)整后的高度,一開始我以為這是個BUG,但我編寫時同樣出現(xiàn)這個問題,發(fā)現(xiàn)這兩種模式會影響WM_MOVING參數(shù)的意義;
10、粘附在兩邊的時候當(dāng)你設(shè)置任務(wù)欄自動隱藏,QQ窗口會自動調(diào)整高度充滿屏幕高度;
11、窗口顯示或隱藏不是一瞬間的,這點在第9點提到的兩種模式下,會有所不同;
12、任務(wù)欄并不顯示QQ窗口;
二、編寫代碼
觀察完畢,就開始編寫了。
首先新建一個基于對話框的MFC程序,命名為QQHideWnd,在對話框?qū)傩缘膕tyles頁把border改為Resizing,你也可同時把Entended styles 的 tool window 鉤上,對于這點我在程序了動態(tài)修改了。
在QQHideWndDlg.h頭文件添加以下成員函數(shù):
protected:
//修正移動時窗口的大小
void FixMoving(UINT fwSide, LPRECT pRect);
//從收縮狀態(tài)顯示窗口
void DoShow();
//從顯示狀態(tài)收縮窗口
void DoHide();
//重載函數(shù),只是為了方便調(diào)用,實際調(diào)用CWnd的SetWindowPos(…)
BOOL SetWindowPos(const CWnd* pWndInsertAfter,LPCRECT pCRect, UINT nFlags = SWP_SHOWWINDOW);
繼續(xù)添加成員變量:
private::BOOL m_isSizeChanged;//窗口大小是否改變了
BOOL m_isSetTimer;//是否設(shè)置了檢測鼠標(biāo)的Timer
INTm_oldWndHeight;//舊的窗口寬度INTm_taskBarHeight;//任務(wù)欄高度INTm_edgeHeight;//邊緣高度
INTm_edgeWidth;//邊緣寬度
INTm_hideMode;//隱藏模式
BOOL m_hsFinished;//隱藏或顯示過程是否完成
BOOL m_hiding;//該參數(shù)只有在!m_hsFinished才有效
//真:正在隱藏,假:正在顯示
增加消息響應(yīng),需要注意的是有些消息你只有把右下角的 Filter for message設(shè)置為window才能看到。
WM_ NCHITTEST
WM_MOVING
WM_CREATE
WM_TIMER
然后來到對應(yīng)的cpp文件,在頭部定義一些宏:
//收縮模式#define HM_NONE0//不收縮
#define HM_1//向上收縮
#define HM_BOTTOM2//向下收縮
#define HM_LEFT3//向左收縮
#define HM_RIGHT4//向右收縮
#define CM_ELAPSE200 //檢測鼠標(biāo)是否離開窗口的時間間隔
#define HS_ELAPSE5//伸縮過程每步的時間間隔
#define HS_STEPS10//伸縮過程分成多少步完成
#define INTERVAL20//觸發(fā)粘附時鼠標(biāo)與屏幕邊界的最小間隔,單位為象素
#define INFALTE10//觸發(fā)收縮時鼠標(biāo)與窗口邊界的最小間隔,單位為象素