本来是一篇复制下来的文章,现在找不到作者了请原谅。
探索QQ隐藏原理

一、【观察】

模仿前最重要的一步就是观察,经过半天对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窗口;

二、【编写代码】

1、观察完毕,就开始编写了。
首先新建一个基于对话框的MFC程序,命名为QQHideWnd,在对话框属性的styles页把border改为Resizing,你也可同时把Entended styles 的 tool window 钩上,对于这点我在程序了动态修改了。
在QQHideWndDlg.h头文件添加以下成员函数:

protected:
//修正移动时窗口的大小
void FixMoving(UINT fwSide, LPRECT pRect);
//修正改改变窗口大小时窗口的大小
void FixSizing(UINT fwSide, LPRECT pRect);
//从收缩状态显示窗口
void DoShow();
//从显示状态收缩窗口
void DoHide();
//重载函数,只是为了方便调用
BOOL SetWindowPos(const CWnd* pWndInsertAfter,
LPCRECT pCRect, UINT nFlags = SWP_SHOWWINDOW);
2、继续添加成员变量:
private:
BOOL m_isSizeChanged; //窗口大小是否改变了
BOOL m_isSetTimer; //是否设置了检测鼠标的Timer

INT  m_oldWndHeight;    //旧的窗口宽度
INT  m_taskBarHeight;   //任务栏高度
INT  m_edgeHeight;      //边缘高度
INT  m_edgeWidth;       //边缘宽度INT  m_hideMode;        //隐藏模式
BOOL m_hsFinished;      //隐藏或显示过程是否完成
BOOL m_hiding;          //该参数只有在!m_hsFinished才有效
//真:正在隐藏,假:正在显示
/*
public:
BOOL m_topMost;         //是否使用TopMost风格
BOOL m_useSteps;        //是否使用抽屉效果
BOOL m_toolWnd;         //是否使用Tool Window 风格
*/

3、增加消息响应,需要注意的是有些消息你只有把右下角的 Filter for message设置为window才能看到。
WM_ NCHITTEST
WM_MOVING

WM_CREATE

WM_TIMER

WM_SIZING

4、然后来到Dlg对应的cpp文件,在头部定义一些宏:

//收缩模式

define HM_NONE 0 //不收缩

define HM_TOP 1 //向上收缩

define HM_BOTTOM 2 //向下收缩

define HM_LEFT 3 //向左收缩

define HM_RIGHT 4 //向右收缩

define CM_ELAPSE 200 //检测鼠标是否离开窗口的时间间隔

define HS_ELAPSE 5 //隐藏或显示过程每步的时间间隔

define HS_STEPS 10 //隐藏或显示过程分成多少步

define INTERVAL 20 //触发粘附时鼠标与屏幕边界的最小间隔,单位为象素

define INFALTE 10 //触发收缩时鼠标与窗口边界的最小间隔,单位为象素

define MINCX 200 //窗口最小宽度

define MINCY 400 //窗口最小高度

5、然后在构造函数初始化成员变量:

CQQHideWndDlg::CQQHideWndDlg(CWnd* pParent /=NULL/)
: CDialogEx(CQQHideWndDlg::IDD, pParent)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
/---------------------------------1-/
//构造函数初始化成员变量
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;
/*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-2-*/

}
6、完成了一些初始的工作,那么就开始进入关键的函数实现了。首先是在OnCreate做些窗口的初始化和获得一些系统信息。
//添加消息函数 WM_CREATE
int CQQHideWndDlg::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CDialogEx::OnCreate(lpCreateStruct) == -1)
return -1;

