qt无边框窗体 拦截windows消息实现
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了qt无边框窗体 拦截windows消息实现,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含8449字,纯文字阅读大概需要13分钟。
内容图文
![qt无边框窗体 拦截windows消息实现](/upload/InfoBanner/zyjiaocheng/929/824e3d05c65a4526acbfd70536d178d7.jpg)
无边框其实就是去掉windows自带的标题栏,去掉标题栏之后手动实现标题栏的功能:
1、左键按住标题栏移动窗体
2、双击标题栏切换最大化和normal状态
3、贴靠窗口功能
4、四个边和四个角resize窗体大小
5、窗体阴影
窗体的客户区和非客户区
用一个超级老的图(来自msdn)来介绍客户区和非客户区,简单来说就是,客户区是下图白色区域,除此之外都是非客户区。
客户区好说,qml写的东西都是堆在客户区的,非客户区比较麻烦,但是win32提供了一些办法来自定义非客户区
https://docs.microsoft.com/en-us/windows/win32/dwm/customframe#related-topics
msdn上面的这个链接提供了非常详细的介绍,简单总结一下
1、客户区和非客户区的关系就类似于ps中的图层,客户区是在非客户的上边
2、我们可以通过win32的api设置非客户区四个边框的大小
这个图左右两个只有细微的差别,那就是看起来左边的客户区带边框,而右边的没有,实际上是有边的非客户区边框被重新设置过,做了加粗处理,客户区覆盖了边框(边框是非客户区的,客户区没有),如果把客户区设置为透明就能看出其中玄机:
窗体的样式
这个属于非客户的功能,窗体带不带标题栏、有没有最大化那几个按钮等都是Windows系统的窗体样式。
每个窗口都有一个或多个窗口样式。 窗口样式是一个命名常量(named constant),它定义了窗口类(并不是那个结构体的window class,而是指create函数创建的一个窗体)未指定的窗口外观和行为。 应用程序通常在创建窗口时设置窗口样式(create函数),也可以在创建window后使用SetWindowLong函数设置样式。
窗体样式的参考可以看这个链接: https://docs.microsoft.com/zh-cn/windows/win32/winmsg/window-styles
除此之外还有扩展样式: https://docs.microsoft.com/zh-cn/windows/win32/winmsg/extended-window-styles
setwindowlong就是设置样式的函数,比如去掉标题栏:
效果如下图
虽然从代码上我们是去掉了标题栏,但是很明显,标题栏还有一个高度,测量了一下是3个像素,所以直接设置窗体样式并不能实现我们的需求(需要别的操作,我看有人可以实现,我没深究这个方案)
与窗体相关的几个Windows的消息
WM_NCHITTEST
鼠标在窗体上的时候Windows发送该消息,通过“窗口过程”的返回值能让Windows了解鼠标是在哪,比如返回HTCAPTION
就是在标题栏中。关于窗口过程这里不细讲。
假设我们拦截了这个消息,然后不管什么情况都返回HTCAPTION
,那么Windows系统就认为你一直在标题栏上。用鼠标左键点按就可以移动窗体。
这个消息是resize和按住标题栏移动窗体的关键。
这个消息的详细定义可以看这里 https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-nchittest
WM_NCCALCSIZE
当客户区大小和坐标需要被重新计算的时候Windows会发送这个消息,默认os会自动处理该消息,但是如果我们拦截该消息并返回一些特定的值比如数字0x00,那么就能更改客户区的大小和坐标。
实际上返回0x00就是让客户区完全覆盖窗体,效果可以看下图
红框就是客户区(客户区做了透明处理),右边的图完全覆盖了窗体(包括非客户区)。其实这就是创造无边框窗体的核心操作
这个消息的详细定义可以看这里 https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-nccalcsize
WM_ACTIVATE
窗口被激活的时候os会发送这条消息
https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-activate
qt中的消息过滤器
win32程序中可以在WndProc
函数中处理消息,这非常方便,但是qt封装了系统相关的东西做到了跨平台,要在qt中截获windows的系统消息就没有win32程序那么方便,但是qt仍开放了接口
使用 QAbstractNativeEventFilter
类 , 参考 https://doc.qt.io/qt-5/qabstractnativeeventfilter.html
除了构造函数,就一个成员函数nativeEventFilter
所有的native消息都会经过这个函数,如果重写该函数,抓取到某个消息后函数返回值为true,那么该消息就永远不会进入qt系统(被阻断了),如果返回false,那么这条消息就会正常进入qt系统被处理。
该函数的第三个参数就相当于win32中WndProc
函数的返回值。比如在win32中处理WM_NCCALCSIZE
消息的代码
在qt的消息过滤器中上图就等价于:
所以:我们继承QAbstractNativeEventFilter
类就可以对Windows系统消息做自定义处理,当然,我们写的消息过滤器要在qt的main函数中注册到qt系统,否则qt不会识别的:
去掉Windows系统的非客户区
其实就是用客户区完全覆盖整个窗体,根据msdn消息说明
截取WM_NCCALCSIZE
消息,并设置返回值为0
就这么简单,就可以实现了。不过首先还是要写一个类,继承QAbstractNativeEventFilter
类 ,代码如下:
此时的窗口,用鼠标根本无法操作,也没有阴影,因为非客户区的所有可控因素都被客户区盖住了。
添加阴影
设置一下非客户区边框就可以了,利用WM_ACTIVATE
消息
resize和移动窗体
利用WM_NCHITTEST
消息
下边代码绝大多数是参考了 https://docs.microsoft.com/en-us/windows/win32/dwm/customframe#appendix-c-hittestnca-function msdn这个提供的代码
case WM_NCHITTEST:
{
//处理resize
//标记只处理resize
bool isResize = false;
//鼠标点击的坐标
POINT ptMouse = { GET_X_LPARAM(pMsg->lParam), GET_Y_LPARAM(pMsg->lParam) };
//窗口矩形
RECT rcWindow;
GetWindowRect(pMsg->hwnd, &rcWindow);
RECT rcFrame = { 0,0,0,0 };
AdjustWindowRectEx(&rcFrame, WS_OVERLAPPEDWINDOW & ~WS_CAPTION, FALSE, NULL);
USHORT uRow = 1;
USHORT uCol = 1;
bool fOnResizeBorder = false;
//确认鼠标指针是否在top或者bottom
if (ptMouse.y >= rcWindow.top && ptMouse.y < rcWindow.top + 1)
{
fOnResizeBorder = (ptMouse.y < (rcWindow.top - rcFrame.top));
uRow = 0;
isResize = true;
}
else if (ptMouse.y < rcWindow.bottom && ptMouse.y >= rcWindow.bottom - 5)
{
uRow = 2;
isResize = true;
}
//确认鼠标指针是否在left或者right
if (ptMouse.x >= rcWindow.left && ptMouse.x < rcWindow.left + 5)
{
uCol = 0; // left side
isResize = true;
}
else if (ptMouse.x < rcWindow.right && ptMouse.x >= rcWindow.right - 5)
{
uCol = 2; // right side
isResize = true;
}
if (ptMouse.x >= rcWindow.left && ptMouse.x <= rcWindow.right - 135 && ptMouse.y > rcWindow.top + 3 && ptMouse.y <= rcWindow.top + 30)
{
*result = HTCAPTION;
return true;
}
LRESULT hitTests[3][3] =
{
{ HTTOPLEFT, fOnResizeBorder ? HTTOP : HTCAPTION, HTTOPRIGHT },
{ HTLEFT, HTNOWHERE, HTRIGHT },
{ HTBOTTOMLEFT, HTBOTTOM, HTBOTTOMRIGHT },
};
if (isResize == true)
{
*result = hitTests[uRow][uCol];
return true;
}
else
{
return false;
}
}
关于移动窗体,这个可选的方案有很多,qt中可以使用mousearea实现,但是使用Windows消息更好用,因为mousearea实现的办法没有“贴边停靠”,而且双击标题栏的操作也要手动实现。
关于贴边停靠功能,我并没有找到什么可查询的资料准确定义怎么实现,我做了很多次尝试,总结一下基本上是有两个条件可以实现,首先是设置窗体样式,具体是哪几个样式我没细究,总之必须有某些样式,才能实现贴边停靠,第二个条件是鼠标按住标题栏贴近屏幕某个边缘。这两个条件缺一不可,这也是为什么移动窗体我是拦截系统消息而不是在qml中使用mousearea的原因。关于第一个条件“窗体样式”在我的代码中我猜测是在WM_ACTIVATE
处理阴影的时候添加了合适的窗口样式,这里没有细究。
完整代码
#ifndef WINMSGFILTER_H
#define WINMSGFILTER_H
#include <QAbstractNativeEventFilter>
#include <QDebug>
#include <Windows.h>
#pragma comment(lib, "dwmapi")
#pragma comment(lib,"user32.lib")
#include <dwmapi.h>
#include <windowsx.h>
class WinMsgFilter :public QAbstractNativeEventFilter
{
public:
WinMsgFilter();
//过滤掉消息返回true,否则返回false
bool nativeEventFilter(const QByteArray& eventType, void* message, long* result) override
{
MSG* pMsg = reinterpret_cast<MSG*>(message);
switch (pMsg->message)
{
//去掉边框
case WM_NCCALCSIZE:
{
*result = 0;
return true;
break;
}
//阴影
case WM_ACTIVATE:
{
MARGINS margins = { 1,1,1,1 };
HRESULT hr = S_OK;
hr = DwmExtendFrameIntoClientArea(pMsg->hwnd, &margins);
*result = hr;
return true;
}
case WM_NCHITTEST:
{
//处理resize
//标记只处理resize
bool isResize = false;
//鼠标点击的坐标
POINT ptMouse = { GET_X_LPARAM(pMsg->lParam), GET_Y_LPARAM(pMsg->lParam) };
//窗口矩形
RECT rcWindow;
GetWindowRect(pMsg->hwnd, &rcWindow);
RECT rcFrame = { 0,0,0,0 };
AdjustWindowRectEx(&rcFrame, WS_OVERLAPPEDWINDOW & ~WS_CAPTION, FALSE, NULL);
USHORT uRow = 1;
USHORT uCol = 1;
bool fOnResizeBorder = false;
//确认鼠标指针是否在top或者bottom,顺带说一下屏幕坐标原点是左上角,窗体坐标原点也是左上角
if (ptMouse.y >= rcWindow.top && ptMouse.y < rcWindow.top + 1)
{
fOnResizeBorder = (ptMouse.y < (rcWindow.top - rcFrame.top));
uRow = 0;
isResize = true;
}
else if (ptMouse.y < rcWindow.bottom && ptMouse.y >= rcWindow.bottom - 5)
{
uRow = 2;
isResize = true;
}
//确认鼠标指针是否在left或者right
if (ptMouse.x >= rcWindow.left && ptMouse.x < rcWindow.left + 5)
{
uCol = 0; // left side
isResize = true;
}
else if (ptMouse.x < rcWindow.right && ptMouse.x >= rcWindow.right - 5)
{
uCol = 2; // right side
isResize = true;
}
//检测是不是在标题栏上,右边预留出了45*3 = 135的宽度,是留给关闭按钮、最大化、最小化的。
if (ptMouse.x >= rcWindow.left && ptMouse.x <= rcWindow.right - 135 && ptMouse.y > rcWindow.top + 3 && ptMouse.y <= rcWindow.top + 30)
{
*result = HTCAPTION;
return true;
}
LRESULT hitTests[3][3] =
{
{ HTTOPLEFT, fOnResizeBorder ? HTTOP : HTCAPTION, HTTOPRIGHT },
{ HTLEFT, HTNOWHERE, HTRIGHT },
{ HTBOTTOMLEFT, HTBOTTOM, HTBOTTOMRIGHT },
};
if (isResize == true)
{
*result = hitTests[uRow][uCol];
return true;
}
else
{
return false;
}
}
}
//这里一定要返回false,否则是屏蔽所有消息了
return false;
}
};
#endif // WINMSGFILTER_H
不要忘了在main函数中注册消息拦截的类
demo演示
demo代码
https://gitee.com/feipeng8848/frameless-window-qml/tree/master
内容总结
以上是互联网集市为您收集整理的qt无边框窗体 拦截windows消息实现全部内容,希望文章能够帮你解决qt无边框窗体 拦截windows消息实现所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。