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

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

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

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

具体实现过程如下:

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

CFramelessWindowBase.h

/*

QMainWindow无边框窗口基类

可拉伸

其它QMainWindow窗口派生于该类即可

*/

#pragma once
#include <QMainWindow>

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;
};

CFramelessWindowBase.cpp

#include "CFramelessWindowBase.h"
#include <qt_windows.h>
#include <windowsx.h>
#include <QWindow>
#include <windows.h>
#include <dwmapi.h>

#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)在CFramelessWindowBase类设置窗口标志,去掉窗口边框,设置最大最小显示效果。

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

(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);

这里使用的是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);
}

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

#pragma once

#include <QtWidgets/QMainWindow>
#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;
};

FramelessWindow.cpp

/*

主窗口

*/

#include "FramelessWindow.h"
#include <QVBoxLayout>
#include <QMessageBox>


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();
    }
}

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

QMainWindowQt框架中的一个窗口控件类,用于创建基于主窗口的应用程序界面。通常情况下,QMainWindow会自带一个标题栏边框,用于窗口的显示和与用户交互。然而,即使没有标题栏边框,也可以通过四周改变窗口大小。 要实现这个效果,我们可以使用QWidget类的窗口样式(WindowFlags)来设置窗口的属性。具体来说,我们可以使用窗口样式的Qt::FramelessWindowHint标志来指示窗口没有边框。同时,我们可以使用窗口样式的Qt::WindowTitleHint标志来指示窗口没有标题栏。这样设置之后,窗口将不再显示边框标题栏。 然而,即使没有标题栏边框,我们仍然可以通过四周改变窗口大小。这是因为QWidget类提供了resizeEvent()函数,该函数在窗口大小变化时被调用。我们可以通过重写这个函数,并在函数内部实现自定义窗口大小改变逻辑。比如,我们可以使用setGeometry()函数来设置窗口的新位置和大小。 当用户通过鼠标拖动窗口四周时,会触发resizeEvent()函数,并且传递相应的参数,包括窗口的新位置和大小。我们可以根据这些参数来改变窗口的大小,以达到通过四周改变窗口大小的效果。在这个过程中,我们可以通过调用QMouseEvent类提供的函数来获得鼠标的位置信息,然后进行相应的处理。 总之,即使没有标题栏边框,我们仍然可以通过重写resizeEvent()函数并结合鼠标事件来实现通过四周改变窗口大小的效果。这样可以增加用户对窗口大小的控制,提升应用程序的可用性和交互性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

令狐掌门

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值