1.概要
需求来源
有时候,自定义的标题栏,在一定的尺寸上,显的太小,在工控车间,且是触摸屏的情况下,系统默认的那个关闭按钮往往不容点中,所以想自定义一个。
基本功能
1.最大化,最小化,还原
2.拖拽整个窗体
2.代码
2.1 代码
#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QMouseEvent>
#include <QStyle>
class CustomTitleBar : public QWidget {
public:
explicit CustomTitleBar(QWidget* parent = nullptr) : QWidget(parent) {
// 创建控制按钮
minimizeBtn = new QPushButton("-", this);
maximizeBtn = new QPushButton("□", this);
closeBtn = new QPushButton("×", this);
// 设置按钮样式(放大按钮)
setButtonStyle(minimizeBtn);
setButtonStyle(maximizeBtn);
setButtonStyle(closeBtn);
// 布局结构
QHBoxLayout* layout = new QHBoxLayout(this);
layout->addWidget(new QWidget, 1); // 左侧占位
layout->addWidget(minimizeBtn);
layout->addWidget(maximizeBtn);
layout->addWidget(closeBtn);
layout->setContentsMargins(8, 0, 8, 0);
layout->setSpacing(4);
layout->setAlignment(Qt::AlignVCenter);
// 固定标题栏高度
setFixedHeight(30);
// 设置标题栏背景色(新增功能)
setStyleSheet("background-color: #F0F0F0;"); // 灰白色背景
// 连接按钮信号
connect(minimizeBtn, &QPushButton::clicked, parent, &QWidget::showMinimized);
connect(closeBtn, &QPushButton::clicked, parent, &QWidget::close);
connect(maximizeBtn, &QPushButton::clicked, [this](){
if (parentWidget()->isMaximized()) {
parentWidget()->showNormal();
maximizeBtn->setText("□");
} else {
parentWidget()->showMaximized();
maximizeBtn->setText("❐");
}
});
}
private:
void setButtonStyle(QPushButton* btn) {
btn->setFixedSize(28, 28);
btn->setStyleSheet(
"QPushButton {"
" background: #DDDDDD;" // 按钮背景色
" color: #444;"
" border: 1px solid #CCCCCC;"
" border-radius: 4px;"
"}"
"QPushButton:hover {"
" background: #EEEEEE;"
"}"
"QPushButton:pressed {"
" background: #CCCCCC;"
"}"
);
}
QPushButton* minimizeBtn;
QPushButton* maximizeBtn;
QPushButton* closeBtn;
};
class FramelessWindow : public QWidget {
public:
FramelessWindow() {
// 隐藏默认标题栏
setWindowFlags(Qt::FramelessWindowHint | Qt::Window);
setAttribute(Qt::WA_TranslucentBackground);
// 创建布局
QVBoxLayout* layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
// 添加自定义标题栏
titleBar = new CustomTitleBar(this);
layout->addWidget(titleBar);
// 添加内容区域
content = new QWidget(this);
content->setStyleSheet("background: white;");
layout->addWidget(content);
// 窗口移动处理
setMouseTracking(true);
titleBar->installEventFilter(this);
}
protected:
bool eventFilter(QObject* obj, QEvent* event) override {
if (obj == titleBar && event->type() == QEvent::MouseButtonPress) {
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
if (mouseEvent->button() == Qt::LeftButton) {
dragPosition = mouseEvent->globalPosition().toPoint() - frameGeometry().topLeft();
event->accept();
}
}
return QWidget::eventFilter(obj, event);
}
void mouseMoveEvent(QMouseEvent* event) override {
if (event->buttons() & Qt::LeftButton) {
move(event->globalPosition().toPoint() - dragPosition);
event->accept();
}
}
private:
CustomTitleBar* titleBar;
QWidget* content;
QPoint dragPosition;
};
// 使用示例
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
FramelessWindow window;
window.resize(800, 600);
window.show();
return a.exec();
}
2.2 代码分析
2.1.1 处理标题栏的拖拽事件
2.2.2 最大化/恢复按钮分析
3.运行结果
4.关联链接
5.关联知识
1.标题栏布局解析
// 布局结构
QHBoxLayout* layout = new QHBoxLayout(this);
layout->addWidget(new QWidget, 1); // 左侧占位
layout->addWidget(minimizeBtn);
layout->addWidget(maximizeBtn);
layout->addWidget(closeBtn);
layout->setContentsMargins(8, 0, 8, 0);
layout->setSpacing(4);
layout->setAlignment(Qt::AlignVCenter);
这段代码使用Qt框架创建了一个水平布局(QHBoxLayout),主要用于实现窗口标题栏的按钮布局(最小化、最大化/还原、关闭按钮)。以下是逐层解析:
- 布局容器创建
QHBoxLayout* layout = new QHBoxLayout(this);
- 创建水平布局并绑定到当前窗口(
this
),使布局成为窗口的主布局
- 左侧弹性占位
layout->addWidget(new QWidget, 1); // 左侧占位
- 添加一个空白QWidget作为占位符
- 拉伸因子设为1,使其占据所有剩余空间
- 效果:将后续按钮推到布局最右侧
- 功能按钮排列
layout->addWidget(minimizeBtn);
layout->addWidget(maximizeBtn);
layout->addWidget(closeBtn);
- 依次添加三个标准窗口按钮
- 默认按添加顺序从左到右排列
- 实际显示效果为:
[占位空间] [最小化] [最大化] [关闭]
- 视觉参数设置
layout->setContentsMargins(8, 0, 8, 0); // 左右8px边距,上下无边距
layout->setSpacing(4); // 按钮间距4px
layout->setAlignment(Qt::AlignVCenter); // 垂直居中对齐
- 布局特性总结
- 典型应用场景:窗口标题栏右侧按钮组
- 布局效果:
[弹性空间] [最小化] [4px] [最大化] [4px] [关闭]
- 按钮始终垂直居中
- 按钮组与窗口边缘保持8px间距
- 按钮间距均匀且紧凑
- 扩展建议
- 可为按钮设置固定大小(
setFixedSize()
)保持一致性 - 添加鼠标悬停效果提升交互体验
- 最大化按钮可切换为还原按钮(通过
setCheckable(true)
实现状态切换)
这种布局模式广泛用于跨平台桌面应用开发,能自动适配不同分辨率和DPI设置,是现代GUI界面设计的标准实践。
2.eventFilter
eventFilter
是 Qt 框架中用于实现事件过滤的核心机制,允许你在不修改目标对象源码的情况下,拦截并处理其事件。以下是关键点解析和典型使用场景:
核心原理
- 事件传播路径:
- 事件首先发送到过滤器 → 过滤器决定是否处理/拦截 → 未处理则继续传递给目标对象
- 通过返回
true
阻止事件进一步传递,返回false
允许事件继续传播
- 实现步骤:
// 1. 子类化目标对象或创建过滤器对象 class MyFilter : public QObject { protected: bool eventFilter(QObject *obj, QEvent *event) override { if (event->type() == QEvent::MouseButtonPress) { qDebug() << "Mouse pressed on" << obj; return true; // 拦截事件,目标对象不再接收 } return QObject::eventFilter(obj, event); // 默认处理 } }; // 2. 安装过滤器到目标对象 QWidget *targetWidget = new QWidget; MyFilter *filter = new MyFilter(targetWidget); targetWidget->installEventFilter(filter);
代码分析
典型应用场景
-
全局快捷键监听:
bool eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event); if (keyEvent->key() == Qt::Key_F5 && keyEvent->modifiers() == Qt::NoModifier) { qDebug() << "F5 pressed!"; return true; // 阻止默认刷新行为 } } return false; }
-
监控输入框回车事件:
if (obj == lineEdit && event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event); if (keyEvent->key() == Qt::Key_Return) { qDebug() << "Enter pressed in lineEdit"; return true; // 阻止默认换行行为 } }
-
禁用控件交互:
if (obj == disabledButton) { if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::HoverEnter) { return true; // 完全屏蔽鼠标事件 } }
注意事项
- 性能优化:避免在过滤器中执行耗时操作,否则可能阻塞事件循环
- 对象生命周期:确保过滤器对象存活时间不短于目标对象
- 事件类型判断:优先使用
event->type()
而非直接类型转换,提高可读性 - 组合使用:可同时安装多个过滤器,按安装顺序依次处理
调试技巧
- 使用
qDebug() << event->type()
打印所有事件类型 - 通过
obj->metaObject()->className()
查看事件来源对象类型 - 结合 Qt Creator 的 Event Debugger 工具可视化事件流
事件过滤器是 Qt 中实现非侵入式事件处理的利器,特别适合需要动态监控/修改第三方控件行为的场景。合理使用可显著提升代码的模块化和可维护性。
3.installEventFilter
installEventFilter
是 Qt 中用于注册事件过滤器的核心方法,允许你为任意 QObject
派生类对象安装自定义事件处理器。以下是关键使用指南和注意事项:
基本用法
// 1. 创建过滤器对象(需继承自 QObject)
class MyEventFilter : public QObject {
protected:
bool eventFilter(QObject *watched, QEvent *event) override {
if (event->type() == QEvent::MouseMove) {
qDebug() << "Mouse moved over" << watched;
return true; // 拦截事件,阻止目标对象处理
}
return QObject::eventFilter(watched, event); // 默认处理
}
};
// 2. 安装过滤器到目标对象
QWidget *targetWidget = new QWidget;
MyEventFilter *filter = new MyEventFilter(targetWidget); // 通常设置父对象管理内存
targetWidget->installEventFilter(filter);
关键特性
- 事件传递链:
- 事件首先发送到过滤器 → 过滤器返回
true
表示已处理,事件停止传播 - 返回
false
则事件继续传递给目标对象的event()
方法
- 事件首先发送到过滤器 → 过滤器返回
- 多过滤器支持:
- 可为同一对象安装多个过滤器,按安装顺序依次处理
- 最后一个安装的过滤器最先接收事件
- 对象生命周期:
- 过滤器对象必须比目标对象存活时间长
- 推荐将目标对象设为过滤器的父对象,自动管理内存:
MyEventFilter *filter = new MyEventFilter(targetWidget); // targetWidget 销毁时自动删除 filter
典型应用场景
-
全局输入监控:
// 监控所有 QLineEdit 的回车事件 class LineEditFilter : public QObject { protected: bool eventFilter(QObject *obj, QEvent *event) override { if (event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event); if (keyEvent->key() == Qt::Key_Return && qobject_cast<QLineEdit*>(obj)) { qDebug() << "Enter pressed in line edit"; return true; // 阻止默认提交行为 } } return false; } };
-
禁用控件交互:
// 完全屏蔽按钮的鼠标和键盘事件 class DisablerFilter : public QObject { protected: bool eventFilter(QObject *obj, QEvent *event) override { if (obj == targetButton && (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::KeyPress)) { return true; // 阻止所有交互 } return false; } };
注意事项
- 性能影响:
- 避免在过滤器中执行耗时操作(如网络请求),否则会阻塞事件循环
- 使用
QCoreApplication::processEvents()
谨慎处理需要异步操作的情况
- 调试技巧:
- 打印事件类型和来源对象:
qDebug() << "Event type:" << event->type() << "from" << obj->metaObject()->className();
- 使用 Qt Creator 的 Event Debugger 工具可视化事件流
- 打印事件类型和来源对象:
- 卸载过滤器:
- 手动移除过滤器防止内存泄漏:
targetWidget->removeEventFilter(filter);
- 或通过父对象机制自动管理(推荐)
- 手动移除过滤器防止内存泄漏:
高级用法
-
动态过滤规则:
class DynamicFilter : public QObject { bool m_blockMouse = false; public: void setBlockMouse(bool block) { m_blockMouse = block; } protected: bool eventFilter(QObject *obj, QEvent *event) override { if (m_blockMouse && event->type() == QEvent::MouseMove) { return true; } return false; } };
-
跨线程过滤(需谨慎):
- 确保过滤器对象和目标对象在同一个线程
- 或使用信号槽进行线程间通信
通过合理使用 installEventFilter
,可以实现非侵入式的事件监控和修改,特别适合需要动态调整控件行为或实现全局交互逻辑的场景。正确管理对象生命周期是避免崩溃的关键。