QT自绘标题和边框

news2025/7/20 22:45:06

在QT中如果想要自绘标题和边框,一般步骤是:

  1) 在创建窗口前设置Qt::FramelessWindowHint标志,设置该标志后会创建一个无标题、无边框的窗口。

  2)在客户区域的顶部创建一个自绘标题栏。

  3)给窗口绘制一个背景作为边框。

  4)如果想要鼠标拖动效果,可以在WM_NCHITTEST消息中返回HTCAPTION,具体方法百度这里不再详述。

  但是这样做会导致一个问题:

    在win7系统上,将窗口移动到屏幕边缘会自动排列(在屏幕顶部,左边,右边都会自动排列)的功能失效。

  如果你的窗口没有这个功能,只有两种可能:

  1)你的窗口不支持"移动到屏幕边缘自动排列"功能。

  2)你从系统关闭了此项功能(控制面板\轻松访问\轻松访问中心\使任务更容易被关注\防止将窗口移动到屏幕边缘时自动排列窗口)。

怎么样才能够既能够自绘标题和边框,又能够使用屏幕自动排列功能:

  有一个windows消息能够帮助我们,响应WM_NCCALCSIZE消息,直接返回true,就可以使客户区域的大小和窗口大小完全一样,这样就没有了标题栏和边框,我们可以按照上面的一般步骤来自绘标题栏和边框,唯一不同的是不需要设置Qt::FramelessWindowHint标志。

  这样做也会有问题:

    窗口显示的不完整,特别是在最大化的时候,非常明显。

  为什么会显示不完整,这个问题困扰我一整天。我新建了一个win32项目,响应WM_NCCALCSIZE消息,窗口显示完整,应该是QT自己处理的问题,最后不断调试QT源码,终于明白问题所在:

  调用堆栈(从下往上看):

QWindowsWindow::frameMarginsDp() 行 1854    C++
QWindowsWindow::frameMargins() 行 188    C++
QWidgetPrivate::updateFrameStrut() 行 11824    C++
QWidget::create(unsigned int window, bool initializeWindow, bool destroyOldWindow) 行 1358    C++

  关键函数:

QMargins QWindowsWindow::frameMarginsDp() const
{
    // Frames are invalidated by style changes (window state, flags).
    // As they are also required for geometry calculations in resize
    // event sequences, introduce a dirty flag mechanism to be able
    // to cache results.
    if (testFlag(FrameDirty)) {
        // Always skip calculating style-dependent margins for windows claimed to be frameless.
        // This allows users to remove the margins by handling WM_NCCALCSIZE with WS_THICKFRAME set
        // to ensure Areo snap still works (QTBUG-40578).
        m_data.frame = window()->flags() & Qt::FramelessWindowHint
            ? QMargins(0, 0, 0, 0)
            : QWindowsGeometryHint::frame(style(), exStyle());
        clearFlag(FrameDirty);
    }
    return m_data.frame + m_data.customMargins;
}

  注释里面清楚说明这是一个BUG(QTBUG-40578),我们虽然已经让客户区域大小和窗口大小完全一样,但是QT还是认为系统有边框,只有当设置了Qt::FramelessWindowHint标志,才会返回QMargins(0, 0, 0, 0)。

现在又回到了原点,且问题相互矛盾,想要自绘标题和边框必须设置Qt::FramelessWindowHint标志,但是设置Qt::FramelessWindowHint标志后"屏幕边缘自动排列"无效。

  首先要搞清楚Qt::FramelessWindowHint标志如何影响窗口,因为它直接导致"屏幕边缘自动排列"无效:

