• C++ Qt QMainWindow实现无边框窗口自定义标题栏可拖拽移动拉伸改变窗口大小


    本篇博客介绍C++ Qt QMainWindow实现无边框窗口,适用于win10/win11系统。

    QMainWindow相对于QWidget多了dockedwidget功能,跟多人可能更喜欢用QMainWindow做主窗口,如果不需要dockedwidget功能,QMainWindow与QWidget做主窗口基本无差别。

    效果图如下:
    在这里插入图片描述
    自带窗口阴影、圆角、可拉伸,拖拽。

    具体实现过程如下:

    一、编写无边框窗口基类CFramelessWindowBase

    CFramelessWindowBase.h

    /*
    
    QMainWindow无边框窗口基类
    
    可拉伸
    
    其它QMainWindow窗口派生于该类即可
    
    */
    
    #pragma once
    #include 
    
    class CFramelessWindowBase : public QMainWindow
    {
    public:
    	CFramelessWindowBase(QWidget* parent = nullptr);
    	~CFramelessWindowBase();
    
    protected:
    	bool nativeEvent(const QByteArray& eventType, void* message, long* result) override;
    
    private:
    	int mouse_margin = 5;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    CFramelessWindowBase.cpp

    #include "CFramelessWindowBase.h"
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #pragma comment(lib, "dwmapi.lib")
    
    
    CFramelessWindowBase::CFramelessWindowBase(QWidget* parent)
    	: QMainWindow(parent)
    {
    	setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint);
    	setAttribute(Qt::WA_Hover);
    
    	// 添加窗口阴影,窗口圆角
    	HWND hWnd = reinterpret_cast<HWND>(winId());
    	DWMNCRENDERINGPOLICY ncrp = DWMNCRP_ENABLED;
    	::DwmSetWindowAttribute(hWnd, DWMWA_NCRENDERING_POLICY, &ncrp, sizeof(ncrp));
    	MARGINS shadow = { 1, 1, 1, 1 };
    	DwmExtendFrameIntoClientArea((HWND)winId(), &shadow);
    }
    
    CFramelessWindowBase::~CFramelessWindowBase()
    {
    }
    
    bool CFramelessWindowBase::nativeEvent(const QByteArray& eventType, void* message, long* result)
    {
    	MSG* msg = static_cast<MSG*>(message);
    
    	switch (msg->message)
    	{
    	case WM_NCHITTEST:
    	{
    		QPoint globalPos = QCursor::pos();
    		int x = globalPos.x();
    		int y = globalPos.y();
    
    		//int nX = GET_X_LPARAM(param->lParam) - this->geometry().x();   // bug : windows 在高分屏下,坐标值不正确
    		//int nY = GET_Y_LPARAM(param->lParam) - this->geometry().y();
    
    		int nX = x - this->geometry().x();
    		int nY = y - this->geometry().y();
    
    		// 如果鼠标位于内部子控件上,则不进行处理
    		if (nX > mouse_margin && nX < width() - mouse_margin &&
    			nY > mouse_margin && nY < this->height() - mouse_margin)
    		{
    			if (childAt(nX, nY) != nullptr)
    				return QWidget::nativeEvent(eventType, message, result);
    		}
    
    		// 鼠标区域位于窗体边框,进行缩放
    		if ((nX > 0) && (nX < mouse_margin))
    			*result = HTLEFT;
    
    		if ((nX > this->width() - mouse_margin) && (nX < this->width()))
    			*result = HTRIGHT;
    
    		if ((nY > 0) && (nY < mouse_margin))
    			*result = HTTOP;
    
    		if ((nY > this->height() - mouse_margin) && (nY < this->height()))
    			*result = HTBOTTOM;
    
    		if ((nX > 0) && (nX < mouse_margin) && (nY > 0)
    			&& (nY < mouse_margin))
    			*result = HTTOPLEFT;
    
    		if ((nX > this->width() - mouse_margin) && (nX < this->width())
    			&& (nY > 0) && (nY < mouse_margin))
    			*result = HTTOPRIGHT;
    
    		if ((nX > 0) && (nX < mouse_margin)
    			&& (nY > this->height() - mouse_margin) && (nY < this->height()))
    			*result = HTBOTTOMLEFT;
    
    		if ((nX > this->width() - mouse_margin) && (nX < this->width())
    			&& (nY > this->height() - mouse_margin) && (nY < this->height()))
    			*result = HTBOTTOMRIGHT;
    
    		return true;
    	}
    	}
    
    	return QWidget::nativeEvent(eventType, message, result);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89

    代码解释:
    (1)在CFramelessWindowBase类设置窗口标志,去掉窗口边框,设置最大最小显示效果。

    setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint);
    
    • 1

    (2)增加windows窗口阴影与圆角:

    // 添加窗口阴影,窗口圆角
    HWND hWnd = reinterpret_cast<HWND>(winId());
    DWMNCRENDERINGPOLICY ncrp = DWMNCRP_ENABLED;
    ::DwmSetWindowAttribute(hWnd, DWMWA_NCRENDERING_POLICY, &ncrp, sizeof(ncrp));
    MARGINS shadow = { 1, 1, 1, 1 };
    DwmExtendFrameIntoClientArea((HWND)winId(), &shadow);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这里使用的是DWM API实现窗口阴影和圆角,圆角是windows窗口的圆角,不需要手动设置圆角大小。
    (3)重写nativeEvent实现无边框窗口

    bool CFramelessWindowBase::nativeEvent(const QByteArray& eventType, void* message, long* result)
    {
    	MSG* msg = static_cast<MSG*>(message);
    
    	switch (msg->message)
    	{
    	case WM_NCHITTEST:
    	{
    		QPoint globalPos = QCursor::pos();
    		int x = globalPos.x();
    		int y = globalPos.y();
    
    		//int nX = GET_X_LPARAM(param->lParam) - this->geometry().x();   // bug : windows 在高分屏下,坐标值不正确
    		//int nY = GET_Y_LPARAM(param->lParam) - this->geometry().y();
    
    		int nX = x - this->geometry().x();
    		int nY = y - this->geometry().y();
    
    		// 如果鼠标位于内部子控件上,则不进行处理
    		if (nX > mouse_margin && nX < width() - mouse_margin &&
    			nY > mouse_margin && nY < this->height() - mouse_margin)
    		{
    			if (childAt(nX, nY) != nullptr)
    				return QWidget::nativeEvent(eventType, message, result);
    		}
    
    		// 鼠标区域位于窗体边框,进行缩放
    		if ((nX > 0) && (nX < mouse_margin))
    			*result = HTLEFT;
    
    		if ((nX > this->width() - mouse_margin) && (nX < this->width()))
    			*result = HTRIGHT;
    
    		if ((nY > 0) && (nY < mouse_margin))
    			*result = HTTOP;
    
    		if ((nY > this->height() - mouse_margin) && (nY < this->height()))
    			*result = HTBOTTOM;
    
    		if ((nX > 0) && (nX < mouse_margin) && (nY > 0)
    			&& (nY < mouse_margin))
    			*result = HTTOPLEFT;
    
    		if ((nX > this->width() - mouse_margin) && (nX < this->width())
    			&& (nY > 0) && (nY < mouse_margin))
    			*result = HTTOPRIGHT;
    
    		if ((nX > 0) && (nX < mouse_margin)
    			&& (nY > this->height() - mouse_margin) && (nY < this->height()))
    			*result = HTBOTTOMLEFT;
    
    		if ((nX > this->width() - mouse_margin) && (nX < this->width())
    			&& (nY > this->height() - mouse_margin) && (nY < this->height()))
    			*result = HTBOTTOMRIGHT;
    
    		return true;
    	}
    	}
    
    	return QWidget::nativeEvent(eventType, message, result);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    二、实现主窗口
    派生于上面的CFramelessWindowBase,代码如下:
    FramelessWindow.h

    #pragma once
    
    #include 
    #include "CFramelessWindowBase.h"
    #include "TitleBar.h"
    #include "ContentWidget.h"
    #include "LeftBar.h"
    #include "CustomStatusBar.h"
    
    class FramelessWindow : public CFramelessWindowBase
    {
        Q_OBJECT
    
    public:
        FramelessWindow(QWidget *parent = nullptr);
        ~FramelessWindow();
    
    private slots:
        void OnClose();
    
    private:
        TitleBar* m_pTitleBar = nullptr;
        ContentWidget* m_pContentWidget = nullptr;
        LeftBar* m_pLeftBar = nullptr;
        CustomStatusBar* m_pStatusBar = nullptr;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    FramelessWindow.cpp

    /*
    
    主窗口
    
    */
    
    #include "FramelessWindow.h"
    #include 
    #include 
    
    
    FramelessWindow::FramelessWindow(QWidget *parent)
        : CFramelessWindowBase(parent)
    {
        this->resize(800, 600);
    
        QWidget* pWidget = new QWidget(this);
        this->setCentralWidget(pWidget);
    
        m_pTitleBar = new TitleBar(pWidget);
        m_pTitleBar->SetTitleText(tr("QMainWindow Custom Title"));
    
        QString logo_qss = R"(
    		QLabel{
    			background-image:url(:/TitleBar/Resources/TitleBar/logo32.svg);
    			background-position:center; 
    			background-repeat: no-repeat;
    			border:none
    		}
    	)";
    
        m_pTitleBar->SetTitleIcon(logo_qss);
    
        m_pContentWidget = new ContentWidget(pWidget);
    
        m_pLeftBar = new LeftBar(pWidget);
        m_pStatusBar = new CustomStatusBar(pWidget);
    
        QVBoxLayout* pVLay = new QVBoxLayout(pWidget);
        pVLay->setSpacing(0);
        pVLay->setContentsMargins(0, 0, 0, 0);
    
        pVLay->addWidget(m_pTitleBar);
    
        QHBoxLayout* pHLay = new QHBoxLayout(pWidget);
        pHLay->setSpacing(0);
        pHLay->addWidget(m_pLeftBar);
        pHLay->addWidget(m_pContentWidget);
    
        pVLay->addLayout(pHLay);
        pVLay->addWidget(m_pStatusBar);
    
        pWidget->setLayout(pVLay);
    
        connect(m_pTitleBar, &TitleBar::sig_Close, this, &FramelessWindow::OnClose);
    }
    
    FramelessWindow::~FramelessWindow()
    {
    }
    
    void FramelessWindow::OnClose()
    {
        QMessageBox::StandardButton resBtn = QMessageBox::question(this, tr("Tips"),
            tr("Are you sure you want to close the window?"),
            QMessageBox::Cancel | QMessageBox::Yes,
            QMessageBox::Yes);
    
        if (resBtn == QMessageBox::Yes) 
        {
            close();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    本篇博客源码下载:
    https://download.csdn.net/download/yao_hou/89211306?spm=1001.2014.3001.5501

  • 相关阅读:
    利用python连接linux虚机并执行命令
    SpringBoot流程解析(二)
    003:高精地图数据采集
    聊聊写代码的20个反面教材
    【Django毕业设计源码】Python考试题库练习系统
    Linux——02(网络配置、进程和服务、关机重启命令)
    Docker Compose
    计算机网络—ENSP常用指令
    spring boot + vue 前后端下载文件文件
    采集Nginx日志的几种方式
  • 原文地址:https://blog.csdn.net/yao_hou/article/details/138112653