本节课,来实现一个非常精简的无边框窗口,支持拖动4个边和4个角来缩放窗口
整体效果如下:
整体效果说明:
- 当鼠标位于
12346789区域时,鼠标形状如上,可以进行窗口缩放 - 当鼠标位于
5区域时,鼠标恢复正常形状如上,可以进行窗口的移动

1. 新建项目、去除边框
1.1 新建工程
使用纯代码布局,新建FramelessDemo项目时,取消勾选 “Generate form” ,如下:
项目创建完毕的目录结构,如下:
1.2 添加按钮,设置样式
在空白界面上,添加两个水平布局的按钮,并设置窗体的最小大小,设置窗体的背景色,如下:
#include <QDebug>
#include <QHBoxLayout>
#include <QPushButton>
Widget::Widget(QWidget* parent) : QWidget(parent)
{
this->setWindowTitle("明王讲QT | 第五章 事件、无边框窗口 | 简单无边框窗口");
this->resize(800, 500);
this->setMinimumSize(400, 300);
this->setStyleSheet("background:#303030"); // 设置背景色
QHBoxLayout* layout = new QHBoxLayout(this);
layout->setSpacing(10);
layout->setContentsMargins(10, 10, 10, 10);
QPushButton* btn1 = new QPushButton("确定");
QPushButton* btn2 = new QPushButton("取消");
layout->addWidget(btn1);
layout->addWidget(btn2);
QString style = R"(
QPushButton {
background-color: rgb(64, 64, 64);
font:20px "Microsoft YaHei";
color:rgb(200,200,200);
border: 1px solid #707070;
border-radius: 5px;
padding: 5px;
}
QPushButton:hover {
background-color: rgb(40, 40, 40);
}
QPushButton:pressed {
background-color: rgb(64, 64, 64);
}
)";
btn1->setStyleSheet(style);
btn2->setStyleSheet(style);
connect(btn1, &QPushButton::clicked, this, [=]() { qDebug() << btn1->text(); });
connect(btn2, &QPushButton::clicked, this, [=]() { qDebug() << btn2->text(); });
}
此时,运行程序:
1.3 去除标题栏
自带的标题栏很丑,在构造中,添加一条语句即可去除,如下:
Widget::Widget(QWidget* parent) : QWidget(parent)
{
this->setWindowTitle("明王讲QT | 第五章 事件、无边框窗口 | 简单无边框窗口");
this->resize(800, 500);
this->setStyleSheet("background:#303030"); // 设置背景色
// 去除标题栏
setWindowFlags(Qt::FramelessWindowHint | Qt::WindowSystemMenuHint);
}
1.4 右键退出
没有了标题栏,就无法点击右上角的 “关闭” 按钮来退出程序了。
接下来增加一个右键退出的功能
首先,来到widget.h中,声明几个鼠标事件函数并空实现,如下:
class Widget : public QWidget {
protected:
void mousePressEvent(QMouseEvent* event);
void mouseMoveEvent(QMouseEvent* event);
void mouseReleaseEvent(QMouseEvent* event);
};
然后,来到widget.cpp的构造中,创建菜单,如下:
Widget::Widget(QWidget* parent) : QWidget(parent)
{
// ...
// 创建右键菜单
menu = new QMenu(this);
QAction* action = new QAction("退出程序", this);
connect(action, &QAction::triggered, this, [=]() { this->close(); });
menu->addAction(action);
// 设置菜单的样式表
menu->setStyleSheet(R"(
QMenu {
background-color: rgb(120, 120, 120);
}
QMenu::item:selected {
background-color: rgb(40, 40, 40);
}
)");
}
然后,来到widget.cpp 中,实现mousePressEvent()函数,如下:
#include <QMouseEvent>
void Widget::mousePressEvent(QMouseEvent* event)
{
switch (event->button()) {
case Qt::RightButton:
menu->exec(event->globalPos()); // 在鼠标右键点击的位置显示菜单
break;
}
QWidget::mousePressEvent(event);
}
此时,点击右键就可以直接退出程序
2. 拖动窗口
由于没有了标题栏,无法移动窗口。接下来就实现窗口的移动
首先,在widget.h中,声明需要的成员变量和成员函数:
// 针对9个区域,定义了一个枚举类型
enum Location { TOP, BOTTOM, LEFT, RIGHT, TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT, CENTER };
class Widget : public QWidget {
protected:
void mousePressEvent(QMouseEvent* event);
void mouseMoveEvent(QMouseEvent* event);
void mouseReleaseEvent(QMouseEvent* event);
private:
bool isLeftPressed = false; // 鼠标左键是否按下
QPoint offset; // 记录鼠标按下的位置
Location location;
};
然后,来到widget.cpp中,修改鼠标按下、移动和释放的3个事件处理函数,如下:
void Widget::mousePressEvent(QMouseEvent* event)
{
switch (event->button()) {
case Qt::LeftButton:
isLeftPressed = true;
offset = event->globalPos() - this->frameGeometry().topLeft();
break;
case Qt::RightButton:
menu->exec(event->globalPos()); // 在鼠标右键点击的位置显示菜单
break;
}
QWidget::mousePressEvent(event);
}
void Widget::mouseMoveEvent(QMouseEvent* event)
{
QPoint globalPos = event->globalPos();
if (isLeftPressed) {
this->move(globalPos - offset);
}
}
void Widget::mouseReleaseEvent(QMouseEvent* event)
{
if (event->button() == Qt::LeftButton) {
isLeftPressed = false;
}
}
3. 边界处的光标形状
接下来实现鼠标在边界处按下时,可以缩放窗口的功能。
不过本节先来实现当鼠标移动到边界时,改变鼠标的形状的功能。
首先,声明并实现setCursorShape()函数,如下:
// widget.h
// 定义一个间距
#define PADDING 5
class Widget : public QWidget {
private:
void setCursorShape(const QPoint& cursorPoint);
};
// widget.cpp
void Widget::setCursorShape(const QPoint& cursorPoint)
{
QRect rect = this->rect();
QPoint topLeft = mapToGlobal(rect.topLeft());
QPoint bottomRight = mapToGlobal(rect.bottomRight());
int x = cursorPoint.x();
int y = cursorPoint.y();
if (x >= topLeft.x() && x <= topLeft.x() + PADDING && y >= topLeft.y() && y <= topLeft.y() + PADDING) {
// 左上角
location = TOP_LEFT;
this->setCursor(QCursor(Qt::SizeFDiagCursor));
} else if (x <= bottomRight.x() && x >= bottomRight.x() - PADDING && y <= bottomRight.y() && y >= bottomRight.y() - PADDING) {
// 右下角
location = BOTTOM_RIGHT;
this->setCursor(QCursor(Qt::SizeFDiagCursor));
} else if (x >= topLeft.x() && x <= topLeft.x() + PADDING && y <= bottomRight.y() && y >= bottomRight.y() - PADDING) {
//左下角
location = BOTTOM_LEFT;
this->setCursor(QCursor(Qt::SizeBDiagCursor));
} else if (x <= bottomRight.x() && x >= bottomRight.x() - PADDING && y >= topLeft.y() && y <= topLeft.y() + PADDING) {
// 右上角
location = TOP_RIGHT;
this->setCursor(QCursor(Qt::SizeBDiagCursor));
} else if (x >= topLeft.x() && x <= topLeft.x() + PADDING) {
// 左边
location = LEFT;
this->setCursor(QCursor(Qt::SizeHorCursor));
} else if (x <= bottomRight.x() && x >= bottomRight.x() - PADDING) {
// 右边
location = RIGHT;
this->setCursor(QCursor(Qt::SizeHorCursor));
} else if (y >= topLeft.y() && y <= topLeft.y() + PADDING) {
// 上边
location = TOP;
this->setCursor(QCursor(Qt::SizeVerCursor));
} else if (y <= bottomRight.y() && y >= bottomRight.y() - PADDING) {
// 下边
location = BOTTOM;
this->setCursor(QCursor(Qt::SizeVerCursor));
} else {
// 默认
location = CENTER;
this->setCursor(QCursor(Qt::ArrowCursor));
}
}
然后,来到widget.cpp中修改mouseMoveEvent(),如下:
void Widget::mouseMoveEvent(QMouseEvent* event)
{
QPoint globalPos = event->globalPos();
// 1. 左键未按下:则设置光标形状并返回
if (!isLeftPressed) {
this->setCursorShape(globalPos);
return;
}
// 2. 左键按下:在CENTER位置按下,移动窗口并返回
if (location == CENTER) {
move(globalPos - offset);
return;
}
}
最后,不要忘记来到widget.h的构造中,开启鼠标跟踪,如下:
Widget::Widget(QWidget* parent) : QWidget(parent)
{
// 去除标题栏
setWindowFlags(Qt::FramelessWindowHint | Qt::WindowSystemMenuHint);
// 开启鼠标跟踪
this->setMouseTracking(true);
}
3. 实现窗口缩放
只需接着修改mouseMoveEvent(),增加以下代码:
void Widget::mouseMoveEvent(QMouseEvent* event)
{
// ...
// 3. 左键按下:鼠标在 边缘 位置按下
QRect rect = this->rect();
QPoint topLeft = mapToGlobal(rect.topLeft());
QPoint bottomRight = mapToGlobal(rect.bottomRight());
QRect rMove(topLeft, bottomRight);
switch (location) {
case TOP:
// 如果不加if判断,则窗口高度达到最小高度后,会被鼠标 "向下推走"
if (bottomRight.y() - globalPos.y() > this->minimumHeight()) {
rMove.setY(globalPos.y());
}
break;
case BOTTOM:
rMove.setHeight(globalPos.y() - topLeft.y());
break;
case LEFT:
// 如果不加if判断,则窗口高度达到最小宽度后,会被鼠标 "向右推走"
if (bottomRight.x() - globalPos.x() > this->minimumWidth()) {
rMove.setX(globalPos.x());
}
break;
case RIGHT:
rMove.setWidth(globalPos.x() - topLeft.x());
break;
case TOP_LEFT:
// 如果不加if判断,则窗口达到最小宽高后,会被鼠标 "向右下推走"
if (bottomRight.x() - globalPos.x() > this->minimumWidth()) {
rMove.setX(globalPos.x());
}
if (bottomRight.y() - globalPos.y() > this->minimumHeight()) {
rMove.setY(globalPos.y());
}
break;
case TOP_RIGHT:
rMove.setWidth(globalPos.x() - topLeft.x());
// 如果不加if判断,则窗口达到最小宽高后,会被鼠标 "向下推走"
if (bottomRight.y() - globalPos.y() > this->minimumHeight()) {
rMove.setY(globalPos.y());
}
break;
case BOTTOM_LEFT:
// 如果不加if判断,则窗口达到最小宽高后,会被鼠标 "向右推走"
if (bottomRight.x() - globalPos.x() > this->minimumWidth()) {
rMove.setX(globalPos.x());
}
rMove.setHeight(globalPos.y() - topLeft.y());
break;
case BOTTOM_RIGHT:
rMove.setWidth(globalPos.x() - topLeft.x());
rMove.setHeight(globalPos.y() - topLeft.y());
break;
default:
break;
}
this->setGeometry(rMove);
}