WindowCreationData::fromWindow(const QWindow * w, const QFlags<enum Qt::WindowType> flagsIn, unsigned int creationFlags) 行 519    C++
QWindowsWindowData::create(const QWindow * w, const QWindowsWindowData & parameters, const QString & title) 行 1075    C++
QWindowsIntegration::createWindowData(QWindow * window) 行 316    C++
QWindowsIntegration::createPlatformWindow(QWindow * window) 行 340    C++
QWindowPrivate::create(bool recursive) 行 392    C++
QWindow::create() 行 549    C++
QWidgetPrivate::create_sys(unsigned int window, bool initializeWindow, bool destroyOldWindow) 行 1456    C++
QWidget::create(unsigned int window, bool initializeWindow, bool destroyOldWindow) 行 1321    C++
QWidgetPrivate::createWinId(unsigned int winid) 行 2528    C++
void WindowCreationData::fromWindow(const QWindow *w, const Qt::WindowFlags flagsIn,
                                    unsigned creationFlags)
{
    if (popup || (type == Qt::ToolTip) || (type == Qt::SplashScreen)) {
        style = WS_POPUP;
    } else if (topLevel && !desktop) {
        if (flags & Qt::FramelessWindowHint)
            style = WS_POPUP;                // no border
        else if (flags & Qt::WindowTitleHint)
            style = WS_OVERLAPPED;
        else
            style = 0;
    } else {
        style = WS_CHILD;
    }

    if (!desktop) {
        if (topLevel) {
            if ((type == Qt::Window || dialog || tool)) {
                if (!(flags & Qt::FramelessWindowHint)) {
                    style |= WS_POPUP;
                    if (flags & Qt::MSWindowsFixedSizeDialogHint) {
                        style |= WS_DLGFRAME;
                    } else {
                        style |= WS_THICKFRAME;
                    }
                    if (flags & Qt::WindowTitleHint)
                        style |= WS_CAPTION; // Contains WS_DLGFRAME
                }
            } else {
                 exStyle |= WS_EX_TOOLWINDOW;
            }
        }
    }
}

  上面一个是调用堆栈(从下往上看),一个是关键函数(函数中不重要的内容已经删除)。从代码中可以看出,设置Qt::FramelessWindowHint标志会改变窗口样式,从而影响创建的窗口,现在基本已经知道,"屏幕边缘自动排列"功能与窗口样式有关。

  新建一个win32窗口程序,不断改变窗口的样式,最后得出结论:只有在窗口拥有WS_MAXIMIZEBOX | WS_THICKFRAME样式时,"屏幕边缘自动排列"功能才有效,最好还要添加WS_CAPTION样式,否则窗口最大化会覆盖任务栏。

原本以为完美结束了,但是不要高兴的太早,经过不断测试,还有几个问题:

  1)在任务栏点击窗口时,不能最小化:

    只要加上Qt::WindowMinimizeButtonHint标志即可解决该问题。

  2)如果有多个显示器,在辅屏上直接显示最大化,窗口显示不完整:

QWindowsWindow::show_sys() 行 1230    C++
QWindowsWindow::setVisible(bool visible) 行 1092    C++
QWindow::setVisible(bool visible) 行 518    C++
QWidgetPrivate::show_sys() 行 7897    C++
QWidgetPrivate::show_helper() 行 7826    C++
QWidget::setVisible(bool visible) 行 8110    C++
QWidget::showMaximized() 行 3154    C++
void QWindowsWindow::show_sys() const
{
    int sm = SW_SHOWNORMAL;
    bool fakedMaximize = false;
    const QWindow *w = window();
    const Qt::WindowFlags flags = w->flags();
    const Qt::WindowType type = w->type();
    if (w->isTopLevel()) {
        const Qt::WindowState state = w->windowState();
        if (state & Qt::WindowMinimized) {
            sm = SW_SHOWMINIMIZED;
            if (!isVisible())
                sm = SW_SHOWMINNOACTIVE;
        } else {
            updateTransientParent();
            if (state & Qt::WindowMaximized) {
                sm = SW_SHOWMAXIMIZED;
                // Windows will not behave correctly when we try to maximize a window which does not
                // have minimize nor maximize buttons in the window frame. Windows would then ignore
                // non-available geometry, and rather maximize the widget to the full screen, minus the
                // window frame (caption). So, we do a trick here, by adding a maximize button before
                // maximizing the widget, and then remove the maximize button afterwards.
                if (flags & Qt::WindowTitleHint &&
                        !(flags & (Qt::WindowMinMaxButtonsHint | Qt::FramelessWindowHint))) {
                    fakedMaximize = TRUE;
                    setStyle(style() | WS_MAXIMIZEBOX);
                }
            } // Qt::WindowMaximized
        } // !Qt::WindowMinimized
    }
    if (type == Qt::Popup || type == Qt::ToolTip || type == Qt::Tool || testShowWithoutActivating(w))
        sm = SW_SHOWNOACTIVATE;

    if (w->windowState() & Qt::WindowMaximized)
        setFlag(WithinMaximize); // QTBUG-8361

    ShowWindow(m_data.hwnd, sm);

    clearFlag(WithinMaximize);

    if (fakedMaximize) {
        setStyle(style() & ~WS_MAXIMIZEBOX);
        SetWindowPos(m_data.hwnd, 0, 0, 0, 0, 0,
                     SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER
                     | SWP_FRAMECHANGED);
    }
}

  还是老样子,上面一个是调用堆栈,一个是关键函数,我们可以看到最后QT调用了ShowWindow函数来显示最大化窗口,但是为什么会显示不完整呢?

  通常遇到一个复杂的问题,我会新建一个简单的项目来做实验。新建一个win32项目,最开始显示就让它最大化,结果显示正常,证明还是QT自己处理的问题,应该是在ShowWindow之后进行其他的处理,导致窗口显示不完整,最后发现是

