Qt自绘无边框窗口类
概述:
功能: 无边框窗体基类,支持最小化、最大化、关闭、鼠标拖拽、鼠标按下移动等;
文件说明:
1.CFrameWidget.h 无边框窗体基类
2.CTitle.h 窗体上方标题栏类
3.TextWidget.h 测试窗体类
代码示例:
CFrameWidget.h:
/*
* @file :CFrameWidget.h
* @note :无边框窗体基类,支持最小化、最大化、关闭、鼠标拖拽、鼠标按下移动等
* @author :Fantast
* @data :2021-5-10
*/
#pragma once
#include <QWidget>
#include <QFile>
#include <QMoveEvent>
class CFrameWidget : public QWidget
{
Q_OBJECT
public:
CFrameWidget(QWidget *parent);
~CFrameWidget();
/*
* @brief :设置样式
* @qssPath:路径
*/
void setStyleSheet(const QString qssPath);
/*
* @brief 设置是否可以移动
*/
void setMoveAble(bool enable);
/*
* @brief 设置是否可以缩放
*/
void setZoomAble(bool enable);
/*
* @brief 设置可拖动区域高(title高)
*/
void setTitleHeight(const int height);
public slots:
/*
* @brief 窗口关闭
*/
void slot_CloseWidget();
/*
* @brief 窗口缩放
*/
void slot_MaxWidget();
/*
* @brief 窗口最小化
*/
void slot_MinWidget();
private:
/*
* @brief 检测鼠标是否在边缘,显示拖动鼠标样式
*/
void checkEdge();
protected:
/*
* @brief 鼠标双击事件
*/
virtual void mouseDoubleClickEvent(QMouseEvent *event);
/*
* @brief 鼠标按下事件
*/
virtual void mousePressEvent(QMouseEvent * event);
/*
* @brief 鼠标抬起事件
*/
virtual void mouseReleaseEvent(QMouseEvent * event);
/*
* @brief 鼠标移动事件
*/
virtual void mouseMoveEvent(QMouseEvent * event);
/*
* @brief 设置从最小化放大后正常处理事件
*/
void showEvent(QShowEvent * e);
private:
// 鼠标上次移动开始时相对屏幕的位置
QPoint mPntStart;
// 鼠标是否在可拖动位置按下
bool mousePressInMoveArea;
// 鼠标是否在拖拽大小的位置按下
bool mousePressBorderArea;
// 鼠标是否持续按下
bool mbKeepPressed;
//窗口是否可缩放
bool allowZoom;
//窗口是否可移动
bool allowMove;
//鼠标检测的边缘距离
int m_nEdgeMargin;
//窗口拖动距离范围
int TitlearHeight;
//更改窗口大小的方向
typedef enum
{
NO_DIR = 0x00,
TOP = 0x01,
BOTTOM = 0x02,
LEFT = 0x04,
RIGHT = 0x08,
TOP_LEFT = 0x01 | 0x04,
TOP_RIGHT = 0x01 | 0x08,
BOTTOM_LEFT = 0x02 | 0x04,
BOTTOM_RIGHT = 0x02 | 0x08
} EMouseDir;
EMouseDir m_nMouseDir;
};
CFrameWidget.cpp
#include "CFrameWidget.h"
#define min(a,b) ((a)<(b)? (a) :(b))
#define max(a,b) ((a)>(b)? (a) :(b))
CFrameWidget::CFrameWidget(QWidget *parent)
: QWidget(parent)
, m_nMouseDir(EMouseDir::NO_DIR)
, mousePressInMoveArea (false)
, mousePressBorderArea(false)
, mbKeepPressed(false)
, allowZoom(true)
, allowMove(true)
, m_nEdgeMargin(2)
, TitlearHeight(20)
{
setWindowFlags(Qt::FramelessWindowHint);//无边框
setMouseTracking(true);//鼠标跟踪
}
CFrameWidget::~CFrameWidget()
{
}
void CFrameWidget::setStyleSheet(const QString qssPath)
{
QFile file(qssPath);
file.open(QFile::ReadOnly);
if (file.isOpen())
{
QWidget::setStyleSheet(file.readAll());
file.close();
}
}
void CFrameWidget::setZoomAble(bool enable)
{
allowZoom = enable;
}
void CFrameWidget::setMoveAble(bool enable)
{
allowMove = enable;
}
void CFrameWidget::mouseMoveEvent(QMouseEvent *event)
{
// 持续按住才做对应事件
if (mousePressInMoveArea)
{
//如果鼠标不是放在边缘那么说明这是在拖动窗口
if (m_nMouseDir == NO_DIR)
{
//将窗体移动到鼠标位置
move(event->globalPos() - mPntStart);
}
}
//拖拽放大缩小
if (mousePressBorderArea)
{
//窗口上下左右的值
int nTop, nBottom, nLeft, nRight;
nTop = frameGeometry().top();
nBottom = frameGeometry().bottom();
nLeft = frameGeometry().left();
nRight = frameGeometry().right();
//检测更改尺寸方向中包含的上下左右分量
if (m_nMouseDir & TOP)
{
if (height() == minimumHeight())
{
nTop = min(event->globalY(), nTop);
}
else if (height() == maximumHeight())
{
nTop = max(event->globalY(), nTop);
}
else
{
nTop = event->globalY();
}
}
else if (m_nMouseDir & BOTTOM)
{
if (height() == minimumHeight())
{
nBottom = max(event->globalY(), nTop);
}
else if (height() == maximumHeight())
{
nBottom = min(event->globalY(), nTop);
}
else
{
nBottom = event->globalY();
}
}
if (m_nMouseDir & LEFT)
{ //检测左右分量
if (width() == minimumWidth())
{
nLeft = min(event->globalX(), nLeft);
}
else if (width() == maximumWidth())
{
nLeft = max(event->globalX(), nLeft);
}
else
{
nLeft = event->globalX();
}
}
else if (m_nMouseDir & RIGHT)
{
if (width() == minimumWidth())
{
nRight = max(event->globalX(), nRight);
}
else if (width() == maximumWidth())
{
nRight = min(event->globalX(), nRight);
}
else
{
nRight = event->globalX();
}
}
setGeometry(QRect(QPoint(nLeft, nTop), QPoint(nRight, nBottom)));
}
else if (allowZoom&&!isMaximized())
{
checkEdge();
}
event->ignore();
}
void CFrameWidget::checkEdge()
{
//计算鼠标距离窗口上下左右有多少距离
int nDeltaLeft = abs(cursor().pos().x() - frameGeometry().left());
int nDeltaRight = abs(cursor().pos().x() - frameGeometry().right());
int nDeltaTop = abs(cursor().pos().y() - frameGeometry().top());
int nDeltaBottom = abs(cursor().pos().y() - frameGeometry().bottom());
QCursor tempCursor = cursor();
if (nDeltaTop < m_nEdgeMargin)
{
//根据 边缘距离 分类改变尺寸的方向
if (nDeltaLeft < m_nEdgeMargin)
{
m_nMouseDir = TOP_LEFT;
tempCursor.setShape(Qt::SizeFDiagCursor);
}
else if (nDeltaRight < m_nEdgeMargin)
{
m_nMouseDir = TOP_RIGHT;
tempCursor.setShape(Qt::SizeBDiagCursor);
}
else
{
m_nMouseDir = TOP;
tempCursor.setShape(Qt::SizeVerCursor);
}
}
else if (nDeltaBottom < m_nEdgeMargin)
{
if (nDeltaLeft < m_nEdgeMargin)
{
m_nMouseDir = BOTTOM_LEFT;
tempCursor.setShape(Qt::SizeBDiagCursor);
}
else if (nDeltaRight < m_nEdgeMargin)
{
m_nMouseDir = BOTTOM_RIGHT;
tempCursor.setShape(Qt::SizeFDiagCursor);
}
else
{
m_nMouseDir = BOTTOM;
tempCursor.setShape(Qt::SizeVerCursor);
}
}
else if (nDeltaLeft < m_nEdgeMargin)
{
m_nMouseDir = LEFT;
tempCursor.setShape(Qt::SizeHorCursor);
}
else if (nDeltaRight < m_nEdgeMargin)
{
m_nMouseDir = RIGHT;
tempCursor.setShape(Qt::SizeHorCursor);
}
else
{
m_nMouseDir = NO_DIR;
tempCursor.setShape(Qt::ArrowCursor);
}
//重新设置鼠标样式
setCursor(tempCursor);
}
void CFrameWidget::mousePressEvent(QMouseEvent *event)
{
// 鼠标左键按下事件
if (event->button() == Qt::LeftButton)
{
mbKeepPressed = true;
if (!isMaximized())
{
if (m_nMouseDir != NO_DIR&&allowZoom)
{
mousePressBorderArea = true;
}
else if (allowMove)
{
//是否为双击和拖动区域
int x = event->pos().x();
int y = event->pos().y();
if (x >= 0 && x <= this->width()
&& y >= m_nEdgeMargin/2 && y <= TitlearHeight)
{
// 标记鼠标按下
mousePressInMoveArea = true;
// 记录鼠标与窗体位置的差值
mPntStart = event->pos();
}
}
}
}
}
void CFrameWidget::mouseReleaseEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton)
{
// 鼠标左键释放
mbKeepPressed = false;
mousePressBorderArea = false;
mousePressInMoveArea = false;
}
}
void CFrameWidget::mouseDoubleClickEvent(QMouseEvent *event)
{
int x = event->pos().x();
int y = event->pos().y();
if (x >= 0 && x <= this->width()
&& y >= 0 && y <= TitlearHeight)
{
slot_MaxWidget();
}
}
void CFrameWidget::slot_CloseWidget() {
close();
}
void CFrameWidget::slot_MaxWidget()
{
if (allowZoom)
{
if (isMaximized())
{
showNormal();
}
else {
showMaximized();
}
}
}
void CFrameWidget::slot_MinWidget()
{
showMinimized();
}
void CFrameWidget::showEvent(QShowEvent *e)
{
this->setAttribute(Qt::WA_Mapped);
QWidget::showEvent(e);
}
void CFrameWidget::setTitleHeight(const int height)
{
//获取标题栏高度
TitlearHeight = height;
}
// //调用WIN API需要用到的头文件与库 [实现缩放]
// #ifdef Q_OS_WIN
// #include <qt_windows.h>
// #include <Windowsx.h>
// #endif
// //nativeEvent主要用于进程间通信-消息传递,使用这种方式后来实现窗体的缩放 [加上了这函数,窗口也能移动了]
// bool WidgetBaseClass::nativeEvent(const QByteArray &eventType, void *message, long *result)
// {
// int m_nBorderWidth = 5;
// Q_UNUSED(eventType)
// 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();
// *result = HTCLIENT;
// // 鼠标区域位于标题栏按钮之上,则不进行处理
// QList<QPushButton *> buttons = TopBar->findChildren<QPushButton *>();
// foreach(QPushButton *pButton, buttons)
// {
// if (pButton->geometry().contains(QPoint(nX, nY)))
// {
// return true;
// }
// }
// // 鼠标区域位于标题栏中,进行移动
// if (nX >= m_nBorderWidth && nX <= this->width() - m_nBorderWidth
// && nY >= m_nBorderWidth && nY <= TopBar->height())
// {
// return true;
// }
//
// // 鼠标区域位于窗体边框,进行缩放
// 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;
// return true;
//
// }
// }
// return QWidget::nativeEvent(eventType, message, result);
// }
CTitle.h:
#pragma once
#include <QWidget>
#include "ui_CTitle.h"
class CTitle : public QWidget
{
Q_OBJECT
public:
CTitle(QWidget *parent = Q_NULLPTR);
~CTitle();
private:
/*
* @brief:初始化操作
*/
void Init();
signals:
/**
* @note 最小化信号
*/
void SignalMinisizeWidget();
/**
* @note 还原信号
*/
void SignalMaxisizeWidget();
/**
* @note 关闭信号
*/
void SignalCloseWidget();
protected slots:
/**
* @note 最小化窗口的槽函数
*/
void SlotMinisizeWidget();
/**
* @note 还原窗口的槽函数
*/
void SlotMaxisizeWidget();
/**
* @note 关闭窗口的槽函数
*/
void SlotCloseWidget();
private:
Ui::CTitle ui;
};
CTitle.cpp:
#include "CTitle.h"
CTitle::CTitle(QWidget *parent)
: QWidget(parent)
{
ui.setupUi(this);
//无边框
setWindowFlags(Qt::FramelessWindowHint);
Init();
}
CTitle::~CTitle()
{
}
void CTitle::Init()
{
connect(ui.pushButton_min, &QPushButton::clicked, this, &CTitle::SlotMinisizeWidget);
connect(ui.pushButton_max, &QPushButton::clicked, this, &CTitle::SlotMaxisizeWidget);
connect(ui.pushButton_close, &QPushButton::clicked, this, &CTitle::SlotCloseWidget);
}
void CTitle::SlotMinisizeWidget()
{
emit SignalMinisizeWidget();
}
void CTitle::SlotMaxisizeWidget()
{
emit SignalMaxisizeWidget();
}
void CTitle::SlotCloseWidget()
{
emit SignalCloseWidget();
}
CTitle.ui:
TextWidget.h:
#pragma once
#include <QtWidgets/QWidget>
#include "ui_TextWidget.h"
#include "CFrameWidget.h"
class TextWidget : public CFrameWidget
{
Q_OBJECT
public:
TextWidget(QWidget *parent = Q_NULLPTR);
private:
/*
* @brief:初始化函数
*/
void Init();
private:
Ui::TextWidgetClass ui;
};
TextWidget.cpp:
#include "TextWidget.h"
#include "Title/CTitle.h"
TextWidget::TextWidget(QWidget *parent)
: CFrameWidget(parent)
{
ui.setupUi(this);
Init();
}
void TextWidget::Init()
{
CTitle *title = new CTitle;
ui.Layout_title->addWidget(title);
//连接标题栏信号槽
connect(title, SIGNAL(SignalMinisizeWidget()), this, SLOT(slot_MinWidget()));
connect(title, SIGNAL(SignalMaxisizeWidget()), this, SLOT(slot_MaxWidget()));
connect(title, SIGNAL(SignalCloseWidget()), this, SLOT(slot_CloseWidget()));
setStyleSheet(":/TextWidget/Resources/Widget.qss");
}
TextWidget.ui:
运行效果图:
over:
欢迎大家关注作者在文末评论、点赞、转发以及批评指正!
如果大家有更好的方法或有问题可以在文末评论一起讨论!
共同学习!
共同进步!
文末一句话:
因为这些无论好坏的碎片拼凑,才组成我们琐碎又完整的生活。
因为这些喜怒哀乐的微小情绪,才是我们最真实最纯粹的样子。