// TODO:  在此添加您专用的创建代码
//获得任务栏高度
CWnd* p;
p = this->FindWindow(_T("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;

}
7、接着如何知道鼠标进入或移出窗口呢?在前面我已经证明了WM_MOUSEMOVE和WM_MOUSELEAVE不符合我们的要求,于是我用了WM_ NCHITTEST这个消息,你可以看到我在这个消息响应函数中用了两个SetTimer,一个用于检测鼠标是否离开,一个用于伸缩过程,不管你喜欢不喜欢,要达到第7点和第11点,这个是必须的,考虑的效率问题,在不需要的时候关闭这些Timer就好了。
//添加消息函数 WM_ NCHITTEST
LRESULT CQQHideWndDlg::OnNcHitTest(CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
CString str;
str.Format(_T(“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 CDialogEx::OnNcHitTest(point);

}
8、紧接着OnTimer函数

//添加消息函数 WM_TIMER
void CQQHideWndDlg::OnTimer(UINT_PTR nIDEvent)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
if (nIDEvent == 1)
{
POINT curPos;
GetCursorPos(&curPos);

    CString str;str.Format(_T("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); //关闭检测鼠标Timerm_isSetTimer = FALSE;GetDlgItem(IDC_TIMER)->SetWindowText(_T("Timer Off"));m_hsFinished = FALSE;m_hiding = TRUE;SetTimer(2, HS_ELAPSE, NULL); //开启收缩过程}
}if (nIDEvent == 2)
{if (m_hsFinished) //如果收缩或显示过程完毕则关闭TimerKillTimer(2);elsem_hiding ? DoHide() : DoShow();
}
CDialogEx::OnTimer(nIDEvent);

}
9、暂时不管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(_T("Timer off"));
}

}
10、收缩模式和位置决定后,剩下的工作就由最后两个核心函数完成了:实现收缩的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);
}
11、NEXT写DoShow()函数

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);

}
12、添加OnSizing响应函数

//相应消息函数 WM_SIZING
void CQQHideWndDlg::OnSizing(UINT fwSide, LPRECT pRect)
{
FixSizing(fwSide, pRect); //修正pRect
CDialogEx::OnSizing(fwSide, pRect);

// TODO:  在此处添加消息处理程序代码

}
13、添加OnMoving响应函数
//添加消息函数 WM_MOVING
void CQQHideWndDlg::OnMoving(UINT fwSide, LPRECT pRect)
{
FixMoving(fwSide, pRect); //修正pRect
CDialogEx::OnMoving(fwSide, pRect);

// TODO:  在此处添加消息处理程序代码

}
14、接着SetWindowPos和FixSizing函数
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);
}
void CQQHideWndDlg::FixSizing(UINT fwSide, LPRECT pRect)
{
CRect curRect(pRect);

if (curRect.Width() < MINCX || curRect.Height() < MINCY)
{   //小于指定的最小宽度或高度switch (fwSide){case WMSZ_BOTTOM:pRect->bottom = pRect->top + MINCY;break;case WMSZ_BOTTOMLEFT:if (curRect.Width() <= MINCX)pRect->left = pRect->right - MINCX;if (curRect.Height() <= MINCY)pRect->bottom = pRect->top + MINCY;break;case WMSZ_BOTTOMRIGHT:if (curRect.Width() < MINCX)pRect->right = pRect->left + MINCX;if (curRect.Height() < MINCY)pRect->bottom = pRect->top + MINCY;break;case WMSZ_LEFT:pRect->left = pRect->right - MINCX;break;case WMSZ_RIGHT:pRect->right = pRect->left + MINCX;break;case WMSZ_TOP:pRect->top = pRect->bottom - MINCY;break;case WMSZ_TOPLEFT:if (curRect.Width() <= MINCX)pRect->left = pRect->right - MINCX;if (curRect.Height() <= MINCY)pRect->top = pRect->bottom - MINCY;break;case WMSZ_TOPRIGHT:if (curRect.Width() < MINCX)pRect->right = pRect->left + MINCX;if (curRect.Height() < MINCY)pRect->top = pRect->bottom - MINCY;break;}
}

}
15、到此,程序终于完成了。上面源代码中有对WM_SIZING的处理和定义了与之相关的宏,这些主要是控制窗口在调整大小时不能超过最小的宽度和高度,与QQ的自动伸缩无关,所以在这里就提一下。

三、【结束语】

虽然还不能算是完美的模仿,但效果已经非常非常的接近了。也许有人会奇怪为什么要用Tool Window 风格,这是因为,这样在任务栏中不会显示窗口。从QQ的标题栏高度也可以判断出他也是这种风格,但这样一来就不能拥有最小化、最大化按键了。实际上QQ的最大化、最小化和关闭按键都是用DC画上去的。如何在Caption上增加按键,外国一些开源网站有源代码,我下载并看了一下,发现里面有个知识点很有趣,那就是更改消息路由,有兴趣的可以去下载来学习一下。

1、下载源代码(百度云盘)

2、http://download.csdn.net/detail/u013100446/8281311(CSDN下载)