处理WM_GETMINMAXINFO消息导致的,接下来我们看看QT如何处理WM_GETMINMAXINFO消息。

QWindowsWindow::getSizeHints(tagMINMAXINFO * mmi) 行 2044    C++
QWindowsContext::windowsProc 行 1015    C++
qWindowsWndProc(HWND__ * hwnd, unsigned int message, unsigned int wParam, long lParam) 行 1271    C++
void QWindowsWindow::getSizeHints(MINMAXINFO *mmi) const
{
    const QWindowsGeometryHint hint(window(), m_data.customMargins);
    hint.applyToMinMaxInfo(m_data.hwnd, mmi);

    if ((testFlag(WithinMaximize) || (window()->windowState() == Qt::WindowMinimized))
            && (m_data.flags & Qt::FramelessWindowHint)) {
        // This block fixes QTBUG-8361: Frameless windows shouldn't cover the
        // taskbar when maximized
        const QScreen *screen = window()->screen();

        // Documentation of MINMAXINFO states that it will only work for the primary screen
        if (screen && screen == QGuiApplication::primaryScreen()) {
            mmi->ptMaxSize.y = screen->availableGeometry().height();

            // Width, because you can have the taskbar on the sides too.
            mmi->ptMaxSize.x = screen->availableGeometry().width();

            // If you have the taskbar on top, or on the left you don't want it at (0,0):
            mmi->ptMaxPosition.x = screen->availableGeometry().x();
            mmi->ptMaxPosition.y = screen->availableGeometry().y();
        } else if (!screen){
            qWarning() << "window()->screen() returned a null screen";
        }
    }

    qCDebug(lcQpaWindows) << __FUNCTION__ << window() << *mmi;
}

  当程序在辅屏上时,它的screen是辅屏,如果当前screen不等于QGuiApplication::primaryScreen(主屏),则不设置MINMAXINFO结构,但是由于它已经处理了WM_GETMINMAXINFO消息,导致这个消息不会被系统默认的窗口处理函数处理(DefWindowProc),所以才会显示不完整,解决办法是优先响应WM_GETMINMAXINFO消息,让后交给系统默认的窗口处理函数进行处理。

  3)最大化后,窗口内容变小,最明显的就是最小化、最大化、关闭按钮变小了:

  窗口最大化时,系统会在屏幕上面显示所有的客户区域,此时系统会计算边框的大小,然后超出屏幕范围进行显示,例如边框的宽为8高为8,则系统会在(-8,-8,宽度+8,高度+8)的位置显示窗口,给人的感觉窗口的内容变小了,

  除去底部的任务栏,程序最大化可显示的最大宽度是1600*860,而窗口的实际位置是(-8,-8,1608,868)。这样我们可以添加一个QWidget作为主显示窗口,然后在程序最大化时,添加一个外边框,让它向内部缩一点。

最后的解决方案是:

  1. 在窗口的构造函数中添加以下代码,改变窗口的样式:

this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinimizeButtonHint);
// QMainWindow透明显示,当设置主显示窗口的外边距时,防止外边距显示出来。
this->setAttribute(Qt::WA_TranslucentBackground, true);

HWND hwnd = (HWND)this->winId();
DWORD style = ::GetWindowLong(hwnd, GWL_STYLE);
::SetWindowLong(hwnd, GWL_STYLE, style | WS_MAXIMIZEBOX | WS_THICKFRAME | WS_CAPTION);

  2. 重载nativeEvent函数,处理WM_NCHITTEST、WM_NCCALCSIZE和WM_GETMINMAXINFO消息

