
一、为什么要使用无边框窗体?
在桌面应用开发中,系统自带的窗口边框和标题栏样式往往难以满足个性化UI设计需求。通过移除系统原生边框,开发者可以完全自定义窗口的外观------包括标题栏样式、窗口背景、圆角等,从而实现更美观、更统一的界面效果。
二、无边框窗体的核心问题
设置无边框很简单,只需一行代码:
cpp
this->setWindowFlags(Qt::FramelessWindowHint);
但问题随之而来------窗口无法再用鼠标拖拽移动了。这是因为系统默认的窗口拖拽逻辑依赖于原生标题栏,移除边框后,系统不再提供窗口移动功能。因此,我们需要手动实现窗口的拖拽移动功能。
三、实现原理
窗口拖拽移动的核心原理是矢量平移:
-
鼠标按下时,记录鼠标相对于屏幕的全局坐标 与窗口左上角在屏幕上的坐标 ,计算出两者的偏移量(diff_pos)
-
鼠标拖动过程中,用鼠标当前全局坐标 减去偏移量,得到窗口新的左上角坐标
-
调用
move()将窗口移动到新位置
公式如下:
cpp
窗口新位置 = 鼠标当前全局坐标 - 偏移量
偏移量 = 鼠标按下时全局坐标 - 窗口按下时左上角坐标
四、完整代码示例
方法一:重写鼠标事件(最常用)
widget.h
cpp
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QMouseEvent>
#include <QPoint>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
protected:
// 重写鼠标事件
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
private:
QPoint m_dragPosition; // 存储鼠标按下时的偏移量
bool m_bDragging; // 是否正在拖拽
};
#endif // WIDGET_H
widget.cpp
cpp
#include "widget.h"
#include <QApplication>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, m_bDragging(false)
{
// 设置无边框(同时保留最小化和最大化按钮功能)
setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint);
// 可选:设置窗口大小
resize(400, 300);
}
Widget::~Widget()
{
}
void Widget::mousePressEvent(QMouseEvent *event)
{
// 只有左键按下时才启动拖拽
if (event->button() == Qt::LeftButton) {
m_bDragging = true;
// 计算鼠标相对于窗口左上角的偏移量
// globalPos():鼠标在屏幕上的全局坐标
// pos():窗口左上角在屏幕上的坐标
m_dragPosition = event->globalPos() - this->pos();
event->accept();
}
}
void Widget::mouseMoveEvent(QMouseEvent *event)
{
// 只有在拖拽状态且左键按住时才移动窗口
if (m_bDragging && (event->buttons() & Qt::LeftButton)) {
// 用鼠标当前全局坐标减去偏移量,得到窗口新位置
move(event->globalPos() - m_dragPosition);
event->accept();
}
}
main.cpp
cpp
#include <QApplication>
#include "widget.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
方法二:使用事件过滤器
当窗口中有多个子控件时,使用事件过滤器可以统一处理所有控件的鼠标事件,避免在每个子控件中重复编写代码。
widget.h
cpp
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QPoint>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
protected:
bool eventFilter(QObject *watched, QEvent *event) override;
private:
QPoint m_dragPosition;
bool m_bDragging;
};
#endif // WIDGET_H
widget.cpp
cpp
#include "widget.h"
#include <QMouseEvent>
#include <QPushButton>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, m_bDragging(false)
{
setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint);
resize(400, 300);
// 安装事件过滤器到自身,监听所有鼠标事件
this->installEventFilter(this);
// 也可以安装到特定子控件上
QPushButton *btn = new QPushButton("拖动区域", this);
btn->setGeometry(50, 50, 100, 30);
btn->installEventFilter(this);
}
Widget::~Widget()
{
}
bool Widget::eventFilter(QObject *watched, QEvent *event)
{
// 只处理鼠标事件
if (event->type() == QEvent::MouseButtonPress) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
if (mouseEvent->button() == Qt::LeftButton) {
m_bDragging = true;
m_dragPosition = mouseEvent->globalPos() - this->pos();
return true;
}
}
else if (event->type() == QEvent::MouseMove) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
if (m_bDragging && (mouseEvent->buttons() & Qt::LeftButton)) {
move(mouseEvent->globalPos() - m_dragPosition);
return true;
}
}
else if (event->type() == QEvent::MouseButtonRelease) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
if (mouseEvent->button() == Qt::LeftButton) {
m_bDragging = false;
return true;
}
}
return QWidget::eventFilter(watched, event);
}
五、关键要点与注意事项
1. 使用 globalPos() 而非 pos()
在计算偏移量和移动窗口时,必须使用鼠标的全局坐标(相对于屏幕),而不是相对于窗口的本地坐标。如果使用本地坐标,窗口会出现跳跃和闪烁。
2. 判断左键按下
-
mousePressEvent中使用event->button() == Qt::LeftButton判断 -
mouseMoveEvent中使用event->buttons() & Qt::LeftButton判断(注意是buttons()复数形式)
3. 设置窗口标志的注意事项
cpp
// 仅无边框(窗口无法最小化/最大化)
setWindowFlags(Qt::FramelessWindowHint);
// 无边框 + 保留最小化/最大化按钮功能
setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint);
// 无边框 + 窗口置顶
setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
4. 性能优化
在拖动过程中,move() 函数会触发窗口重绘。如果界面内容复杂,频繁重绘可能导致卡顿或闪烁。可以考虑在拖动结束后再统一刷新,或使用双缓冲技术优化。
5. 最大化时的处理
当窗口处于最大化状态时,通常应禁止拖拽移动。可以在 mouseMoveEvent 中添加判断:
cpp
if (m_bDragging && (event->buttons() & Qt::LeftButton)) {
if (windowState() != Qt::WindowMaximized) {
move(event->globalPos() - m_dragPosition);
}
}
六、运行效果
完成以上代码后,编译运行,你会看到一个没有系统边框的窗口。在窗口任意位置按住鼠标左键并拖动,窗口会跟随鼠标平滑移动,效果与有标题栏的窗口完全一致。
七、扩展方向
掌握了基础的无边框窗口移动后,还可以进一步实现:
-
自定义标题栏:在窗口顶部添加自定义标题栏控件,包含图标、标题、最小化/最大化/关闭按钮
-
窗口边缘拉伸:在窗口边缘区域实现鼠标拖拽改变窗口大小
-
窗口阴影 :通过
QGraphicsDropShadowEffect为无边框窗口添加阴影效果