本篇博客介绍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