bool CustomWindow::nativeEvent(const QByteArray &eventType, void *message, long *result)
{
    MSG* msg = (MSG*)message;
    switch (msg->message) {

    case WM_NCHITTEST:
    {
        int xPos = GET_X_LPARAM(msg->lParam) - this->frameGeometry().x();
        int yPos = GET_Y_LPARAM(msg->lParam) - this->frameGeometry().y();
        if (m_title->isCaption(xPos, yPos)) {
            *result = HTCAPTION;
            return true;
        }
    }
        break;
case WM_NCCALCSIZE:
        return true;

    case WM_GETMINMAXINFO:
    {
        if (::IsZoomed(msg->hwnd)) {
            // 最大化时会超出屏幕,所以填充边框间距
            RECT frame = { 0, 0, 0, 0 };
            AdjustWindowRectEx(&frame, WS_OVERLAPPEDWINDOW, FALSE, 0);
            frame.left = abs(frame.left);
            frame.top = abs(frame.bottom);
             this->setContentsMargins(frame.left, frame.top, frame.right, frame.bottom);
        }
        else {
            this->setContentsMargins(2, 2, 2, 2);
        }

        *result = ::DefWindowProc(msg->hwnd, msg->message, msg->wParam, msg->lParam);
        return true;
    }
        break;
    }

    return QMainWindow::nativeEvent(eventType, message, result);
}

显示效果:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/368677.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Hadoop的生成经验调优和基准测试

文章目录&#xff08;1&#xff09;项目经验之HDFS存储多目录&#xff08;2&#xff09;项目经验之集群数据均衡&#xff08;3&#xff09;项目经验之Hadoop参数调优&#xff08;4&#xff09;项目经验之支持LZO压缩配置&#xff08;5&#xff09;项目经验之LZO创建索引&#x…

软考中级--嵌入式系统设计师考试培训教程开始了

1.考试时间: 1.1 上半年5月下旬考试 1.2 下半年11月上旬考试 2.考试内容 2.1 系统基础 满分75分 时间150分钟 2.2 系统设计 满分75分 时间150分钟 3.计划安排 3.1 熟悉考试大纲 3.2 按考纲学习相关内容 整理设计知识 快速学习形成知识印象 3.3 复习整理的知识 …

Hadoop3.3.1完全分布式部署

Hadoop目录Hadoop3.3.1完全分布式部署(一)1、HDFS一、安装1、基础安装1.1、配置JDK-181.2、下载并解压hadoop安装包本地运行模式测试 eg:2、完全分布式运行模式1、概要&#xff1a;2、编写集群分发脚本&#xff0c;把1~4步安装的同步到其他服务器&#xff1a;2.1、创建脚本vim …

Tailwind CSS 在Vue中的使用

什么是Tailwind CSS&#xff1f; Tailwind CSS 是一个功能类优先的 CSS 框架&#xff0c;它集成了诸如 flex, pt-4, text-center 和 rotate-90 这样的的类&#xff0c;支持 hover 和 focus 样式&#xff0c;它们能直接在脚本标记语言中组合起来&#xff0c;构建出任何设计。 …

【算法基础】 Trie树

一、Trie树Trie树用于高效存储和查找字符串集合的数据结构。二、Trie字符串统计维护一个字符串集合&#xff0c;支持两种操作&#xff1a;I x 向集合中插入一个字符串 x&#xff1b;Q x 询问一个字符串在集合中出现了多少次。共有 N&#xfffd;个操作&#xff0c;所有输入的字…

c#前端实现对pcl点云颜色根据强度特征动态变化突出指定对象

前言 本文主要介绍如何使用c# winform对点云颜色根据点云强度信息对显示的点云颜色进行动态调整。 目的是根据强度信息采用不同的颜色特征突出不同的物体。 一、点云强度是什么&#xff1f; 点云强度又可以叫做反射率&#xff0c; 通常常见的点云格式包括&#xff1a;以pcl为…

SpringBoot可以同时处理多少请求?

本文已收录至我的Github仓库DayDayUP&#xff1a;github.com/RobodLee/DayDayUP&#xff0c;欢迎Star ⭐⭐⭐⭐⭐转载请注明出处&#xff1a;https://blog.csdn.net/weixin_43461520/article/details/129207427 前言 前两天面试的时候&#xff0c;面试官问我&#xff1a;一个i…

【拿好了!Linux 运维必备的 13 款实用工具!】

​本文介绍几款 Linux 运维比较实用的工具&#xff0c;希望对 Linux 运维人员有所帮助。 查看进程占用带宽情况 – Nethogs Nethogs 是一个终端下的网络流量监控工具可以直观的显示每个进程占用的带宽。 下载&#xff1a; http://sourceforge.net/projects/nethogs/files/ne…

NPDP认证|产品研发过程中,产生冲突怎么办?

随着传统行业在研发过程中牵涉到很多利益相关方,这些相关方在产品研发过程中关注角度的不同,会产生各种各样的矛盾冲突&#xff0c;如研发与产品的冲突、运营与产品的冲突、客户与产品的冲突&#xff0c;那么产生冲突的原因是什么呢&#xff1f; 产生冲突的原因&#xff1f; 1…

