文章目录
一、QWidget类介绍
QWidget
是 Qt 框架中的一个核心类,它是所有控件和窗口的基类。QWidget
提供了创建和管理 GUI 组件的基础功能。Qt所有的窗口、控件以及布局都是派生于QWidget。
关键特性与功能:
- 窗口和控件的基类:几乎所有的 GUI 组件都是
QWidget
的子类,包括按钮、文本框、标签、窗口等。 - 布局管理:
QWidget
支持多种布局管理方式,如水平布局、垂直布局和网格布局,这些布局可以帮助开发者轻松地组织控件的位置和大小。 - 事件处理:
QWidget
可以接收和处理各种事件,如鼠标点击、键盘输入、窗口大小变化等。 - 绘图功能:
QWidget
提供了基本的绘图功能,允许开发者在控件上绘制图形和文本。 - 控件属性:
QWidget
允许设置控件的各种属性,如背景颜色、边框样式、是否可编辑等。 - 焦点管理:
QWidget
可以控制控件的焦点,决定哪些控件可以接收键盘输入。 - 尺寸和位置:
QWidget
可以设置控件的大小、位置以及最小和最大尺寸。 - 子控件管理:
QWidget
可以添加、移除和查询子控件。 - 窗口修饰:对于窗口类型的
QWidget
,可以设置窗口的标题、图标、状态(如是否可最小化、最大化)等。 - 资源管理:
QWidget
管理着与之关联的资源,如字体、颜色、画笔等。 - 信号和槽:
QWidget
支持 Qt 的信号和槽机制,允许控件之间的事件通信。 - 国际化:
QWidget
支持国际化,可以根据不同地区的设置显示不同的文本。
详细内容可参考https://doc.qt.io/qt-5/qwidget.html
Qt很多问题都可以从QWidget类找到答案,学习Qt大部分就是在与QWidget打交道。
二、无边框窗口的基本实现
无边框窗口就是在QWidget的基础上去掉窗口边框和实现窗口移动(窗口随鼠标做矢量的平移)。
-
如何去掉窗口边框
this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint);
-
如何实现窗口移动
去掉窗口边框后,窗口无法移动。需要重写鼠标按下、移动事件函数。如果你在子类中重写了
mousePressEvent
函数,就可以自定义控件对鼠标按下事件的响应。在按下鼠标时,让窗口移动。事件处理:鼠标按下事件函数用于接收鼠标按下事件。当用户在控件上按下鼠标按钮时,Qt 框架会调用这个函数。
//鼠标按下事件函数 void QWidget::mousePressEvent(QMouseEvent *event) override; //鼠标移动事件函数 void QWidget::mouseMoveEvent(QMouseEvent *event) override;
-
QMouseEvent
该对象包含鼠标事件的详细信息,如鼠标的坐标位置、按下的是哪个鼠标按钮、鼠标事件的类型等。
移动位置函数:用来移动窗口、控件
void move(const QPoint&);
要实现窗口随鼠标做矢量平移的效果,需要利用Qt中的一些位置
-
窗口位置:窗口左上角相对于桌面左上角的位置
this->pos();
-
鼠标位置
event->pos(); //鼠标相对于应用程序的位置 event->globalPos(); //鼠标相对于桌面左上角的位置,绝对位置
-
利用鼠标与窗口左上角的相对位置,实现窗口随坐标的矢量平移
void MainWidget::mousePressEvent(QMouseEvent* event) { mouse_pos = event->globalPos(); window_pos = this->pos(); diff_pos = mouse_pos - window_pos; //鼠标和窗口的相对位移 } void MainWidget::mouseMoveEvent(QMouseEvent* event) { QPoint pos = event->globalPos(); //this->move(pos); //这样移动后窗口的左上角瞬间移动到鼠标处 this->move(pos - diff_pos); //这样移动窗口随坐标矢量平移 }
-
示例:
MainWidget.h:
#pragma once
#include <QtWidgets/QWidget>
class MainWidget : public QWidget
{
Q_OBJECT
public:
MainWidget(QWidget *parent = Q_NULLPTR);
private:
void mousePressEvent(QMouseEvent* event) override; //重写鼠标按下事件处理函数
void mouseMoveEvent(QMouseEvent* event) override; //重写鼠标移动事件处理函数
private:
QPoint diff_pos; //鼠标和窗口的相对位移
QPoint window_pos; //窗口的位置
QPoint mouse_pos; //鼠标的位置
};
MainWidget.cpp:
#include "MainWidget.h"
#include <QMouseEvent>
MainWidget::MainWidget(QWidget *parent)
: QWidget(parent)
{
//去掉窗口边框
this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint);
}
void MainWidget::mousePressEvent(QMouseEvent* event)
{
mouse_pos = event->globalPos();
window_pos = this->pos();
diff_pos = mouse_pos - window_pos; //鼠标和窗口的相对位移
}
void MainWidget::mouseMoveEvent(QMouseEvent* event)
{
QPoint pos = event->globalPos();
//this->move(pos); //这样移动后窗口的左上角瞬间移动到鼠标处
this->move(pos - diff_pos); //这样移动窗口随坐标矢量平移
}
main.cpp:
#include "MainWidget.h"
#include <QtWidgets/QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWidget w;
w.show();
return a.exec();
}
三、自定义标题栏并实现拖拽拉伸
在去掉窗口边框与实现窗口移动后,没有标题栏类,需要自定义标题栏类。创建标题栏界面类,添加到界面上,并添加最小化、最大化,关闭按钮。
-
无边框窗口拖拽移动更好的解决办法
#include <qt_windows.h> #pragma comment(lib, "user32.lib") void CTitleBar::mousePressEvent(QMouseEvent* event) { if (ReleaseCapture()) { QWidget* pWindow = this->window(); if (pWindow->isTopLevel()) { SendMessage(HWND(pWindow->winId()), WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0); } } event->ignore(); }
-
无边窗口的拉伸、改变大小
#include <qt_windows.h> #pragma comment(lib, "user32.lib") this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint); //无边框窗口 this->setAttribute(Qt::WA_Hover); //启用控件悬停效果,配合判断鼠标状态 //通过判断鼠标的状态进行拉伸 bool MainWidget::nativeEvent(const QByteArray& eventType, void* message, long* result) { MSG* param = static_cast<MSG*>(message); switch (param->message) //根据 param->message 的值来处理不同类型的消息 { case WM_NCHITTEST: //WM_NCHITTEST 是一个 Windows 消息,用于确定鼠标点击发生在窗口的哪个部分 { //将鼠标的屏幕坐标转换为相对于控件左上角的坐标 int nX = GET_X_LPARAM(param->lParam) - this->geometry().x(); int nY = GET_Y_LPARAM(param->lParam) - this->geometry().y(); /*if (childAt(nX, nY) != nullptr) return QWidget::nativeEvent(eventType, message, result);*/ //检查鼠标坐标是否在控件的内部边界内,排除边框 if (nX > m_nBorderWidth && nX < this->width() - m_nBorderWidth && nY > m_nBorderWidth && nY < this->height() - m_nBorderWidth) { //如果鼠标位置下存在子控件,则调用基类的 nativeEvent 处理事件;后续会调用该控件的鼠标点击函数用于拖拽移动 if (childAt(nX, nY) != nullptr) return QWidget::nativeEvent(eventType, message, result); } //判断鼠标点击是否在控件的边框上,并设置 *result 为相应的值。例如 HTLEFT、HTRIGHT、HTTOP 等,这些值定义了鼠标点击的边框部分。 if ((nX > 0) && (nX < m_nBorderWidth)) *result = HTLEFT; if ((nX > this->width() - m_nBorderWidth) && (nX < this->width())) *result = HTRIGHT; if ((nY > 0) && (nY < m_nBorderWidth)) *result = HTTOP; if ((nY > this->height() - m_nBorderWidth) && (nY < this->height())) *result = HTBOTTOM; if ((nX > 0) && (nX < m_nBorderWidth) && (nY > 0) && (nY < m_nBorderWidth)) *result = HTTOPLEFT; if ((nX > this->width() - m_nBorderWidth) && (nX < this->width()) && (nY > 0) && (nY < m_nBorderWidth)) *result = HTTOPRIGHT; if ((nX > 0) && (nX < m_nBorderWidth) && (nY > this->height() - m_nBorderWidth) && (nY < this->height())) *result = HTBOTTOMLEFT; if ((nX > this->width() - m_nBorderWidth) && (nX < this->width()) && (nY > this->height() - m_nBorderWidth) && (nY < this->height())) *result = HTBOTTOMRIGHT; //如果处理了 WM_NCHITTEST 消息,函数返回 true,表示事件已被处理。 return true; } } return false; //如果没有处理任何消息,函数返回 false,表示事件将被传递给 Qt 进行默认处理。 }
示例:
CTitleBar.h
#pragma once
#include <QtWidgets/QWidget>
#include <QLabel>
#include <QPushButton>
/**
自定义标题栏界面类,添加设置、最小化、最大化,关闭按钮
*/
class CTitleBar : public QWidget
{
public:
CTitleBar(QWidget* parent = nullptr);
~CTitleBar();
private:
void initUI(); //初始化UI
void mousePressEvent(QMouseEvent* event)override; //无边框窗口拖拽移动
private:
QLabel* m_pLogo;
QLabel* m_pTitleTextLabel;
QPushButton* m_pSetBtn;
QPushButton* m_pMinBtn;
QPushButton* m_pMaxBtn;
QPushButton* m_pCloseBtn;
};
CTitleBar.cpp
#pragma comment(lib, "user32.lib")
#include "CTitleBar.h"
#include <qt_windows.h>
#include <QHBoxLayout>
#include <QMouseEvent>w
CTitleBar::CTitleBar(QWidget* parent)
{
initUI();
}
CTitleBar::~CTitleBar()
{
}
void CTitleBar::initUI()
{
this->setAttribute(Qt::WA_StyledBackground); //禁止父窗口影响子窗口样式
this->setFixedHeight(32 + 10);
this->setStyleSheet("background-color:rgb(54, 54, 54)");
m_pLogo = new QLabel(this);
m_pLogo->setFixedWidth(32);
m_pTitleTextLabel = new QLabel(this);
m_pTitleTextLabel->setText(u8"我是标题");
m_pTitleTextLabel->setFixedWidth(120);
m_pSetBtn = new QPushButton(this);
m_pSetBtn->setFixedSize(QSize(32, 32));
m_pMinBtn = new QPushButton(this);
m_pMinBtn->setFixedSize(QSize(32, 32));
m_pMaxBtn = new QPushButton(this);
m_pMaxBtn->setFixedSize(QSize(32, 32));
m_pCloseBtn = new QPushButton(this);
m_pCloseBtn->setFixedSize(QSize(32, 32));
//布局
QHBoxLayout* m_pHLay = new QHBoxLayout(this);
m_pHLay->addWidget(m_pLogo);
m_pHLay->addWidget(m_pTitleTextLabel);
m_pHLay->addStretch(); //弹簧
m_pHLay->addWidget(m_pSetBtn);
m_pHLay->addWidget(m_pMinBtn);
m_pHLay->addWidget(m_pMaxBtn);
m_pHLay->addWidget(m_pCloseBtn);
//边界间隔
m_pHLay->setContentsMargins(5, 5, 5, 5);
}
//处理鼠标按下事件--无边框窗口拖拽移动的解决办法
void CTitleBar::mousePressEvent(QMouseEvent* event)
{
//释放当前捕获的鼠标,若成功释放返回true
if (ReleaseCapture())
{
QWidget* pWindow = this->window(); //获取窗口指针
if (pWindow->isTopLevel()) //检查获取到的窗口是否是一个顶级窗口(即不是其他窗口的子控件)
{
//如果窗口是顶级窗口,使用 SendMessage 函数向父窗口句柄发送一个 WM_SYSCOMMAND 消息,该消息指示窗口开始移动操作。
SendMessage(HWND(pWindow->winId()), WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0);
}
}
event->ignore();
}
MainWdiget.h
#pragma once
#include <QtWidgets/QWidget>
#include "CTitleBar.h"
class MainWidget : public QWidget
{
Q_OBJECT
public:
MainWidget(QWidget *parent = nullptr);
~MainWidget();
private:
void initUI(); //初始化UI
bool nativeEvent(const QByteArray& eventType, void* message, long* result)override;
private:
CTitleBar* m_pTitleBar = nullptr;
int m_nBorderWidth = 5;
};
MainWdiget.cpp
#include "MainWidget.h"
#include <QVBoxLayout>
#include <qt_windows.h>
#include <windows.h>
#include <windowsx.h>
#pragma comment(lib, "user32.lib")
#pragma comment(lib,"dwmapi.lib")
MainWidget::MainWidget(QWidget *parent)
: QWidget(parent)
{
//this->resize()
this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint); //无边框窗口
this->setAttribute(Qt::WA_Hover); //启用控件悬停效果,配合判断鼠标状态
initUI();
}
MainWidget::~MainWidget()
{}
void MainWidget::initUI()
{
m_pTitleBar = new CTitleBar(this); //标题栏
QWidget* w = new QWidget(this);
w->setMinimumSize(QSize(600, 400));
QVBoxLayout* pVlay = new QVBoxLayout(this);
pVlay->addWidget(m_pTitleBar);
pVlay->addWidget(w);
pVlay->setContentsMargins(0, 0, 0, 0); //边界间隙
}
//通过判断鼠标的状态进行拉伸
bool MainWidget::nativeEvent(const QByteArray& eventType, void* message, long* result)
{
MSG* param = static_cast<MSG*>(message);
switch (param->message) //根据 param->message 的值来处理不同类型的消息
{
case WM_NCHITTEST: //WM_NCHITTEST 是一个 Windows 消息,用于确定鼠标点击发生在窗口的哪个部分
{
//将鼠标的屏幕坐标转换为相对于控件左上角的坐标
int nX = GET_X_LPARAM(param->lParam) - this->geometry().x();
int nY = GET_Y_LPARAM(param->lParam) - this->geometry().y();
/*if (childAt(nX, nY) != nullptr)
return QWidget::nativeEvent(eventType, message, result);*/
//检查鼠标坐标是否在控件的内部边界内,排除边框
if (nX > m_nBorderWidth && nX < this->width() - m_nBorderWidth &&
nY > m_nBorderWidth && nY < this->height() - m_nBorderWidth)
{
//如果鼠标位置下存在子控件,则调用基类的 nativeEvent 处理事件;后续会调用该控件的鼠标点击函数用于拖拽移动
if (childAt(nX, nY) != nullptr)
return QWidget::nativeEvent(eventType, message, result);
}
//判断鼠标点击是否在控件的边框上,并设置 *result 为相应的值。例如 HTLEFT、HTRIGHT、HTTOP 等,这些值定义了鼠标点击的边框部分。
if ((nX > 0) && (nX < m_nBorderWidth))
*result = HTLEFT;
if ((nX > this->width() - m_nBorderWidth) && (nX < this->width()))
*result = HTRIGHT;
if ((nY > 0) && (nY < m_nBorderWidth))
*result = HTTOP;
if ((nY > this->height() - m_nBorderWidth) && (nY < this->height()))
*result = HTBOTTOM;
if ((nX > 0) && (nX < m_nBorderWidth) && (nY > 0)
&& (nY < m_nBorderWidth))
*result = HTTOPLEFT;
if ((nX > this->width() - m_nBorderWidth) && (nX < this->width())
&& (nY > 0) && (nY < m_nBorderWidth))
*result = HTTOPRIGHT;
if ((nX > 0) && (nX < m_nBorderWidth)
&& (nY > this->height() - m_nBorderWidth) && (nY < this->height()))
*result = HTBOTTOMLEFT;
if ((nX > this->width() - m_nBorderWidth) && (nX < this->width())
&& (nY > this->height() - m_nBorderWidth) && (nY < this->height()))
*result = HTBOTTOMRIGHT;
//如果处理了 WM_NCHITTEST 消息,函数返回 true,表示事件已被处理。
return true;
}
}
return false; //如果没有处理任何消息,函数返回 false,表示事件将被传递给 Qt 进行默认处理。
}
main.cpp
#include "MainWidget.h"
#include <QtWidgets/QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWidget w;
w.show();
return a.exec();
}
运行结果:无边框、可移动、可伸缩
四、设计一个无边框窗口公共类
抽取代码,新建无边框窗口类CFrameLessWidgetBase
,这个类具有可拉伸的特性,然后主窗口继承于这个类。
CFrameLessWidgetBase.h:
#pragma once
#include <QtWidgets/QWidget>
class CFrameLessWidgetBase : public QWidget
{
public:
CFrameLessWidgetBase(QWidget* parent = nullptr);
~CFrameLessWidgetBase();
protected:
bool nativeEvent(const QByteArray& eventType, void* message, long* result)override;
private:
int m_nBorderWidth = 5;
};
CFrameLessWidgetBase.cpp:
#include "CFrameLessWidgetBase.h"
#include <qt_windows.h>
#include <windows.h>
#include <windowsx.h>
#pragma comment(lib, "user32.lib")
#pragma comment(lib,"dwmapi.lib")
CFrameLessWidgetBase::CFrameLessWidgetBase(QWidget* parent)
{
this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint); //无边框窗口
this->setAttribute(Qt::WA_Hover); //启用控件悬停效果,配合判断鼠标状态
}
CFrameLessWidgetBase::~CFrameLessWidgetBase()
{
}
//通过判断鼠标的状态进行拉伸
bool CFrameLessWidgetBase::nativeEvent(const QByteArray& eventType, void* message, long* result)
{
MSG* param = static_cast<MSG*>(message);
switch (param->message) //根据 param->message 的值来处理不同类型的消息
{
case WM_NCHITTEST: //WM_NCHITTEST 是一个 Windows 消息,用于确定鼠标点击发生在窗口的哪个部分
{
//将鼠标的屏幕坐标转换为相对于控件左上角的坐标
int nX = GET_X_LPARAM(param->lParam) - this->geometry().x();
int nY = GET_Y_LPARAM(param->lParam) - this->geometry().y();
/*if (childAt(nX, nY) != nullptr)
return QWidget::nativeEvent(eventType, message, result);*/
//检查鼠标坐标是否在控件的内部边界内,排除边框
if (nX > m_nBorderWidth && nX < this->width() - m_nBorderWidth &&
nY > m_nBorderWidth && nY < this->height() - m_nBorderWidth)
{
//如果鼠标位置下存在子控件,则调用基类的 nativeEvent 处理事件;后续会调用该控件的鼠标点击函数用于拖拽移动
if (childAt(nX, nY) != nullptr)
return QWidget::nativeEvent(eventType, message, result);
}
//判断鼠标点击是否在控件的边框上,并设置 *result 为相应的值。例如 HTLEFT、HTRIGHT、HTTOP 等,这些值定义了鼠标点击的边框部分。
if ((nX > 0) && (nX < m_nBorderWidth))
*result = HTLEFT;
if ((nX > this->width() - m_nBorderWidth) && (nX < this->width()))
*result = HTRIGHT;
if ((nY > 0) && (nY < m_nBorderWidth))
*result = HTTOP;
if ((nY > this->height() - m_nBorderWidth) && (nY < this->height()))
*result = HTBOTTOM;
if ((nX > 0) && (nX < m_nBorderWidth) && (nY > 0)
&& (nY < m_nBorderWidth))
*result = HTTOPLEFT;
if ((nX > this->width() - m_nBorderWidth) && (nX < this->width())
&& (nY > 0) && (nY < m_nBorderWidth))
*result = HTTOPRIGHT;
if ((nX > 0) && (nX < m_nBorderWidth)
&& (nY > this->height() - m_nBorderWidth) && (nY < this->height()))
*result = HTBOTTOMLEFT;
if ((nX > this->width() - m_nBorderWidth) && (nX < this->width())
&& (nY > this->height() - m_nBorderWidth) && (nY < this->height()))
*result = HTBOTTOMRIGHT;
//如果处理了 WM_NCHITTEST 消息,函数返回 true,表示事件已被处理。
return true;
}
}
return false; //如果没有处理任何消息,函数返回 false,表示事件将被传递给 Qt 进行默认处理。
}
CTitleBar.h
#pragma once
#include <QtWidgets/QWidget>
#include <QLabel>
#include <QPushButton>
/**
自定义标题栏界面类,添加设置、最小化、最大化,关闭按钮
*/
class CTitleBar : public QWidget
{
public:
CTitleBar(QWidget* parent = nullptr);
~CTitleBar();
private:
void initUI(); //初始化UI
void mousePressEvent(QMouseEvent* event)override; //无边框窗口拖拽移动
private:
QLabel* m_pLogo;
QLabel* m_pTitleTextLabel;
QPushButton* m_pSetBtn;
QPushButton* m_pMinBtn;
QPushButton* m_pMaxBtn;
QPushButton* m_pCloseBtn;
};
CTitleBar.cpp
#pragma comment(lib, "user32.lib")
#include "CTitleBar.h"
#include <qt_windows.h>
#include <QHBoxLayout>
#include <QMouseEvent>w
CTitleBar::CTitleBar(QWidget* parent)
{
initUI();
}
CTitleBar::~CTitleBar()
{
}
void CTitleBar::initUI()
{
this->setAttribute(Qt::WA_StyledBackground); //禁止父窗口影响子窗口样式
this->setFixedHeight(32 + 10);
this->setStyleSheet("background-color:rgb(54, 54, 54)");
m_pLogo = new QLabel(this);
m_pLogo->setFixedWidth(32);
m_pTitleTextLabel = new QLabel(this);
m_pTitleTextLabel->setText(u8"我是标题");
m_pTitleTextLabel->setFixedWidth(120);
m_pSetBtn = new QPushButton(this);
m_pSetBtn->setFixedSize(QSize(32, 32));
m_pMinBtn = new QPushButton(this);
m_pMinBtn->setFixedSize(QSize(32, 32));
m_pMaxBtn = new QPushButton(this);
m_pMaxBtn->setFixedSize(QSize(32, 32));
m_pCloseBtn = new QPushButton(this);
m_pCloseBtn->setFixedSize(QSize(32, 32));
//布局
QHBoxLayout* m_pHLay = new QHBoxLayout(this);
m_pHLay->addWidget(m_pLogo);
m_pHLay->addWidget(m_pTitleTextLabel);
m_pHLay->addStretch(); //弹簧
m_pHLay->addWidget(m_pSetBtn);
m_pHLay->addWidget(m_pMinBtn);
m_pHLay->addWidget(m_pMaxBtn);
m_pHLay->addWidget(m_pCloseBtn);
//边界间隔
m_pHLay->setContentsMargins(5, 5, 5, 5);
}
//处理鼠标按下事件--无边框窗口拖拽移动的解决办法
void CTitleBar::mousePressEvent(QMouseEvent* event)
{
//释放当前捕获的鼠标,若成功释放返回true
if (ReleaseCapture())
{
QWidget* pWindow = this->window(); //获取窗口指针
if (pWindow->isTopLevel()) //检查获取到的窗口是否是一个顶级窗口(即不是其他窗口的子控件)
{
//如果窗口是顶级窗口,使用 SendMessage 函数向父窗口句柄发送一个 WM_SYSCOMMAND 消息,该消息指示窗口开始移动操作。
SendMessage(HWND(pWindow->winId()), WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0);
}
}
event->ignore();
}
MainWidget.h:
#pragma once
#include <QtWidgets/QWidget>
#include "CFrameLessWidgetBase.h"
#include "CTitleBar.h"
class MainWidget : public CFrameLessWidgetBase
{
Q_OBJECT
public:
MainWidget(QWidget *parent = nullptr);
~MainWidget();
private:
void initUI(); //初始化UI
private:
CTitleBar* m_pTitleBar = nullptr;
};
MainWidgwet.cpp:
#include "MainWidget.h"
#include <QVBoxLayout>
MainWidget::MainWidget(QWidget *parent)
: CFrameLessWidgetBase(parent)
{
//this->resize()
initUI();
}
MainWidget::~MainWidget()
{}
void MainWidget::initUI()
{
m_pTitleBar = new CTitleBar(this); //标题栏
QWidget* w = new QWidget(this);
w->setMinimumSize(QSize(600, 400));
QVBoxLayout* pVlay = new QVBoxLayout(this);
pVlay->addWidget(m_pTitleBar);
pVlay->addWidget(w);
pVlay->setContentsMargins(0, 0, 0, 0); //边界间隙
}
五、标题栏qss美化、关闭、最小化、最大化
-
qt按钮样式表设置
//以设置按钮为例 m_pSetBtn = new QPushButton(this); m_pSetBtn->setFixedSize(QSize(32, 32)); //hover指鼠标悬停在按钮时的样式 m_pSetBtn->setStyleSheet("QPushButton{background-image:url(:/MainWidget/resources/titlebar/set.svg);border:none}" \ "QPushButton:hover{" \ "background-color:rgb(99, 99, 99);" \ "background-image:url(:/MainWidget/resources/titlebar/set_hover.svg);border:none;}");
-
关闭、最小化、最大化功能实现
-
方案一:发信号到父窗口,父窗口收到信号后进行相应操作
-
方案二:在子窗口获取父窗口指针,进行相应操作
QWidget* pWindow = this->window(); pWindow->showMinimized(); //最小化
this->window()介绍:
QWidget* QWidget::window() const;
返回此小部件的窗口,即具有(或可能具有)窗口系统框架的下一个祖先小部件。如果小部件是一个窗口,则返回小部件本身。
槽函数代码为:
void CTitleBar::onClicked() { QPushButton* pBtn = qobject_cast<QPushButton*>(sender()); //获得发送者 QWidget* pWindow = this->window(); //获取父窗口指针 if (pBtn == m_pMinBtn) { pWindow->showMinimized(); //最小化显示 } else if (pBtn == m_pMaxBtn) { if (pWindow->isMaximized()) //若已经是最大化,则常规显示 { pWindow->showNormal(); m_pMaxBtn->setStyleSheet("QPushButton{background-image:url(:/MainWidget/resources/titlebar/normal.svg);border:none}" \ "QPushButton:hover{" \ "background-color:rgb(99, 99, 99);" \ "background-image:url(:/MainWidget/resources/titlebar/normal_hover.svg);border:none;}"); } else//否则,最大化显示 { pWindow->showMaximized(); m_pMaxBtn->setStyleSheet("QPushButton{background-image:url(:/MainWidget/resources/titlebar/max.svg);border:none}" \ "QPushButton:hover{" \ "background-color:rgb(99, 99, 99);" \ "background-image:url(:/MainWidget/resources/titlebar/max_hover.svg);border:none;}"); } } else if (pBtn == m_pCloseBtn) { emit signal_closeBtn(); //向主窗口发送信号来关闭 } }
-
示例:
CFrameLessWidgetBase.h:
#pragma once
#include <QtWidgets/QWidget>
class CFrameLessWidgetBase : public QWidget
{
public:
CFrameLessWidgetBase(QWidget* parent = nullptr);
~CFrameLessWidgetBase();
protected:
bool nativeEvent(const QByteArray& eventType, void* message, long* result)override;
private:
int m_nBorderWidth = 5;
};
CFrameLessWidgetBase.cpp:
#include "CFrameLessWidgetBase.h"
#include <qt_windows.h>
#include <windows.h>
#include <windowsx.h>
#pragma comment(lib, "user32.lib")
#pragma comment(lib,"dwmapi.lib")
CFrameLessWidgetBase::CFrameLessWidgetBase(QWidget* parent)
{
this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint); //无边框窗口
this->setAttribute(Qt::WA_Hover); //启用控件悬停效果,配合判断鼠标状态
}
CFrameLessWidgetBase::~CFrameLessWidgetBase()
{
}
//通过判断鼠标的状态进行拉伸
bool CFrameLessWidgetBase::nativeEvent(const QByteArray& eventType, void* message, long* result)
{
MSG* param = static_cast<MSG*>(message);
switch (param->message) //根据 param->message 的值来处理不同类型的消息
{
case WM_NCHITTEST: //WM_NCHITTEST 是一个 Windows 消息,用于确定鼠标点击发生在窗口的哪个部分
{
//将鼠标的屏幕坐标转换为相对于控件左上角的坐标
int nX = GET_X_LPARAM(param->lParam) - this->geometry().x();
int nY = GET_Y_LPARAM(param->lParam) - this->geometry().y();
//这种逻辑是不合理的,因为边框也在控件之上,点击边框会一直调用基类的 nativeEvent 处理事件,而不能伸缩
/*if (childAt(nX, nY) != nullptr)
return QWidget::nativeEvent(eventType, message, result);*/
//检查鼠标坐标是否在控件的内部边界内,排除边框
if (nX > m_nBorderWidth && nX < this->width() - m_nBorderWidth &&
nY > m_nBorderWidth && nY < this->height() - m_nBorderWidth)
{
//如果鼠标位置下存在子控件,则调用基类的 nativeEvent 处理事件;后续会调用该控件的鼠标点击函数用于拖拽移动
if (childAt(nX, nY) != nullptr)
return QWidget::nativeEvent(eventType, message, result);
}
//判断鼠标点击是否在控件的边框上,并设置 *result 为相应的值。例如 HTLEFT、HTRIGHT、HTTOP 等,这些值定义了鼠标点击的边框部分。
if ((nX > 0) && (nX < m_nBorderWidth))
*result = HTLEFT;
if ((nX > this->width() - m_nBorderWidth) && (nX < this->width()))
*result = HTRIGHT;
if ((nY > 0) && (nY < m_nBorderWidth))
*result = HTTOP;
if ((nY > this->height() - m_nBorderWidth) && (nY < this->height()))
*result = HTBOTTOM;
if ((nX > 0) && (nX < m_nBorderWidth) && (nY > 0)
&& (nY < m_nBorderWidth))
*result = HTTOPLEFT;
if ((nX > this->width() - m_nBorderWidth) && (nX < this->width())
&& (nY > 0) && (nY < m_nBorderWidth))
*result = HTTOPRIGHT;
if ((nX > 0) && (nX < m_nBorderWidth)
&& (nY > this->height() - m_nBorderWidth) && (nY < this->height()))
*result = HTBOTTOMLEFT;
if ((nX > this->width() - m_nBorderWidth) && (nX < this->width())
&& (nY > this->height() - m_nBorderWidth) && (nY < this->height()))
*result = HTBOTTOMRIGHT;
//如果处理了 WM_NCHITTEST 消息,函数返回 true,表示事件已被处理。
return true;
}
}
return false; //如果没有处理任何消息,函数返回 false,表示事件将被传递给 Qt 进行默认处理。
}
CTitleBar.h:
#pragma once
#include <QtWidgets/QWidget>
#include <QLabel>
#include <QPushButton>
/**
自定义标题栏界面类,添加设置、最小化、最大化,关闭按钮
*/
class CTitleBar : public QWidget
{
Q_OBJECT
public:
CTitleBar(QWidget* parent = nullptr);
~CTitleBar();
private:
void initUI(); //初始化UI
void mousePressEvent(QMouseEvent* event)override; //无边框窗口拖拽移动
void mouseDoubleClickEvent(QMouseEvent* event)override; //鼠标双击事件
private slots:
void onClicked(); //最大最小关闭按钮统一的槽函数
signals:
void signal_closeBtn(); //点击关闭按钮时向主窗口发送信号
private:
QLabel* m_pLogo;
QLabel* m_pTitleTextLabel;
QPushButton* m_pSetBtn;
QPushButton* m_pMinBtn;
QPushButton* m_pMaxBtn;
QPushButton* m_pCloseBtn;
};
CTitleBar.cpp:
#pragma comment(lib, "user32.lib")
#include "CTitleBar.h"
#include <qt_windows.h>
#include <QHBoxLayout>
#include <QMouseEvent>w
CTitleBar::CTitleBar(QWidget* parent)
{
initUI();
}
CTitleBar::~CTitleBar()
{
}
void CTitleBar::initUI()
{
this->setAttribute(Qt::WA_StyledBackground); //禁止父窗口影响子窗口样式
this->setFixedHeight(32 + 10);
this->setStyleSheet("background-color:rgb(54, 54, 54)");
m_pLogo = new QLabel(this);
m_pLogo->setFixedWidth(32);
m_pLogo->setStyleSheet("background-image:url(:/MainWidget/resources/titlebar/title_icon.png);border:none");
m_pTitleTextLabel = new QLabel(this);
m_pTitleTextLabel->setText(u8"我是标题");
m_pTitleTextLabel->setFixedWidth(120);
m_pTitleTextLabel->setStyleSheet("QLabel{font-family: Microsoft YaHei; \
font-size:18px; \
color:#BDC8E2;background-color:rgb(54,54,54);}");
m_pSetBtn = new QPushButton(this);
m_pSetBtn->setFixedSize(QSize(32, 32));
m_pSetBtn->setStyleSheet("QPushButton{background-image:url(:/MainWidget/resources/titlebar/set.svg);border:none}" \
"QPushButton:hover{" \
"background-color:rgb(99, 99, 99);" \
"background-image:url(:/MainWidget/resources/titlebar/set_hover.svg);border:none;}");
m_pMinBtn = new QPushButton(this);
m_pMinBtn->setFixedSize(QSize(32, 32));
m_pMinBtn->setStyleSheet("QPushButton{background-image:url(:/MainWidget/resources/titlebar/min.svg);border:none}" \
"QPushButton:hover{" \
"background-color:rgb(99, 99, 99);" \
"background-image:url(:/MainWidget/resources/titlebar/min_hover.svg);border:none;}");
m_pMaxBtn = new QPushButton(this);
m_pMaxBtn->setFixedSize(QSize(32, 32));
m_pMaxBtn->setStyleSheet("QPushButton{background-image:url(:/MainWidget/resources/titlebar/normal.svg);border:none}" \
"QPushButton:hover{" \
"background-color:rgb(99, 99, 99);" \
"background-image:url(:/MainWidget/resources/titlebar/normal_hover.svg);border:none;}");
m_pCloseBtn = new QPushButton(this);
m_pCloseBtn->setFixedSize(QSize(32, 32));
m_pCloseBtn->setStyleSheet("QPushButton{background-image:url(:/MainWidget/resources/titlebar/close.svg);border:none}" \
"QPushButton:hover{" \
"background-color:rgb(99, 99, 99);" \
"background-image:url(:/MainWidget/resources/titlebar/close_hover.svg);border:none;}");
//布局
QHBoxLayout* m_pHLay = new QHBoxLayout(this);
m_pHLay->addWidget(m_pLogo);
m_pHLay->addWidget(m_pTitleTextLabel);
m_pHLay->addStretch(); //弹簧
m_pHLay->addWidget(m_pSetBtn);
QSpacerItem* pItem1 = new QSpacerItem(20, 20, QSizePolicy::Fixed, QSizePolicy::Fixed);
m_pHLay->addSpacerItem(pItem1);
m_pHLay->addWidget(m_pMinBtn);
QSpacerItem* pItem2 = new QSpacerItem(18, 20, QSizePolicy::Fixed, QSizePolicy::Fixed);
m_pHLay->addSpacerItem(pItem2);
m_pHLay->addWidget(m_pMaxBtn);
QSpacerItem* pItem3 = new QSpacerItem(18, 20, QSizePolicy::Fixed, QSizePolicy::Fixed);
m_pHLay->addSpacerItem(pItem3); // 弹簧每次使用时得new出来,不能重复使用,否则会报析构错误
m_pHLay->addWidget(m_pCloseBtn);
//边界间隔
m_pHLay->setContentsMargins(5, 5, 5, 5);
//三个发送者(按钮)链接到同一个槽
connect(m_pMaxBtn, &QPushButton::clicked, this, &CTitleBar::onClicked);
connect(m_pMinBtn, &QPushButton::clicked, this, &CTitleBar::onClicked);
connect(m_pCloseBtn, &QPushButton::clicked, this, &CTitleBar::onClicked);
}
//处理鼠标按下事件--无边框窗口拖拽移动的解决办法
void CTitleBar::mousePressEvent(QMouseEvent* event)
{
//释放当前捕获的鼠标,若成功释放返回true
if (ReleaseCapture())
{
QWidget* pWindow = this->window(); //获取窗口指针
if (pWindow->isTopLevel()) //检查获取到的窗口是否是一个顶级窗口(即不是其他窗口的子控件)
{
//如果窗口是顶级窗口,使用 SendMessage 函数向父窗口句柄发送一个 WM_SYSCOMMAND 消息,该消息指示窗口开始移动操作。
SendMessage(HWND(pWindow->winId()), WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0);
}
}
event->ignore();
}
//鼠标双击事件
void CTitleBar::mouseDoubleClickEvent(QMouseEvent* event)
{
m_pMaxBtn->clicked();
}
void CTitleBar::onClicked()
{
QPushButton* pBtn = qobject_cast<QPushButton*>(sender()); //获得发送者
QWidget* pWindow = this->window(); //获取父窗口指针
if (pBtn == m_pMinBtn)
{
pWindow->showMinimized(); //最小化显示
}
else if (pBtn == m_pMaxBtn)
{
if (pWindow->isMaximized()) //若已经是最大化,则常规显示
{
pWindow->showNormal();
m_pMaxBtn->setStyleSheet("QPushButton{background-image:url(:/MainWidget/resources/titlebar/normal.svg);border:none}" \
"QPushButton:hover{" \
"background-color:rgb(99, 99, 99);" \
"background-image:url(:/MainWidget/resources/titlebar/normal_hover.svg);border:none;}");
}
else//否则,最大化显示
{
pWindow->showMaximized();
m_pMaxBtn->setStyleSheet("QPushButton{background-image:url(:/MainWidget/resources/titlebar/max.svg);border:none}" \
"QPushButton:hover{" \
"background-color:rgb(99, 99, 99);" \
"background-image:url(:/MainWidget/resources/titlebar/max_hover.svg);border:none;}");
}
}
else if (pBtn == m_pCloseBtn)
{
emit signal_closeBtn(); //向主窗口发送信号来关闭
}
}
MainWidget.h:
#pragma once
#include <QtWidgets/QWidget>
#include "CFrameLessWidgetBase.h"
#include "CTitleBar.h"
class MainWidget : public CFrameLessWidgetBase
{
Q_OBJECT
public:
MainWidget(QWidget *parent = nullptr);
~MainWidget();
private:
void initUI(); //初始化UI
private slots:
void on_closeSlot();
private:
CTitleBar* m_pTitleBar = nullptr;
};
MainWidget.cpp:
#include "MainWidget.h"
#include <QVBoxLayout>
#include <QMessageBox>
MainWidget::MainWidget(QWidget *parent)
: CFrameLessWidgetBase(parent)
{
//this->resize()
initUI();
//标题栏中的关闭按钮链接槽函数
connect(m_pTitleBar, &CTitleBar::signal_closeBtn, this, &MainWidget::on_closeSlot);
}
MainWidget::~MainWidget()
{}
void MainWidget::initUI()
{
m_pTitleBar = new CTitleBar(this); //标题栏
QWidget* w = new QWidget(this);
w->setMinimumSize(QSize(600, 400));
QVBoxLayout* pVlay = new QVBoxLayout(this);
pVlay->addWidget(m_pTitleBar);
pVlay->addWidget(w);
pVlay->setContentsMargins(0, 0, 0, 0); //边界间隙
}
void MainWidget::on_closeSlot()
{
//窗口关闭处理
QMessageBox::StandardButton _exit = QMessageBox::warning(this, u8"提示", u8"确定要退出吗", QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
if (_exit == QMessageBox::Yes)
{
close();
}
}
main.cpp:
#include "MainWidget.h"
#include <QtWidgets/QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWidget w;
w.show();
return a.exec();
}
运行结果:
无边框窗口、可移动、可伸缩、标题栏样式表、最大化、最小化、关闭、双击标题栏可最大化与正常显示。
六、实现窗口阴影
要实现窗口阴影,需要设置两层窗口。
-
将底层窗口设置为窗体半透明、无边框
//设置底层窗体背景为半透明 this->setAttribute(Qt::WA_TranslucentBackground, true); //设置无边框 this->setWindowFlags(Qt::Window | Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint);
-
将顶层窗口设置背景颜色
//给顶层widget设置背景颜色,不然看不见,因为底层widget已经透明了 pRealWidget->setStyleSheet("background-color:rgb(255, 254, 253)");
-
新建Qt窗口阴影类,给顶层QWidget设置阴影
QGraphicsDropShadowEffect* shadow = new QGraphicsDropShadowEffect(this); //设置阴影距离 shadow->setOffset(0, 0); //设置阴影颜色 686868 shadow->setColor(QColor("#252a2b")); //深灰色 //设置阴影区域 shadow->setBlurRadius(30); //给顶层QWidget设置阴影 pRealWidget->setGraphicsEffect(shadow);
示例:
CLoginDlg.h:
#pragma once
#include <QDialog>
class CLoginDlg : public QDialog
{
Q_OBJECT
public:
CLoginDlg(QWidget* parent = Q_NULLPTR);
~CLoginDlg();
private:
void mousePressEvent(QMouseEvent* event) override;
void mouseMoveEvent(QMouseEvent* event) override;
private:
//记录鼠标,窗口位置
QPoint windowPos;
QPoint mousePos;
QPoint dPos;
};
CLoginDlg.cpp:
#include "CLoginDlg.h"
#include "CLoginRealWidget.h"
#include <QGraphicsDropShadowEffect>
#include <QVboxLayout>
#include <QMouseEvent>
CLoginDlg::CLoginDlg(QWidget* parent)
: QDialog(parent)
{
//设置窗体的背景为半透明
this->setAttribute(Qt::WA_TranslucentBackground, true);
//设置无边框
this->setWindowFlags(Qt::Window | Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint);
QVBoxLayout* pMainLay = new QVBoxLayout(this);
CLoginRealWidget* pRealWidget = new CLoginRealWidget(this);
pMainLay->addWidget(pRealWidget);
pMainLay->setContentsMargins(30, 30, 30, 30);
setLayout(pMainLay);
//给顶层widget设置背景颜色,不然看不见,因为底层widget已经透明了
pRealWidget->setStyleSheet("background-color:rgb(255, 254, 253)");
QGraphicsDropShadowEffect* shadow = new QGraphicsDropShadowEffect(this);
//设置阴影距离
shadow->setOffset(0, 0);
//设置阴影颜色 686868
shadow->setColor(QColor("#686868")); //灰色
//设置阴影区域
shadow->setBlurRadius(30);
//给顶层QWidget设置阴影
pRealWidget->setGraphicsEffect(shadow);
}
CLoginDlg::~CLoginDlg()
{
}
void CLoginDlg::mousePressEvent(QMouseEvent* event)
{
this->windowPos = this->pos(); // 获得部件当前位置
this->mousePos = event->globalPos(); // 获得鼠标位置
this->dPos = mousePos - windowPos; // 移动后部件所在的位置
}
void CLoginDlg::mouseMoveEvent(QMouseEvent* event)
{
this->move(event->globalPos() - this->dPos);
}
CLoginRealWidget.h:
#pragma once
#include <QWidget>
class CLoginRealWidget : public QWidget
{
Q_OBJECT
public:
CLoginRealWidget(QWidget* p = nullptr);
~CLoginRealWidget();
};
CLoginRealWidget.cpp:
#include "CLoginRealWidget.h"
#include <QLabel>
#include <QLineEdit>
#include <QCheckBox>
#include <QPushButton>
#include <QHBoxLayout>
#include <QGridLayout>
CLoginRealWidget::CLoginRealWidget(QWidget* p)
:QWidget(p)
{
//禁止父窗口影响子窗口样式
setAttribute(Qt::WA_StyledBackground);
setWindowFlags(Qt::FramelessWindowHint);
//头像
QLabel* pImageLabel = new QLabel(this);
QPixmap pixmap(":/CLoginDlg/resource/user_image.png");
pImageLabel->setFixedSize(150, 150);
pImageLabel->setPixmap(pixmap);
pImageLabel->setScaledContents(true);
//用户名
QLineEdit* pUserNameLineEdit = new QLineEdit(this);
pUserNameLineEdit->setFixedSize(300, 50);
pUserNameLineEdit->setPlaceholderText(QStringLiteral("QQ号码/手机/邮箱"));
//密码
QLineEdit* pPasswordLineEdit = new QLineEdit(this);
pPasswordLineEdit->setFixedSize(300, 50);
pPasswordLineEdit->setPlaceholderText(QStringLiteral("密码"));
pPasswordLineEdit->setEchoMode(QLineEdit::Password);
QPushButton* pForgotButton = new QPushButton(this);
pForgotButton->setText(QStringLiteral("找回密码"));
pForgotButton->setFixedWidth(80);
QCheckBox* pRememberCheckBox = new QCheckBox(this);
pRememberCheckBox->setText(QStringLiteral("记住密码"));
QCheckBox* pAutoLoginCheckBox = new QCheckBox(this);
pAutoLoginCheckBox->setText(QStringLiteral("自动登录"));
QPushButton* pLoginButton = new QPushButton(this);
pLoginButton->setFixedHeight(48);
pLoginButton->setText(QStringLiteral("登录"));
QPushButton* pRegisterButton = new QPushButton(this);
pRegisterButton->setFixedHeight(48);
pRegisterButton->setText(QStringLiteral("注册账号"));
QHBoxLayout* pMainLay = new QHBoxLayout(this);
QSpacerItem* pHSpacer1 = new QSpacerItem(25, 20, QSizePolicy::Fixed, QSizePolicy::Fixed);
pMainLay->addSpacerItem(pHSpacer1);
QGridLayout* pGridLayout = new QGridLayout(this);
// 头像 第0行,第0列开始,占3行1列
pGridLayout->addWidget(pImageLabel, 0, 0, 3, 1);
// 用户名输入框 第0行,第1列开始,占1行2列
pGridLayout->addWidget(pUserNameLineEdit, 0, 1, 1, 2);
// 密码输入框 第1行,第1列开始,占1行2列
pGridLayout->addWidget(pPasswordLineEdit, 1, 1, 1, 2);
// 忘记密码 第2行,第1列开始,占1行1列
pGridLayout->addWidget(pForgotButton, 2, 1, 1, 1);
// 记住密码 第2行,第2列开始,占1行1列 水平居中 垂直居中
pGridLayout->addWidget(pRememberCheckBox, 2, 2, 1, 1, Qt::AlignLeft | Qt::AlignVCenter);
// 自动登录 第2行,第2列开始,占1行1列 水平居右 垂直居中
pGridLayout->addWidget(pAutoLoginCheckBox, 2, 2, 1, 1, Qt::AlignRight | Qt::AlignVCenter);
// 登录按钮 第3行,第1列开始,占1行2列
pGridLayout->addWidget(pLoginButton, 3, 1, 1, 2);
// 注册按钮 第4行,第1列开始,占1行2列
pGridLayout->addWidget(pRegisterButton, 4, 1, 1, 2);
// 设置水平间距
pGridLayout->setHorizontalSpacing(10);
// 设置垂直间距
pGridLayout->setVerticalSpacing(10);
pMainLay->addLayout(pGridLayout);
QSpacerItem* pHSpacer2 = new QSpacerItem(25, 20, QSizePolicy::Fixed, QSizePolicy::Fixed);
pMainLay->addSpacerItem(pHSpacer2);
pMainLay->setContentsMargins(5, 5, 5, 5);
setLayout(pMainLay);
}
CLoginRealWidget::~CLoginRealWidget()
{
}
main.cpp:
#include "CLoginDlg.h"
#include <QtWidgets/QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
CLoginDlg w;
w.show();
return a.exec();
}
运行结果:
七、圆角窗口
圆角实现有两种方案,前提是:
this->setAttribute(Qt::WA_TranslucentBackground); //设置窗口背景透明
this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint); //设置无边框窗口
-
方案一:重写paintEvent方法
void MainWidget::paintEvent(QPaintEvent * event) { QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); //反锯齿 painter.setBrush(QBrush(QColor(155, 54, 54))); painter.setPen(Qt::transparent); QRect rect = this->rect(); painter.drawRoundedRect(rect, 15, 15); //设置窗口圆角15px }
-
方案二:样式表qss
this->setStyleSheet("QWidget{background-color:gray;border-radius:30px;}"); //窗口透明后,需要重写该方法,不然窗口无法显示 void MainWidget::paintEvent(QPaintEvent* event) { QStyleOption opt; //QStyleOption 用于传递绘制项的配置选项给样式函数 opt.init(this); //表示样式选项与当前的 MainWidget 相关联 QPainter p(this); style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); //调用当前样式的 drawPrimitive 方法来绘制一个基本的绘制项 QWidget::paintEvent(event); //调用基类 QWidget 的 paintEvent 方法 }
样式表qss可以设置更多圆角样式
- 左上角:
border-top-left-radius:15px
- 右上角:
border-top-right-radius:15px
- 左下角:
border-bottom-left-radius:15px
- 右下角:
border-bottom-right-radius:15px
border-radius的参数设置:
- 可以只传一个参数,这样x,y方向的圆度是一样的,例如:
border-radius:30px
- 传两个参数:
border-radius:15px 20px
- 第一个参数设置X轴方向的半径
- 第二个参数设置Y轴方向的半径
- 左上角:
八、一个自定义标题栏带圆角阴影的窗口
需要设计双层窗口,底层为透明窗口,顶层是放控件的窗口。
示例:
CFrameLessWidgetBase.h:
/*
无边框窗口基类--可拉伸
其它窗口派生于该类即可
*/
#pragma once
#include <QWidget>
class CFrameLessWidgetBase : public QWidget
{
public:
CFrameLessWidgetBase(QWidget* parent = nullptr);
~CFrameLessWidgetBase();
protected:
bool nativeEvent(const QByteArray& eventType, void* message, long* result) override;
private:
int mouse_margin = 5;
};
CFrameLessWidgetBase.cpp:
#include "CFrameLessWidgetBase.h"
#ifdef Q_OS_WIN
#include <qt_windows.h>
#include <Windows.h>
#include <windowsx.h>
#endif
#pragma comment(lib, "user32.lib")
#pragma comment(lib,"dwmapi.lib")
CFrameLessWidgetBase::CFrameLessWidgetBase(QWidget* parent)
{
setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint);
setAttribute(Qt::WA_Hover);
}
CFrameLessWidgetBase::~CFrameLessWidgetBase()
{
}
bool CFrameLessWidgetBase::nativeEvent(const QByteArray& eventType, void* message, long* result)
{
MSG* param = static_cast<MSG*>(message);
switch (param->message)
{
case WM_NCHITTEST:
{
int nX = GET_X_LPARAM(param->lParam) - this->geometry().x();
int nY = GET_Y_LPARAM(param->lParam) - 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);
}
CTitleBar.h:
#pragma once
#include <QtWidgets/QWidget>
#include <QLabel>
#include <QPushButton>
/**
自定义标题栏界面类,添加设置、最小化、最大化,关闭按钮
*/
class CTitleBar : public QWidget
{
Q_OBJECT
public:
CTitleBar(QWidget* parent = nullptr);
~CTitleBar();
private:
void initUI(); //初始化UI
void paintEvent(QPaintEvent* event) override;
void mousePressEvent(QMouseEvent* event)override; //无边框窗口拖拽移动
void mouseDoubleClickEvent(QMouseEvent* event)override; //鼠标双击事件
private slots:
void onClicked(); //最大最小关闭按钮统一的槽函数
signals:
void sig_close();
void sig_max(bool isMax);
private:
QLabel* m_pLogo;
QLabel* m_pTitleTextLabel;
QPushButton* m_pSetBtn;
QPushButton* m_pMinBtn;
QPushButton* m_pMaxBtn;
QPushButton* m_pCloseBtn;
};
CTitleBar.cpp:
#pragma comment(lib, "user32.lib")
#include "CTitleBar.h"
#include <qt_windows.h>
#include <QHBoxLayout>
#include <QMouseEvent>
#include <QStyleOption>
#include <QPainter>
CTitleBar::CTitleBar(QWidget* parent)
{
initUI();
}
CTitleBar::~CTitleBar()
{
}
void CTitleBar::initUI()
{
this->setAttribute(Qt::WA_StyledBackground); //禁止父窗口影响子窗口样式
this->setFixedHeight(32 + 10);
//左上,右上圆角10;左下,右下圆角0
this->setStyleSheet("QWidget{background-color:rgb(54,54,54); \
border-top-left-radius:10px; \
border-top-right-radius:10px; \
border-bottom-left-radius:0px; \
border-bottom-right-radius:0px;}");
m_pLogo = new QLabel(this);
m_pLogo->setFixedWidth(32);
m_pLogo->setStyleSheet("background-image:url(:/MainWidget/resources/titlebar/title_icon.png);border:none");
m_pTitleTextLabel = new QLabel(this);
m_pTitleTextLabel->setText(u8"我是标题");
m_pTitleTextLabel->setFixedWidth(120);
m_pTitleTextLabel->setStyleSheet("QLabel{font-family: Microsoft YaHei; \
font-size:18px; \
color:#BDC8E2;background-color:rgb(54,54,54);}");
m_pSetBtn = new QPushButton(this);
m_pSetBtn->setFixedSize(QSize(32, 32));
m_pSetBtn->setText("");
m_pSetBtn->setFlat(true);
m_pSetBtn->setStyleSheet("QPushButton{background-image:url(:/MainWidget/resources/titlebar/set.svg);border:none}" \
"QPushButton:hover{" \
"background-color:rgb(99, 99, 99);" \
"background-image:url(:/MainWidget/resources/titlebar/set_hover.svg);border:none;}");
m_pMinBtn = new QPushButton(this);
m_pMinBtn->setFixedSize(QSize(32, 32));
m_pMinBtn->setText("");
m_pMinBtn->setFlat(true);
m_pMinBtn->setStyleSheet("QPushButton{background-image:url(:/MainWidget/resources/titlebar/min.svg);border:none}" \
"QPushButton:hover{" \
"background-color:rgb(99, 99, 99);" \
"background-image:url(:/MainWidget/resources/titlebar/min_hover.svg);border:none;}");
m_pMaxBtn = new QPushButton(this);
m_pMaxBtn->setFixedSize(QSize(32, 32));
m_pMaxBtn->setText("");
m_pMaxBtn->setFlat(true);
m_pMaxBtn->setStyleSheet("QPushButton{background-image:url(:/MainWidget/resources/titlebar/normal.svg);border:none}" \
"QPushButton:hover{" \
"background-color:rgb(99, 99, 99);" \
"background-image:url(:/MainWidget/resources/titlebar/normal_hover.svg);border:none;}");
m_pCloseBtn = new QPushButton(this);
m_pCloseBtn->setFixedSize(QSize(32, 32));
m_pCloseBtn->setText("");
m_pCloseBtn->setFlat(true);
m_pCloseBtn->setStyleSheet("QPushButton{background-image:url(:/MainWidget/resources/titlebar/close.svg);border:none}" \
"QPushButton:hover{" \
"background-color:rgb(99, 99, 99);" \
"background-image:url(:/MainWidget/resources/titlebar/close_hover.svg);border:none;}");
//布局
QHBoxLayout* m_pHLay = new QHBoxLayout(this);
m_pHLay->addWidget(m_pLogo);
m_pHLay->addWidget(m_pTitleTextLabel);
m_pHLay->addStretch(); //弹簧
m_pHLay->addWidget(m_pSetBtn);
QSpacerItem* pItem1 = new QSpacerItem(20, 20, QSizePolicy::Fixed, QSizePolicy::Fixed);
m_pHLay->addSpacerItem(pItem1);
m_pHLay->addWidget(m_pMinBtn);
QSpacerItem* pItem2 = new QSpacerItem(18, 20, QSizePolicy::Fixed, QSizePolicy::Fixed);
m_pHLay->addSpacerItem(pItem2);
m_pHLay->addWidget(m_pMaxBtn);
QSpacerItem* pItem3 = new QSpacerItem(18, 20, QSizePolicy::Fixed, QSizePolicy::Fixed);
m_pHLay->addSpacerItem(pItem3); // 弹簧每次使用时得new出来,不能重复使用,否则会报析构错误
m_pHLay->addWidget(m_pCloseBtn);
//边界间隔
m_pHLay->setContentsMargins(5, 5, 5, 5);
//三个发送者(按钮)链接到同一个槽
connect(m_pMaxBtn, &QPushButton::clicked, this, &CTitleBar::onClicked);
connect(m_pMinBtn, &QPushButton::clicked, this, &CTitleBar::onClicked);
connect(m_pCloseBtn, &QPushButton::clicked, this, &CTitleBar::onClicked);
}
void CTitleBar::paintEvent(QPaintEvent* event)
{
//决定样式表是否起作用
//QStyleOption opt;
//opt.init(this);
//QPainter p(this);
//style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
//QWidget::paintEvent(event);
}
//处理鼠标按下事件--无边框窗口拖拽移动的解决办法
void CTitleBar::mousePressEvent(QMouseEvent* event)
{
//释放当前捕获的鼠标,若成功释放返回true
if (ReleaseCapture())
{
QWidget* pWindow = this->window(); //获取窗口指针
if (pWindow->isTopLevel()) //检查获取到的窗口是否是一个顶级窗口(即不是其他窗口的子控件)
{
//如果窗口是顶级窗口,使用 SendMessage 函数向父窗口句柄发送一个 WM_SYSCOMMAND 消息,该消息指示窗口开始移动操作。
SendMessage(HWND(pWindow->winId()), WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0);
}
}
event->ignore();
}
//鼠标双击事件
void CTitleBar::mouseDoubleClickEvent(QMouseEvent* event)
{
m_pMaxBtn->clicked();
}
void CTitleBar::onClicked()
{
QPushButton* pBtn = qobject_cast<QPushButton*>(sender()); //获得发送者
QWidget* pWindow = this->window(); //获取父窗口指针
if (pBtn == m_pMinBtn)
{
pWindow->showMinimized(); //最小化显示
}
else if (pBtn == m_pMaxBtn)
{
if (pWindow->isMaximized()) //若已经是最大化,则常规显示
{
pWindow->showNormal();
m_pMaxBtn->setStyleSheet("QPushButton{background-image:url(:/MainWidget/resources/titlebar/normal.svg);border:none; \
background-position:center; \
background-repeat:no-repeat;} \
QPushButton:hover{ \
background-color:rgb(99, 99, 99); \
background-image:url(:/MainWidget/resource/titlebar/normal_hover.svg);border:none;}");
emit sig_max(false);
}
else//否则,最大化显示
{
pWindow->showMaximized();
m_pMaxBtn->setStyleSheet("QPushButton{background-image:url(:/MainWidget/resources/titlebar/max.svg);border:none; \
background-position:center; \
background-repeat:no-repeat;} \
QPushButton:hover{ \
background-color:rgb(99, 99, 99); \
background-image:url(:/MainWidget/resource/titlebar/max_hover.svg);border:none;}");
emit sig_max(true);
}
}
else if (pBtn == m_pCloseBtn)
{
emit sig_close();
}
}
CTopWidget.h:
#pragma once
#include <QWidget>
class CTopWidget : public QWidget
{
Q_OBJECT
public:
CTopWidget(QWidget* p = nullptr);
~CTopWidget();
signals:
void sig_close();
void sig_max(bool isMax);
private:
void paintEvent(QPaintEvent* event) override;
};
CTopWidget.cpp:
#include "CTopWidget.h"
#include "CTitleBar.h"
#include <QVBoxLayout>
#include <QStyleOption>
#include <QPainter>
CTopWidget::CTopWidget(QWidget* p)
:QWidget(p)
{
setAttribute(Qt::WA_StyledBackground); //禁止父窗口影响子窗口样式
this->setAttribute(Qt::WA_TranslucentBackground); //设置窗口背景透明
//this->setStyleSheet("QWidget{background-color:rgb(255, 254, 253);border-radius:10px;}");
/*this->setStyleSheet("QWidget{background-color:rgb(255, 254, 253); \
border-bottom-left-radius:15px; \
border-bottom-right-radius:15px;}");*/
QVBoxLayout* pVLay = new QVBoxLayout(this);
CTitleBar* pTitle = new CTitleBar(this);
QWidget* pWidget = new QWidget(this);
pWidget->setStyleSheet("QWidget{background-color:rgb(255, 254, 253); \
border-bottom-left-radius:15px; \
border-bottom-right-radius:15px;}");
//需要指定pWidget最小尺寸,或最大尺寸
pWidget->setMinimumSize(1200, 800);
pVLay->addWidget(pTitle);
pVLay->addWidget(pWidget);
pVLay->setContentsMargins(0, 0, 0, 0);
setLayout(pVLay);
connect(pTitle, &CTitleBar::sig_close, this, &CTopWidget::sig_close);
connect(pTitle, &CTitleBar::sig_max, this, &CTopWidget::sig_max);
}
CTopWidget::~CTopWidget()
{
}
void CTopWidget::paintEvent(QPaintEvent* event)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing); //反锯齿
painter.setBrush(QBrush(QColor(255, 254, 253)));
painter.setPen(Qt::transparent);
QRect rect = this->rect();
painter.drawRoundedRect(rect, 10, 10); //设置窗口圆角15px
}
MainWidget.h:
#pragma once
#include <QtWidgets/QWidget>
#include "CFrameLessWidgetBase.h"
#include "CTopWidget.h"
#include <QVBoxLayout>
#include <QGraphicsDropShadowEffect>
//派生于CFrameLessWidgetBase
class MainWidget : public CFrameLessWidgetBase
{
Q_OBJECT
public:
MainWidget(QWidget* parent = Q_NULLPTR);
private slots:
void onClose();
void onDoMax(bool isMax);
private:
QVBoxLayout* m_pMainVLay = nullptr;
CTopWidget* m_pTopWidget = nullptr;
QGraphicsDropShadowEffect* m_pShadow = nullptr;
};
MainWidget.cpp:
#include "MainWidget.h"
MainWidget::MainWidget(QWidget* parent)
: CFrameLessWidgetBase(parent)
{
//设置窗体透明
this->setAttribute(Qt::WA_TranslucentBackground, true);
m_pMainVLay = new QVBoxLayout(this);
m_pTopWidget = new CTopWidget();
m_pMainVLay->addWidget(m_pTopWidget);
int shadow_width = 30;
m_pMainVLay->setContentsMargins(shadow_width, shadow_width, shadow_width, shadow_width);
setLayout(m_pMainVLay);
//给顶层widget设置背景颜色,不然看不见,因为底层widget已经透明了
m_pTopWidget->setStyleSheet("background-color:rgb(255, 254, 253)");
m_pShadow = new QGraphicsDropShadowEffect(this);
//设置阴影距离
m_pShadow->setOffset(0, 0);
//设置阴影颜色 686868
m_pShadow->setColor(QColor("#686868"));
//设置阴影区域
m_pShadow->setBlurRadius(shadow_width - 5);
//给顶层QWidget设置阴影
m_pTopWidget->setGraphicsEffect(m_pShadow);
connect(m_pTopWidget, &CTopWidget::sig_close, this, &MainWidget::onClose);
connect(m_pTopWidget, &CTopWidget::sig_max, this, &MainWidget::onDoMax);
}
void MainWidget::onClose()
{
close();
}
void MainWidget::onDoMax(bool isMax)
{
int shadow_width = 25;
if (isMax)
{
shadow_width = 0;
}
else
{
}
m_pMainVLay->setContentsMargins(shadow_width, shadow_width, shadow_width, shadow_width);
//设置阴影区域
m_pShadow->setBlurRadius(shadow_width);
//给顶层QWidget设置阴影
m_pTopWidget->setGraphicsEffect(m_pShadow);
}
main.cpp:
#include "MainWidget.h"
#include <QtWidgets/QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWidget w;
w.show();
return a.exec();
}
运行结果: