Qt实现自定义窗口

​ 基于 QMainWindow 实现的效果很好的 Qt 无边框窗口,支持 Windows 和 OS X 系统。在 Windows 上,支持窗口阴影、Aero 效果等;在 OS X 上,支持原生窗口样式,比如窗口圆角、窗口阴影、三个系统按钮(关闭、最小化、最大化)等。

window效果

screenshot_win_1

OSX

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fmi8s2C4-1629970950506)(https://gitee.com/zlozl5566/drawing-bed-1/raw/master/20210826174006.gif)]

#ifndef CFRAMELESSWINDOW_H
#define CFRAMELESSWINDOW_H
#include "qsystemdetection.h"
#include <QObject>
#include <QMainWindow>

//A nice frameless window for both Windows and OS X
//Author: Bringer-of-Light
//Github: https://github.com/Bringer-of-Light/Qt-Nice-Frameless-Window
// Usage: use "CFramelessWindow" as base class instead of "QMainWindow", and enjoy
#ifdef Q_OS_WIN
#include <QWidget>
#include <QList>
#include <QMargins>
#include <QRect>
class CFramelessWindow : public QMainWindow
{
    Q_OBJECT
public:
    explicit CFramelessWindow(QWidget *parent = 0);
public:

    //设置是否可以通过鼠标调整窗口大小
    //if resizeable is set to false, then the window can not be resized by mouse
    //but still can be resized programtically
    void setResizeable(bool resizeable=true);
    bool isResizeable(){return m_bResizeable;}

    //设置可调整大小区域的宽度,在此区域内,可以使用鼠标调整窗口大小
    //set border width, inside this aera, window can be resized by mouse
    void setResizeableAreaWidth(int width = 5);
protected:
    //设置一个标题栏widget,此widget会被当做标题栏对待
    //set a widget which will be treat as SYSTEM titlebar
    void setTitleBar(QWidget* titlebar);

    //在标题栏控件内,也可以有子控件如标签控件“label1”,此label1遮盖了标题栏,导致不能通过label1拖动窗口
    //要解决此问题,使用addIgnoreWidget(label1)
    //generally, we can add widget say "label1" on titlebar, and it will cover the titlebar under it
    //as a result, we can not drag and move the MainWindow with this "label1" again
    //we can fix this by add "label1" to a ignorelist, just call addIgnoreWidget(label1)
    void addIgnoreWidget(QWidget* widget);

    bool nativeEvent(const QByteArray &eventType, void *message, long *result);
private slots:
    void onTitleBarDestroyed();
public:
    void setContentsMargins(const QMargins &margins);
    void setContentsMargins(int left, int top, int right, int bottom);
    QMargins contentsMargins() const;
    QRect contentsRect() const;
    void getContentsMargins(int *left, int *top, int *right, int *bottom) const;
public slots:
    void showFullScreen();
private:
    QWidget* m_titlebar;
    QList<QWidget*> m_whiteList;
    int m_borderWidth;

    QMargins m_margins;
    QMargins m_frames;
    bool m_bJustMaximized;

    bool m_bResizeable;
};

#elif defined Q_OS_MAC
#include <QMouseEvent>
#include <QResizeEvent>
#include <QPoint>
class CFramelessWindow : public QMainWindow
{
    Q_OBJECT
public:
    explicit CFramelessWindow(QWidget *parent = 0);
private:
    void initUI();
public:
    //设置可拖动区域的高度,在此区域内,可以通过鼠标拖动窗口, 0表示整个窗口都可拖动
    //In draggable area, window can be moved by mouse, (height = 0) means that the whole window is draggable
    void setDraggableAreaHeight(int height = 0);

    //只有OS X10.10及以后系统,才支持OS X原生样式包括:三个系统按钮、窗口圆角、窗口阴影
    //类初始化完成后,可以通过此函数查看是否已经启用了原生样式。如果未启动,需要自定义关闭按钮、最小化按钮、最大化按钮
    //Native style(three system button/ round corner/ drop shadow) works only on OS X 10.10 or later
    //after init, we should check whether NativeStyle is OK with this function
    //if NOT ok, we should implement close button/ min button/ max button ourself
    bool isNativeStyleOK() {return m_bNativeSystemBtn;}

    //如果设置setCloseBtnQuit(false),那么点击关闭按钮后,程序不会退出,而是会隐藏,只有在OS X 10.10 及以后系统中有效
    //if setCloseBtnQuit(false), then when close button is clicked, the application will hide itself instead of quit
    //be carefull, after you set this to false, you can NOT change it to true again
    //this function should be called inside of the constructor function of derived classes, and can NOT be called more than once
    //only works for OS X 10.10 or later
    void setCloseBtnQuit(bool bQuit = true);

    //启用或禁用关闭按钮,只有在isNativeStyleOK()返回true的情况下才有效
    //enable or disable Close button, only worked if isNativeStyleOK() returns true
    void setCloseBtnEnabled(bool bEnable = true);

    //启用或禁用最小化按钮,只有在isNativeStyleOK()返回true的情况下才有效
    //enable or disable Miniaturize button, only worked if isNativeStyleOK() returns true
    void setMinBtnEnabled(bool bEnable = true);

    //启用或禁用zoom(最大化)按钮,只有在isNativeStyleOK()返回true的情况下才有效
    //enable or disable Zoom button(fullscreen button), only worked if isNativeStyleOK() returns true
    void setZoomBtnEnabled(bool bEnable = true);

    bool isCloseBtnEnabled() {return m_bIsCloseBtnEnabled;}
    bool isMinBtnEnabled() {return m_bIsMinBtnEnabled;}
    bool isZoomBtnEnabled() {return m_bIsZoomBtnEnabled;}
protected:
    void mousePressEvent(QMouseEvent *event);
    void mouseReleaseEvent(QMouseEvent *event);
    void mouseMoveEvent(QMouseEvent *event);
private:
    int m_draggableHeight;
    bool    m_bWinMoving;
    bool    m_bMousePressed;
    QPoint  m_MousePos;
    QPoint  m_WindowPos;
    bool m_bCloseBtnQuit;
    bool m_bNativeSystemBtn;
    bool m_bIsCloseBtnEnabled, m_bIsMinBtnEnabled, m_bIsZoomBtnEnabled;

    //===============================================
    //TODO
    //下面的代码是试验性质的
    //tentative code

    //窗口从全屏状态恢复正常大小时,标题栏又会出现,原因未知。
    //默认情况下,系统的最大化按钮(zoom button)是进入全屏,为了避免标题栏重新出现的问题,
    //以上代码已经重新定义了系统zoom button的行为,是其功能变为最大化而不是全屏
    //以下代码尝试,每次窗口从全屏状态恢复正常大小时,都再次进行设置,以消除标题栏
    //after the window restore from fullscreen mode, the titlebar will show again, it looks like a BUG
    //on OS X 10.10 and later, click the system green button (zoom button) will make the app become fullscreen
    //so we have override it's action to "maximized" in the CFramelessWindow Constructor function
    //but we may try something else such as delete the titlebar again and again...
private:
    bool m_bTitleBarVisible;

    void setTitlebarVisible(bool bTitlebarVisible = false);
    bool isTitlebarVisible() {return m_bTitleBarVisible;}
private slots:
    void onRestoreFromFullScreen();
signals:
    void restoreFromFullScreen();
protected:
    void resizeEvent(QResizeEvent *event);
};
#endif

#endif // CFRAMELESSWINDOW_H

#include "framelesswindow.h"
#include <QApplication>
#include <QPoint>
#include <QSize>
#ifdef Q_OS_WIN

#include <windows.h>
#include <WinUser.h>
#include <windowsx.h>
#include <dwmapi.h>
#include <objidl.h> // Fixes error C2504: 'IUnknown' : base class undefined
#include <gdiplus.h>
#include <GdiPlusColor.h>
#pragma comment (lib,"Dwmapi.lib") // Adds missing library, fixes error LNK2019: unresolved external symbol __imp__DwmExtendFrameIntoClientArea
#pragma comment (lib,"user32.lib")

CFramelessWindow::CFramelessWindow(QWidget *parent)
    : QMainWindow(parent),
      m_titlebar(Q_NULLPTR),
      m_borderWidth(5),
      m_bJustMaximized(false),
      m_bResizeable(true)
{
//    setWindowFlag(Qt::Window,true);
//    setWindowFlag(Qt::FramelessWindowHint, true);
//    setWindowFlag(Qt::WindowSystemMenuHint, true);
//    setWindowFlag() is not avaliable before Qt v5.9, so we should use setWindowFlags instead

    setWindowFlags(windowFlags() | Qt::Window | Qt::FramelessWindowHint | Qt::WindowSystemMenuHint);

    setResizeable(m_bResizeable);
}

void CFramelessWindow::setResizeable(bool resizeable)
{
    bool visible = isVisible();
    m_bResizeable = resizeable;
    if (m_bResizeable){
        setWindowFlags(windowFlags() | Qt::WindowMaximizeButtonHint);
//        setWindowFlag(Qt::WindowMaximizeButtonHint);

        //此行代码可以带回Aero效果,同时也带回了标题栏和边框,在nativeEvent()会再次去掉标题栏
        //
        //this line will get titlebar/thick frame/Aero back, which is exactly what we want
        //we will get rid of titlebar and thick frame again in nativeEvent() later
        HWND hwnd = (HWND)this->winId();
        DWORD style = ::GetWindowLong(hwnd, GWL_STYLE);
        ::SetWindowLong(hwnd, GWL_STYLE, style | WS_MAXIMIZEBOX | WS_THICKFRAME | WS_CAPTION);
    }else{
        setWindowFlags(windowFlags() & ~Qt::WindowMaximizeButtonHint);
//        setWindowFlag(Qt::WindowMaximizeButtonHint,false);

        HWND hwnd = (HWND)this->winId();
        DWORD style = ::GetWindowLong(hwnd, GWL_STYLE);
        ::SetWindowLong(hwnd, GWL_STYLE, style & ~WS_MAXIMIZEBOX & ~WS_CAPTION);
    }

    //保留一个像素的边框宽度,否则系统不会绘制边框阴影
    //
    //we better left 1 piexl width of border untouch, so OS can draw nice shadow around it
    const MARGINS shadow = { 1, 1, 1, 1 };
    DwmExtendFrameIntoClientArea(HWND(winId()), &shadow);

    setVisible(visible);
}

void CFramelessWindow::setResizeableAreaWidth(int width)
{
    if (1 > width) width = 1;
    m_borderWidth = width;
}

void CFramelessWindow::setTitleBar(QWidget* titlebar)
{
    m_titlebar = titlebar;
    if (!titlebar) return;
    connect(titlebar, SIGNAL(destroyed(QObject*)), this, SLOT(onTitleBarDestroyed()));
}

void CFramelessWindow::onTitleBarDestroyed()
{
    if (m_titlebar == QObject::sender())
    {
        m_titlebar = Q_NULLPTR;
    }
}

void CFramelessWindow::addIgnoreWidget(QWidget* widget)
{
    if (!widget) return;
    if (m_whiteList.contains(widget)) return;
    m_whiteList.append(widget);
}

bool CFramelessWindow::nativeEvent(const QByteArray &eventType, void *message, long *result)
{
    //Workaround for known bug -> check Qt forum : https://forum.qt.io/topic/93141/qtablewidget-itemselectionchanged/13
    #if (QT_VERSION == QT_VERSION_CHECK(5, 11, 1))
    MSG* msg = *reinterpret_cast<MSG**>(message);
    #else
    //MSG* msg = reinterpret_cast<MSG*>(message);
    const auto msg = static_cast<LPMSG>(message);
    #endif

    if (!msg->hwnd) {
        // Why sometimes the window handle is null? Is it designed to be?
        // Anyway, we should skip it in this case.
        return false;
    }
    
    
    switch (msg->message)
    {
    case WM_NCCALCSIZE:
    {
     // Windows是根据这个消息的返回值来设置窗口的客户区(窗口中真正显示的内容)
        // 和非客户区(标题栏、窗口边框、菜单栏和状态栏等Windows系统自行提供的部分
        // ,不过对于Qt来说,除了标题栏和窗口边框,非客户区基本也都是自绘的)的范
        // 围的,lParam里存放的就是新客户区的几何区域,默认是整个窗口的大小,正常
        // 的程序需要修改这个参数,告知系统窗口的客户区和非客户区的范围(一般来说可
        // 以完全交给Windows,让其自行处理,使用默认的客户区和非客户区),因此如果
        // 我们不修改lParam,就可以使客户区充满整个窗口,从而去掉标题栏和窗口边框
        // (因为这些东西都被客户区给盖住了。但边框阴影也会因此而丢失,不过我们会使
        // 用其他方式将其带回,请参考其他消息的处理,此处不过多提及)。但有个情况要
        // 特别注意,那就是窗口最大化后,窗口的实际尺寸会比屏幕的尺寸大一点,从而使
        // 用户看不到窗口的边界,这样用户就不能在窗口最大化后调整窗口的大小了(虽然
        // 这个做法听起来特别奇怪,但Windows确实就是这样做的),因此如果我们要自行
        // 处理窗口的非客户区,就要在窗口最大化后,将窗口边框的宽度和高度(一般是相
        // 等的)从客户区裁剪掉,否则我们窗口所显示的内容就会超出屏幕边界,显示不全。
        // 如果用户开启了任务栏自动隐藏,在窗口最大化后,还要考虑任务栏的位置。因为
        // 如果窗口最大化后,其尺寸和屏幕尺寸相等(因为任务栏隐藏了,所以窗口最大化
        // 后其实是充满了整个屏幕,变相的全屏了),Windows会认为窗口已经进入全屏的
        // 状态,从而导致自动隐藏的任务栏无法弹出。要避免这个状况,就要使窗口的尺寸
        // 小于屏幕尺寸。我下面的做法参考了火狐、Chromium和Windows Terminal
        // 如果没有开启任务栏自动隐藏,是不存在这个问题的,所以要先进行判断。
        // 一般情况下,*result设置为0(相当于DefWindowProc的返回值为0)就可以了,
        // 根据MSDN的说法,返回0意为此消息已经被程序自行处理了,让Windows跳过此消
        // 息,否则Windows会添加对此消息的默认处理,对于当前这个消息而言,就意味着
        // 标题栏和窗口边框又会回来,这当然不是我们想要的结果。根据MSDN,当wParam
        // 为FALSE时,只能返回0,但当其为TRUE时,可以返回0,也可以返回一个WVR_常
        // 量。根据Chromium的注释,当存在非客户区时,如果返回WVR_REDRAW会导致子
        // 窗口/子控件出现奇怪的bug(自绘控件错位),并且Lucas在Windows 10
        // 上成功复现,说明这个bug至今都没有解决。我查阅了大量资料,发现唯一的解决
        // 方案就是返回0。但如果不存在非客户区,且wParam为TRUE,最好返回
        // WVR_REDRAW,否则窗口在调整大小可能会产生严重的闪烁现象。
        // 虽然对大多数消息来说,返回0都代表让Windows忽略此消息,但实际上不同消息
        // 能接受的返回值是不一样的,请注意自行查阅MSDN。

        // Sent when the size and position of a window's client area must be
        // calculated. By processing this message, an application can
        // control the content of the window's client area when the size or
        // position of the window changes. If wParam is TRUE, lParam points
        // to an NCCALCSIZE_PARAMS structure that contains information an
        // application can use to calculate the new size and position of the
        // client rectangle. If wParam is FALSE, lParam points to a RECT
        // structure. On entry, the structure contains the proposed window
        // rectangle for the window. On exit, the structure should contain
        // the screen coordinates of the corresponding window client area.
        // The client area is the window's content area, the non-client area
        // is the area which is provided by the system, such as the title
        // bar, the four window borders, the frame shadow, the menu bar, the
        // status bar, the scroll bar, etc. But for Qt, it draws most of the
        // window area (client + non-client) itself. We now know that the
        // title bar and the window frame is in the non-client area and we
        // can set the scope of the client area in this message, so we can
        // remove the title bar and the window frame by let the non-client
        // area be covered by the client area (because we can't really get
        // rid of the non-client area, it will always be there, all we can
        // do is to hide it) , which means we should let the client area's
        // size the same with the whole window's size. So there is no room
        // for the non-client area and then the user won't be able to see it
        // again. But how to achieve this? Very easy, just leave lParam (the
        // re-calculated client area) untouched. But of course you can
        // modify lParam, then the non-client area will be seen and the
        // window borders and the window frame will show up. However, things
        // are quite different when you try to modify the top margin of the
        // client area. DWM will always draw the whole title bar no matter
        // what margin value you set for the top, unless you don't modify it
        // and remove the whole top area (the title bar + the one pixel
        // height window border). This can be confirmed in Windows
        // Terminal's source code, you can also try yourself to verify
        // it. So things will become quite complicated if you want to
        // preserve the four window borders. So we just remove the whole
        // window frame, otherwise the code will become much more complex.

        if (msg->wParam == FALSE) {
            *result = 0;
            return true;
        }

        if(::IsZoomed(msg->hwnd)) {
            *result = WVR_REDRAW;
            return true;
        } else {
            *result = 0;
            return true;
        }
    //    NCCALCSIZE_PARAMS& params = *reinterpret_cast<NCCALCSIZE_PARAMS*>(msg->lParam);
 	//if (params.rgrc[0].top != 0)
	//	params.rgrc[0].top -= 1;

    //    //this kills the window frame and title bar we added with WS_THICKFRAME and WS_CAPTION
    //    *result = WVR_REDRAW;
    //    return true;
    }
    case WM_NCHITTEST:
    {
     // 原生Win32窗口只有顶边是在窗口内部resize的,其余三边都是在窗口
        // 外部进行resize的,其原理是,WS_THICKFRAME这个窗口样式会在窗
        // 口的左、右和底边添加三个透明的resize区域,这三个区域在正常状态
        // 下是完全不可见的,它们由DWM负责绘制和控制。这些区域的宽度等于
        // (SM_CXSIZEFRAME + SM_CXPADDEDBORDER),高度等于
        // (SM_CYSIZEFRAME + SM_CXPADDEDBORDER),在100%缩放时,均等
        // 于8像素。它们属于窗口区域的一部分,但不属于客户区,而是属于非客
        // 户区,因此GetWindowRect获取的区域中是包含这三个resize区域的,
        // 而GetClientRect获取的区域是不包含它们的。当把
        // DWMWA_EXTENDED_FRAME_BOUNDS作为参数调用
        // DwmGetWindowAttribute时,也能获取到一个窗口大小,这个大小介
        // 于前面两者之间,暂时不知道这个数据的意义及其作用。我们在
        // WM_NCCALCSIZE消息的处理中,已经把整个窗口都设置为客户区了,也
        // 就是说,我们的窗口已经没有非客户区了,因此那三个透明的resize区
        // 域,此刻也已经成为窗口客户区的一部分了,从而变得不透明了。所以
        // 现在的resize,看起来像是在窗口内部resize,是因为原本透明的地方
        // 现在变得不透明了,实际上,单纯从范围上来看,现在我们resize的地方,
        // 就是普通窗口的边框外部,那三个透明区域的范围。
        // 因此,如果我们把边框完全去掉(就是我们正在做的事情),resize就
        // 会看起来是在内部进行,这个问题通过常规方法非常难以解决。我测试过
        // QQ和钉钉的窗口,它们的窗口就是在外部resize,但实际上它们是通过
        // 把窗口实际的内容,嵌入到一个完全透明的但尺寸要大一圈的窗口中实现
        // 的,虽然看起来效果还行,但在我看来不是正途。而且我之所以能发现,
        // 也是由于这种方法在很多情况下会露馅,比如窗口未响应卡住或贴边的时
        // 候,能明显看到窗口周围多出来一圈边界。我曾经尝试再把那三个区域弄
        // 透明,但无一例外都会破坏DWM绘制的边框阴影,因此只好作罢。

        // As you may have found, if you use this code, the resize areas
        // will be inside the frameless window, however, a normal Win32
        // window can be resized outside of it. Here is the reason: the
        // WS_THICKFRAME window style will cause a window has three
        // transparent areas beside the window's left, right and bottom
        // edge. Their width or height is eight pixels if the window is not
        // scaled. In most cases, they are totally invisible. It's DWM's
        // responsibility to draw and control them. They exist to let the
        // user resize the window, visually outside of it. They are in the
        // window area, but not the client area, so they are in the
        // non-client area actually. But we have turned the whole window
        // area into client area in WM_NCCALCSIZE, so the three transparent
        // resize areas also become a part of the client area and thus they
        // become visible. When we resize the window, it looks like we are
        // resizing inside of it, however, that's because the transparent
        // resize areas are visible now, we ARE resizing outside of the
        // window actually. But I don't know how to make them become
        // transparent again without breaking the frame shadow drawn by DWM.
        // If you really want to solve it, you can try to embed your window
        // into a larger transparent window and draw the frame shadow
        // yourself. As what we have said in WM_NCCALCSIZE, you can only
        // remove the top area of the window, this will let us be able to
        // resize outside of the window and don't need much process in this
        // message, it looks like a perfect plan, however, the top border is
        // missing due to the whole top area is removed, and it's very hard
        // to bring it back because we have to use a trick in WM_PAINT
        // (learned from Windows Terminal), but no matter what we do in
        // WM_PAINT, it will always break the backing store mechanism of Qt,
        // so actually we can't do it. And it's very difficult to do such
        // things in NativeEventFilters as well. What's worse, if we really
        // do this, the four window borders will become white and they look
        // horrible in dark mode. This solution only supports Windows 10
        // because the border width on Win10 is only one pixel, however it's
        // eight pixels on Windows 7 so preserving the three window borders
        // looks terrible on old systems. I'm testing this solution in
        // another branch, if you are interested in it, you can give it a
        // try.

        *result = 0;

        const LONG border_width = m_borderWidth;
        RECT winrect;
        GetWindowRect(HWND(winId()), &winrect);

        long x = GET_X_LPARAM(msg->lParam);
        long y = GET_Y_LPARAM(msg->lParam);

        if(m_bResizeable)
        {

            bool resizeWidth = minimumWidth() != maximumWidth();
            bool resizeHeight = minimumHeight() != maximumHeight();

            if(resizeWidth)
            {
                //left border
                if (x >= winrect.left && x < winrect.left + border_width)
                {
                    *result = HTLEFT;
                }
                //right border
                if (x < winrect.right && x >= winrect.right - border_width)
                {
                    *result = HTRIGHT;
                }
            }
            if(resizeHeight)
            {
                //bottom border
                if (y < winrect.bottom && y >= winrect.bottom - border_width)
                {
                    *result = HTBOTTOM;
                }
                //top border
                if (y >= winrect.top && y < winrect.top + border_width)
                {
                    *result = HTTOP;
                }
            }
            if(resizeWidth && resizeHeight)
            {
                //bottom left corner
                if (x >= winrect.left && x < winrect.left + border_width &&
                        y < winrect.bottom && y >= winrect.bottom - border_width)
                {
                    *result = HTBOTTOMLEFT;
                }
                //bottom right corner
                if (x < winrect.right && x >= winrect.right - border_width &&
                        y < winrect.bottom && y >= winrect.bottom - border_width)
                {
                    *result = HTBOTTOMRIGHT;
                }
                //top left corner
                if (x >= winrect.left && x < winrect.left + border_width &&
                        y >= winrect.top && y < winrect.top + border_width)
                {
                    *result = HTTOPLEFT;
                }
                //top right corner
                if (x < winrect.right && x >= winrect.right - border_width &&
                        y >= winrect.top && y < winrect.top + border_width)
                {
                    *result = HTTOPRIGHT;
                }
            }
        }
        if (0!=*result) return true;
        //*result仍然等于0,说明鼠标位置不在上述的边框范围内,则还有可能处于标题栏范围内
        //标题栏效果(win7 及以上):
        //1. 双击标题栏,窗口在最大化、正常化之间切换
        //2. 拖动标题栏时,可以移动窗口,移动时如果鼠标触碰到桌面顶端,则最大化窗口等类似效果
        
        //*result still equals 0, that means the cursor locate OUTSIDE the frame area
        //but it may locate in titlebar area
        if (!m_titlebar) return false;

        //support highdpi
        double dpr = this->devicePixelRatioF();
        QPoint pos = m_titlebar->mapFromGlobal(QPoint(x/dpr,y/dpr));

        if (!m_titlebar->rect().contains(pos)) return false;
        QWidget* child = m_titlebar->childAt(pos);
        if (!child)
        {
            *result = HTCAPTION;
            return true;
        }else{
            if (m_whiteList.contains(child))
            {
                *result = HTCAPTION;
                return true;
            }
        }
        return false;
    } //end case WM_NCHITTEST
    case WM_GETMINMAXINFO:
    {
        if (::IsZoomed(msg->hwnd)) {
            RECT frame = { 0, 0, 0, 0 };
            AdjustWindowRectEx(&frame, WS_OVERLAPPEDWINDOW, FALSE, 0);

            //record frame area data
            double dpr = this->devicePixelRatioF();

            m_frames.setLeft(abs(frame.left)/dpr+0.5);
            m_frames.setTop(abs(frame.bottom)/dpr+0.5);
            m_frames.setRight(abs(frame.right)/dpr+0.5);
            m_frames.setBottom(abs(frame.bottom)/dpr+0.5);

            QMainWindow::setContentsMargins(m_frames.left()+m_margins.left(), \
                                            m_frames.top()+m_margins.top(), \
                                            m_frames.right()+m_margins.right(), \
                                            m_frames.bottom()+m_margins.bottom());
            m_bJustMaximized = true;
        }else {
            if (m_bJustMaximized)
            {
                QMainWindow::setContentsMargins(m_margins);
                m_frames = QMargins();
                m_bJustMaximized = false;
            }
        }
        return false;
    }
    default:
        return QMainWindow::nativeEvent(eventType, message, result);
    }
}

void CFramelessWindow::setContentsMargins(const QMargins &margins)
{
    QMainWindow::setContentsMargins(margins+m_frames);
    m_margins = margins;
}
void CFramelessWindow::setContentsMargins(int left, int top, int right, int bottom)
{
    QMainWindow::setContentsMargins(left+m_frames.left(),\
                                    top+m_frames.top(), \
                                    right+m_frames.right(), \
                                    bottom+m_frames.bottom());
    m_margins.setLeft(left);
    m_margins.setTop(top);
    m_margins.setRight(right);
    m_margins.setBottom(bottom);
}
QMargins CFramelessWindow::contentsMargins() const
{
    QMargins margins = QMainWindow::contentsMargins();
    margins -= m_frames;
    return margins;
}
void CFramelessWindow::getContentsMargins(int *left, int *top, int *right, int *bottom) const
{
    QMainWindow::getContentsMargins(left,top,right,bottom);
    if (!(left&&top&&right&&bottom)) return;
    if (isMaximized())
    {
        *left -= m_frames.left();
        *top -= m_frames.top();
        *right -= m_frames.right();
        *bottom -= m_frames.bottom();
    }
}
QRect CFramelessWindow::contentsRect() const
{
    QRect rect = QMainWindow::contentsRect();
    int width = rect.width();
    int height = rect.height();
    rect.setLeft(rect.left() - m_frames.left());
    rect.setTop(rect.top() - m_frames.top());
    rect.setWidth(width);
    rect.setHeight(height);
    return rect;
}
void CFramelessWindow::showFullScreen()
{
    if (isMaximized())
    {
        QMainWindow::setContentsMargins(m_margins);
        m_frames = QMargins();
    }
    QMainWindow::showFullScreen();
}

#endif //Q_OS_WIN

使用方法,就是QWidget 继承 CFramelessWindow类

class MainWindow : public CFramelessWindow
{

}

完整代码:

demo下载地址:
https://download.csdn.net/download/ZLOZL/21569440?spm=1001.2014.3001.5503

QtCustomWindow/src at master · liamlife/QtCustomWindow (github.com)

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