1.设置为无边框窗口后拉伸、移动、放大缩小等事件需要自己重新实现,我只实现标题栏拖动、双击放大/还原、窗体边缘可拉伸等基本功能,有其他需求的小伙伴可参考和修改源码
2.拉伸右、下、右下这三个方向不会抖动,拉伸其他方向均会抖动,貌似很多自定义窗口的软件都是这样,包括微软的Edge浏览器、QQ、网易云音乐等
3.事件实现看过很多别人的方法,思路都是差不多的,但仅实现放大、缩小、移动、拉伸的话,很多人的代码都写得过于复杂,不便于我这种菜鸡阅读和理解。我的代码中拉伸实现主要参考了qt 无边框窗口,可拖动,可拉伸 这篇文章
4.试过刘典武大佬的无抖动方案源码,一样是会抖动的,相对来说没那么抖
Qt开源作品38-无边框窗体方案(无抖动,支持win、linux、mac等系统,侧边半屏顶部全屏)
链接: https://pan.baidu.com/s/1HtN-ciYU_-HaVTId6gjMKw
提取码: 5dmy
声明
#ifndef TITLEBAR_H
#define TITLEBAR_H
#include
#include
#include
QT_BEGIN_NAMESPACE
namespace Ui { class TitleBar; }
QT_END_NAMESPACE
class TitleBar : public QWidget
{
Q_OBJECT
public:
TitleBar(QWidget *parent = nullptr);
~TitleBar();
/**
* @brief msetStyleSheet 通过文件设置全局样式
* @param styleSheet css/qss等样式文件
*/
static void msetStyleSheet(const QString &styleSheet);
/**
* @brief setMainWidget 设置主窗口
* @param widget 窗口指针
*/
void setMainWidget(QWidget *widget);
/**
* @brief setTitleBarText 设置标题
* @param text 文本
*/
void setTitleBarText(const QString &text);
/**
* @brief setTitleBarIcon 设置标题栏图标
* @param icon 图标
*/
void setTitleBarIcon(const QString &icon);
/**
* @brief setTitleBarStyleSheet 设置标题栏样式
* @param sheet 样式
*/
void setTitleBarStyleSheet(const QString &sheet);
/**
* @brief setTitleBarBackGround 设置标题栏背景颜色
* @param r 红色值
* @param g 绿色值
* @param b 蓝色值
*/
void setTitleBarBackGround(const int &r, const int &g, const int &b);
/**
* @brief setTitleTextFont 设置标题栏字体
* @param font 字体样式
*/
void setTitleTextFont(const QFont &font);
/**
* @brief setMarginSize 设置拉伸边距
* @param size 边距值
*/
void setMarginSize(const int &size);
/**
* @brief The MousePosition enum 鼠标区域枚举
* 左上角(1,1) 上(1,2) 右上角(1,3)
* 左(2,1) 中(2,2) 右(2,3)
* 左下角(3,1) 下(3,2) 右下角(3,3)
*/
enum MousePosition
{
PosLeftTop = 11,
PosTop = 12,
PosRightTop = 13,
PosLeft = 21,
PosMid = 22,
PosRight = 23,
PosLeftBottom = 31,
PosBottom = 32,
PosRightBottom = 33,
};
protected:
bool eventFilter(QObject *watched, QEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
/**
* @brief getMouseArea 获取鼠标所在区域
* @param pos 全局坐标
* @return
*/
int getMouseArea(const QPoint &pos);
/**
* @brief setMouseCursor 设置鼠标样式
* @param pos 全局坐标
*/
void setMouseCursor(const QPoint &pos);
private:
Ui::TitleBar *ui;
QWidget *mainWidget;//主窗口指针
QPoint dragPos;//窗口拖动位置
bool isMoveEvent;//标题栏移动事件标志
QPoint lastPos;//左键按下时停留的坐标
bool leftBtnPress;//左键按下标志
int mousePressArea; //鼠标点击的区域
int marginSize;//边缘边距值
bool isMidArea;//在非边缘区域标志
int minWidth;//窗体(可缩小至)宽度
int minHeight;//窗体(可缩小至)高度
};
#endif // TITLEBAR_H
以一个Widget作为整体窗口,在此基础上添加一个Widget作为标题栏,然后提供一个添加主窗口的接口函数 setMainWidget,布局属性边距、垂直策略那些啥的在ui文件里设置了
void TitleBar::setMainWidget(QWidget *widget)
{
if (widget != nullptr) {
mainWidget = widget;
mainWidget->setParent(this);
mainWidget->show();
mainWidget->installEventFilter(this);
//重新布局标题栏和窗口
QLayout *mainLayout = this->layout();
mainLayout->removeWidget(ui->titleWidget);
mainLayout->addWidget(ui->titleWidget);
mainLayout->addWidget(mainWidget);
}
}
2. 标题栏拖动窗口及双击最大化
用事件过滤器限定窗口整体拖动只在标题栏事件中
窗口拖动应该不难,偏移量就是左键按下时鼠标的位置相对于整体窗口的左上角的位置
双击放大就没啥好说了,但是会和拖动冲突,现象就是双击放大后窗口移动到了鼠标的位置,所以用isMoveEvent这个标志来解决冲突
bool TitleBar::eventFilter(QObject *watched, QEvent *event)
{
//标题栏区域事件
if (isMidArea && watched == ui->titleWidget) {
setCursor(Qt::ArrowCursor);
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
switch (event->type()) {
case QEvent::MouseButtonPress:
dragPos = mouseEvent->globalPos() - frameGeometry().topLeft();//计算偏移量
isMoveEvent = true;
break;
case QEvent::MouseButtonDblClick:
isMoveEvent = false;
emit ui->btnMax->clicked();
break;
case QEvent::MouseMove:
if (isMoveEvent && !isMaximized())
move(mouseEvent->globalPos() - dragPos);//移动窗口
break;
case QEvent::MouseButtonRelease:
isMoveEvent = false;
break;
default:
break;
}
}
//主窗口区域事件
if (isMidArea && watched == mainWidget)
setCursor(Qt::ArrowCursor);
return QWidget::eventFilter(watched, event);
}
上图把整体窗口分为了9个区域,光标在哪个区域就根据图上的划分计算(函数getMouseArea),得出区域值后便根据区域值对应的区域设置光标(函数setMouseCursor),在mouseMoveEvent中调用setMouseCursor
区域枚举值
/**
* @brief The MousePosition enum 鼠标区域枚举
* 左上角(1,1) 上(1,2) 右上角(1,3)
* 左(2,1) 中(2,2) 右(2,3)
* 左下角(3,1) 下(3,2) 右下角(3,3)
*/
enum MousePosition
{
PosLeftTop = 11,
PosTop = 12,
PosRightTop = 13,
PosLeft = 21,
PosMid = 22,
PosRight = 23,
PosLeftBottom = 31,
PosBottom = 32,
PosRightBottom = 33,
};
int TitleBar::getMouseArea(const QPoint &pos)
{
int posX = pos.x();//全局x坐标
int posY = pos.y();//全局y坐标
int mainWidth = width();//全局宽度
int mainHeight = height();//全局高度
int areaX = 0;//x所在区域
int areaY = 0;//y所在区域
//判断x所在区域
if (posX > (mainWidth - marginSize))
areaX = 3;
else if (posX < marginSize)
areaX = 1;
else
areaX = 2;
//判断y所在区域
if (posY > (mainHeight - marginSize))
areaY = 3;
else if (posY < marginSize)
areaY = 1;
else
areaY = 2;
//返回区域值,如区域1:1 + 1*10 = 11
return areaX + areaY*10;
}
void TitleBar::setMouseCursor(const QPoint &pos)
{
Qt::CursorShape cursor;
//获取区域值
int area = getMouseArea(pos);
switch (area) {
case PosLeftTop:
case PosRightBottom:
cursor = Qt::SizeFDiagCursor;
break;
case PosRightTop:
case PosLeftBottom:
cursor = Qt::SizeBDiagCursor;
break;
case PosLeft:
case PosRight:
cursor = Qt::SizeHorCursor;
break;
case PosTop:
case PosBottom:
cursor = Qt::SizeVerCursor;
break;
case PosMid:
cursor = Qt::ArrowCursor;
break;
default:
break;
}
setCursor(cursor);
}
3.2. 拉伸动作
重点在重写mouseMoveEvent函数,核心在switch语句中,根据光标所在区域以及偏移量进行运算,条件判断用于防止过度拉伸,理解代码时可不看
void TitleBar::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
leftBtnPress = true;//左键按下标志
lastPos = event->globalPos();//按下时鼠标坐标
mousePressArea = getMouseArea(event->pos());//按下时鼠标所在区域
}
return QWidget::mousePressEvent(event);
}
void TitleBar::mouseMoveEvent(QMouseEvent *event)
{
//最大化状态时不能拉伸
if (isMaximized())
return;
//根据位置设置鼠标样式
if (!leftBtnPress)
setMouseCursor(event->pos());
if (leftBtnPress && (event->buttons() & Qt::LeftButton)) {
if (mousePressArea != PosMid)
isMidArea = false;
QPoint offset = event->globalPos() - lastPos;//偏移量=当前鼠标坐标-按下时鼠标坐标
int offsetX = offset.x();
int offsetY = offset.y();
QRect globalGeometry = frameGeometry();//获取窗口矩形
int globalWidth = globalGeometry.width();
int globalHeight = globalGeometry.height();
//拉伸后位置=当前位置+偏移量
//加入条件判断防止过度拉伸
switch (mousePressArea) {
case PosLeftTop:
if (minWidth <= (globalWidth - offsetX))
globalGeometry.setLeft(globalGeometry.left() + offsetX);
if (minHeight <= (globalHeight - offsetY))
globalGeometry.setTop(globalGeometry.top() + offsetY);
//globalGeometry.setTopLeft(globalGeometry.topLeft() + offset);
break;
case PosTop:
if (minHeight <= (globalHeight - offsetY))
globalGeometry.setTop(globalGeometry.top() + offsetY);
break;
case PosRightTop:
if (minWidth <= (globalWidth + offsetX))
globalGeometry.setRight(globalGeometry.right() + offsetX);
if (minHeight <= (globalHeight - offsetY))
globalGeometry.setTop(globalGeometry.top() + offsetY);
//globalGeometry.setTopRight(globalGeometry.topRight() + offset);
break;
case PosRight:
if (minWidth <= (globalWidth + offsetX))
globalGeometry.setRight(globalGeometry.right() + offsetX);
break;
case PosRightBottom:
if (minWidth <= (globalWidth + offsetX))
globalGeometry.setRight(globalGeometry.right() + offsetX);
if (minHeight <= globalHeight + offsetY)
globalGeometry.setBottom(globalGeometry.bottom() + offsetY);
//globalGeometry.setBottomRight(globalGeometry.bottomRight() + offset);
break;
case PosBottom:
if (minHeight <= (globalHeight + offsetY))
globalGeometry.setBottom(globalGeometry.bottom() + offsetY);
break;
case PosLeftBottom:
if (minWidth <= (globalWidth - offsetX))
globalGeometry.setLeft(globalGeometry.left() + offsetX);
if (minHeight <= (globalHeight + offsetY))
globalGeometry.setBottom(globalGeometry.bottom() + offsetY);
//globalGeometry.setBottomLeft(globalGeometry.bottomLeft() + offset);
break;
case PosLeft:
if (minWidth <= (globalWidth - offsetX))
globalGeometry.setLeft(globalGeometry.left() + offsetX);
break;
default:
break;
}
//设置拉伸后坐标并记录
setGeometry(globalGeometry);
lastPos = event->globalPos();
}
return QWidget::mouseMoveEvent(event);
}
void TitleBar::mouseReleaseEvent(QMouseEvent *event)
{
leftBtnPress = false;//左键释放
isMidArea = true;
setCursor(Qt::ArrowCursor);
return QWidget::mouseReleaseEvent(event);
}
4. 其他接口函数声明
/**
* @brief msetStyleSheet 通过文件设置全局样式
* @param styleSheet css/qss等样式文件
*/
static void msetStyleSheet(const QString &styleSheet);
/**
* @brief setMainWidget 设置主窗口
* @param widget 窗口指针
*/
void setMainWidget(QWidget *widget);
/**
* @brief setTitleBarText 设置标题
* @param text 文本
*/
void setTitleBarText(const QString &text);
/**
* @brief setTitleBarIcon 设置标题栏图标
* @param icon 图标
*/
void setTitleBarIcon(const QString &icon);
/**
* @brief setTitleBarStyleSheet 设置标题栏样式
* @param sheet 样式
*/
void setTitleBarStyleSheet(const QString &sheet);
/**
* @brief setTitleBarBackGround 设置标题栏背景颜色
* @param r 红色值
* @param g 绿色值
* @param b 蓝色值
*/
void setTitleBarBackGround(const int &r, const int &g, const int &b);
/**
* @brief setTitleTextFont 设置标题栏字体
* @param font 字体样式
*/
void setTitleTextFont(const QFont &font);
/**
* @brief setMarginSize 设置拉伸边距
* @param size 边距值
*/
void setMarginSize(const int &size);
[1]: qt 无边框窗口,可拖动,可拉伸
[2]: qt 无边框窗体拖动、拉伸