QT无边框窗体

您所在的位置:网站首页 华为荣耀蓝牙接收文件在哪 QT无边框窗体

QT无边框窗体

#QT无边框窗体| 来源: 网络整理| 查看: 265

无边框其实就是去掉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.bottom - 5) { uRow = 2; isResize = true; } //确认鼠标指针是否在left或者right if (ptMouse.x >= rcWindow.left && ptMouse.x = rcWindow.right - 5) { uCol = 2; // right side isResize = true; } if (ptMouse.x >= rcWindow.left && ptMouse.x rcWindow.top + 3 && ptMouse.y 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.bottom - 5) { uRow = 2; isResize = true; } //确认鼠标指针是否在left或者right if (ptMouse.x >= rcWindow.left && ptMouse.x = rcWindow.right - 5) { uCol = 2; // right side isResize = true; } //检测是不是在标题栏上,右边预留出了45*3 = 135的宽度,是留给关闭按钮、最大化、最小化的。 if (ptMouse.x >= rcWindow.left && ptMouse.x rcWindow.top + 3 && ptMouse.y hwnd, nullptr, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER); RedrawWindow(msg->hwnd, nullptr, nullptr, RDW_INVALIDATE | RDW_UPDATENOW | RDW_NOCHILDREN); 即可解决多个显示器之间切换问题,但是程序还是会有其他问题。 这个方法我在VS调试诊断中看到,使用这个方法CPU性能前后都是在25左右。 感觉是歪门邪道,但是目前确实解决了我的问题,大家可以参考一下。 确实解决了,还可以精简下只设置SetWindowPos就行: RECT rcClient; GetWindowRect(msg->hwnd, &rcClient); SetWindowPos(msg->hwnd, NULL, rcClient.left, rcClient.top, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top, SWP_FRAMECHANGED);


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3