Redis:实现全局唯一ID

Redis&#xff1a;实现全局唯一ID一. 概述二. 实现&#xff08;1&#xff09;获取初始时间戳&#xff08;2&#xff09;生成全局ID三. 测试为什么可以实现全局唯一&#xff1f;其他唯一ID策略补充&#xff1a;countDownLatch一. 概述 全局ID生成器&#xff1a;是一种在【分布式…

墨天轮发布数据库行业报告,创邻科技Galaxybase大放异彩

近日&#xff0c;知名数据库社区墨天轮发布《2022中国数据库行业年度分析报告》&#xff0c;该报告由墨天轮联合业界专家学者共同编写&#xff0c;共122页&#xff0c;详细总结了2022年数据库行业产学研用的发展近况、挑战以及对未来趋势的展望。旨在于给数据库行业带来有价值的…

Hadoop命令大全

HDFS分布式文件系统 &#xff0c; 将一个大的文件拆分成多个小文件存储在多台服务器中 文件系统&#xff1a; 目录结构&#xff08;树状结构&#xff09; "/" 树根&#xff0c; 目录结构在namenode中维护 目录 1.查看当前目录 2.创建多级目录 3.上传文件 4.查…

狂神说:面向对象(二)

一、创建与初始化对象new分配内存空间、默认初始化、构造器调用二、构造器特点&#xff1a;1. 没有返回值 2. 方法名与类名相同类里面啥都没写的时候&#xff0c;new一个这个类对象&#xff0c;java会生成一个默认构造函数&#xff08;构造器&#xff09;&#xff1a;构造器核心…

公司新招了个腾讯5年经验的测试员,让我见识到什么才是真正的测试天花板····

5年测试&#xff0c;应该是能达到资深测试的水准&#xff0c;即不仅能熟练地开发业务&#xff0c;而且还能熟悉项目开发&#xff0c;测试&#xff0c;调试和发布的流程&#xff0c;而且还应该能全面掌握数据库等方面的技能&#xff0c;如果技能再高些的话&#xff0c;甚至熟悉分…

Qt 进程间通信

Qt进程间通信的方法&#xff1a; TCP/IPLocal Server/Socket共享内存D-Bus &#xff08;Unix库&#xff09;QProcess会话管理 TCP/IP &#xff1a; 使用套接字的方式&#xff0c;进行通信&#xff08;之前介绍了&#xff0c;这里就不介绍了&#xff09;。 Local Server/Socket…

智能家居之主机--驱动层搭建

智能家居之主机--驱动层搭建bsp-底层驱动bsp_gpiobsp_adcbsp_uartbsp_timer伪调度bsp-底层驱动 bsp_gpio 利用一个config.h的配置文件&#xff0c;把所有要使用的gpio的属性配置好&#xff0c;这样有一个好处&#xff0c;比较集中&#xff0c;也比较好查&#xff0c;不需要一个…

fp32/fp64精度,4/8字节16进制转float/double十进制

1、IEEE-754 32位单精度浮点数&#xff08;4字节&#xff09; 1.1 32位单精度浮点数 其中&#xff0c; 32位16进制数包括1位符号位(SIGN)&#xff0c;8位指数位(EXPONENT)和 23位尾数位(MANTISSA)。 例如&#xff1a; 25.3可以表示为41CA6666&#xff08;0x41为高字节&#xf…

算法第十六期——动态规划(DP)之线性DP

【概述】 线性动态规划&#xff0c;是较常见的一类动态规划问题&#xff0c;其是在线性结构上进行状态转移&#xff0c;这类问题不像背包问题、区间DP等有固定的模板。 线性动态规划的目标函数为特定变量的线性函数&#xff0c;约束是这些变量的线性不等式或等式&#xff0c;目…

Mysql中关于查询日志的配置详解

查询日志 MySQL中的查询日志保存在文本文件中&#xff0c;能够记录MySQL中的所有数据操作。 开启查询日志 MySQL默认情况下没有开启查询日志&#xff0c;如果需要开启查询日志&#xff0c;则需要在 my.cnf 文件或者 my.ini 文件的 [mysqld] 选项下进行配置。例如&#xff0c;…

GoFrame工程目录设计介绍

GoFrame框架针对业务项目的目录设计&#xff0c;主体的思想来源于三层架构&#xff0c;但在具体实现中&#xff0c;对其进行了一定的改进和细化使其更符合工程实践和时代进步。 一.工程目录结构 GoFrame业务项目基本目录结构如下&#xff1a; 二.目录结构解释 对外接口 对…