Win32探索QQ隐藏原理相关推荐

  1. win32版QQ隐藏功能,气泡消息显示详细时间

    偶然发现win32版QQ的一个隐藏功能,QQ在电脑上的消息可以显示为气泡模式和经典模式,气泡模式通常情况下不会显示详细的时间信息,QQ有一个隐藏功能,按鼠标中键可以显示一会详细时间,效果如图 正常是这 ...

  2. QQ隐藏图原理与C#实现(含源文件)

    前往我的博客以获得更好的阅读体验QQ隐藏图原理与C#实现(含源文件) - DearXuan的主页https://blog.dearxuan.com/2021/08/26/QQ%E9%9A%90%E8% ...

  3. 显示封装_怎么显示与隐藏原理图库的PCB封装名称?

    标题:怎么显示与隐藏原理图库的PCB封装名称? 答:这里我们分为两种情况进行分析,一种是在绘制原理图库的时候,怎么显示与隐藏元器件封装名称:另外一种是在绘制原理图的时候,怎么显示与隐藏元器件封装名称. ...

  4. pyqt win32发送QQ消息

    标题应该改为:python+win32发送QQ消息,全程使用python套个pyqt壳. 其实代码来自: http://blog.csdn.net/suzyu12345/article/details ...

  5. QQ通信原理--转载

    QQ通信原理概述 QQ通信原理概述一.登陆. 不管UDP还是TCP,最终登陆成功之后,QQ都会有一个TCP连接来保持在线状态.这个TCP连接的远程端口一般是80,采用UDP方式登陆的时候,端口是800 ...

  6. 计算机图像隐藏信息,信息隐藏原理及应用

    <信息隐藏原理及应用>是2011年8月5日清华大学出版社出版的图书,作者是葛秀慧.[1] 本书可以作为计算机应用.网络工程.通信与信息系统.信号与处理.信息安全与密码学.电子商务专业的本科 ...

  7. 探究网页中检测QQ登录原理

    探究网页中检测QQ登录原理 在登录QQ邮箱时会自动检测QQ是否已经登录(阿里旺旺也有这个功能了,不过经常不好用),今天突然发觉,这也是个小小的技术点,今天就来探究一下 PS:之前也有人分析过此问题,如 ...

  8. 信息隐藏原理与技术复习

    信息隐藏原理与技术 第一章 1.密码发展的三个阶段 第一阶段:古典密码,是从古代到1949年,1949年以前的密码技术可以说是一种艺术,而不是一种科学,那时的密 码专家是凭直觉和信念来进行密码设计和分 ...

  9. 用计算机隐藏功能撩妹,除“自动回复”外,QQ隐藏的3个“撩妹功能”,会玩的已经脱单...

    这段时间QQ自动回复,在网上特别火,因为QQ的新版更新之后,就可以编辑自动回复的内容了,很多人都用自动回复这个功能来撩妹.除"自动回复"外,QQ隐藏的3个"撩妹功能&qu ...

最新文章

  1. Redis的多种启动方式比较!
  2. 是否存在两台 MacOS 之间无缝切换的办法?
  3. 横空出世,席卷互联网--评微软等公司数据结构+算法面试100题
  4. 设置 Nuget 本地源、在线私有源、自动构建打包
  5. Ansible 介绍和架构、特性
  6. 代理模式详解(包含原理详解)
  7. 计算机器内存数量+引入和显示ARDS成员
  8. [教程]一份简单易懂的 TensorFlow 教程
  9. java native 接口_Java本地接口--Java Native Interface (JNI)
  10. 计算机一级查询记录,技巧查看电脑中使用过的记录痕迹的详细教程
  11. 抗锯齿 文字_PS之使用文字工具
  12. ES6-weakset集合
  13. 只会用console.log调试?快来试试这些高效的调试方法
  14. 为什么很多人赚不到钱?
  15. poj 3243 Clever Y(Baby-Step Giant-Step)
  16. UVALive 7276 Wooden Signs (DP)
  17. 2017-2018-1 20179215 《从问题到程序》第三章
  18. sudo: must be setuid root错误解决方法.
  19. c++ 协程_深入理解异步I/O+epoll+协程
  20. spark压缩和序列化相关

热门文章

  1. mybatis原理(含图)
  2. 爬虫实践---豆瓣短评+词云分析
  3. 相对路径和绝对路径的区别(详解)
  4. Spring:IOC和AOP
  5. 利用Android studio设计WeChat的门户界面
  6. Linux上快速搭建自己的深度学习虚拟环境
  7. 51单片机的有符号数和无符号数运算
  8. 1.1 haas506 2.0开发教程-导学篇
  9. 体育数字化服务,全方位为学校体育赋能.
  10. fadora 23 dnf 更新错误