Qt 无边框窗口方案
一、方案整体概述
目标:去掉系统原生标题栏和边框,自定义界面样式,同时保留窗口拖拽、边缘缩放、最大/最小/关闭等完整功能。
整体架构
1. 界面层:Frame_Title(自定义标题栏,UI + 按钮交互)
2. 调度层:FramelessHelper(事件过滤器,统一接管鼠标事件)
3. 数据层:WidgetData(单窗口状态管理,执行拖拽/缩放/光标切换)
4. 计算层:CursorPosCalculator(鼠标位置判断,核心算法)
二、核心设计原理
1. 窗口基础:无边框标志设置
作用
移除系统自带的标题栏、边框、窗口装饰,为自定义界面铺路。
关键代码
cpp
setWindowFlags(Qt::Window
| Qt::FramelessWindowHint // 核心:无边框
| Qt::WindowSystemMenuHint
| Qt::WindowMinimizeButtonHint);
标志说明
Qt::FramelessWindowHint:真正实现无边框Qt::Window:保证控件是顶层窗口- 后两个:保留系统菜单与最小化能力
2. 计算核心:CursorPosCalculator(鼠标位置判断)
整个方案的"眼睛",负责判断鼠标在哪里。
核心功能
- 判断鼠标是否在窗口边缘(用于缩放)
- 判断鼠标是否在标题栏区域(用于拖拽)
- 判断鼠标是否在四角/四边(切换对应光标)
关键静态参数(全局生效)
cpp
static int m_nBorderWidth; // 边缘缩放宽度,默认 5px
static int m_nTitleHeight; // 标题栏拖拽高度,默认 30px
核心算法
通过全局鼠标坐标 与窗口全局矩形做差值计算:
cpp
m_bOnLeftEdge = (mouseX >= rect.x() && mouseX <= rect.x() + 边框宽度);
m_bOnTitle = (mouseY >= rect.y() && mouseY <= rect.y() + 标题栏高度);
3. 事件处理核心:WidgetData
每个窗口对应一个 WidgetData 实例,负责:
- 鼠标按下/移动/释放/离开的事件处理
- 窗口拖拽逻辑
- 窗口边缘缩放逻辑
- 光标样式自动切换
- 橡皮筋效果(可选)
核心方法
updateCursorShape():根据鼠标位置自动切换光标(箭头→水平缩放→斜向缩放)moveWidget():窗口拖拽resizeWidget():窗口边缘缩放- 四大鼠标事件处理:按下/移动/释放/离开
拖拽原理
- 鼠标按下标题栏 → 记录起始偏移
- 鼠标移动 → 窗口位置 = 鼠标位置 - 起始偏移
- 鼠标释放 → 结束拖拽
缩放原理
- 判断鼠标落在哪个边缘/角落
- 动态修改窗口矩形
setGeometry() - 限制最小宽高,防止缩到 0
4. 调度核心:FramelessHelper
**使用 Qt 事件过滤器 技术。
核心能力
- 给任意顶层窗口安装事件过滤器
- 拦截鼠标相关事件(按下/移动/释放/离开/悬停)
- 将事件分发给对应 WidgetData 处理
- 支持多窗口同时管理
关键机制
cpp
bool eventFilter(QObject *obj, QEvent *event) {
if(是鼠标事件) {
找到对应窗口的 WidgetData
data->handleWidgetEvent(event); // 交给数据层处理
return true; // 拦截事件,不传给原窗口
}
return false;
}
5. UI 核心:Frame_Title 自定义标题栏
替代系统标题栏。
功能清单
- 标题文字居中显示
- 最小化 / 最大化 / 关闭按钮
- 自定义图标、背景图片
- 跟随主窗口宽度自动适配
- 点击拖拽主窗口
关键实现
- 布局结构
- 标题标签居中
- 按钮组右对齐悬浮
- 互不干扰
- 大小同步
- 监听主窗口
resize事件 - 实时更新标题栏宽度
- 监听主窗口
- 样式完全自定义
- 支持背景图
border-image - 支持按钮图标
- 支持 QSS 样式表
- 支持背景图
三、完整使用方法(一步一照抄)
步骤 1:导入文件
将以下文件加入项目:
CursorPosCalculator.cpp/h
WidgetData.cpp/h
FramelessHelper.cpp/h
FramelessHelperPrivate.h
Frame_Title.cpp/h
.pro 文件自动包含即可。
步骤 2:主窗口构造函数初始化
cpp
// 1. 设置无边框标志
setWindowFlags(Qt::Window | Qt::FramelessWindowHint | ...);
// 2. 创建无边框助手
framelessHelper = new FramelessHelper(this);
framelessHelper->activateOn(this); // 激活当前窗口
framelessHelper->setWidgetMovable(true); // 可拖拽
framelessHelper->setWidgetResizable(true);//可缩放
// 3. 创建标题栏
titleBar = new Frame_Title(ui->centralwidget);
titleBar->setActive(this); // 绑定主窗口
步骤 3:布局重构
- 删除中央控件原有布局
- 创建垂直布局
- 顶部加入标题栏
- 下面放入原来的业务布局
cpp
QVBoxLayout *newLayout = new QVBoxLayout(centralWidget);
newLayout->setContentsMargins(0,0,0,0);
newLayout->addWidget(titleBar); // 标题栏在最顶上
newLayout->addLayout(你的业务布局);
步骤 4:配置标题栏样式
cpp
titleBar->setTitle("无边框窗口");
titleBar->setMinIcon("路径/min.png");
titleBar->setMaxIcon("路径/max.png");
titleBar->setCloseIcon("路径/close.png");
titleBar->setBackgroundImage("路径/background.png");
四、关键 API 速查表
| 类 | 函数 | 作用 |
|---|---|---|
| FramelessHelper | activateOn(widget) | 给窗口启用无边框 |
| setWidgetMovable(true) | 允许拖拽 | |
| setWidgetResizable(true) | 允许缩放 | |
| setTitleHeight(30) | 设置标题栏拖拽高度 | |
| Frame_Title | setActive(widget) | 绑定主窗口 |
| setTitle() | 设置标题 | |
| setMin/Max/CloseIcon | 设置按钮图标 | |
| setBackgroundImage | 设置背景图 |
无边框效果
