解码Qt事件处理与自定义绘图
Qt事件核心概述
事件的本质
Qt是事件驱动的编程框架,程序的所有交互行为(如点击鼠标、按下键盘、窗口重绘)均由事件触发。QEvent是所有事件类的基类,封装了事件的类型、状态等核心信息,贯穿程序整个生命周期。
常见事件类型
| 事件类型 | 触发场景 |
|---|---|
| 键盘事件 | 键盘按键按下/松开 |
| 鼠标事件 | 鼠标移动、按键按下/松开、双击、滚轮滚动 |
| 绘图事件 | 窗口显示/隐藏、被遮挡后恢复、调用update()/repaint()时 |
| 拖放事件 | 鼠标拖拽控件/数据并释放 |
| 定时器事件 | 调用startTimer()后指定时间到达时 |
| 进入/离开事件 | 鼠标指针移入/移出Widget区域 |
| 移动事件 | Widget的位置(x/y坐标)发生改变时 |
| 显示/隐藏事件 | Widget调用show()/hide()或被父窗口控制显示/隐藏时 |
| 焦点事件 | Widget获取/失去焦点(如点击输入框、切换窗口) |
| 自定义事件 | 程序内部自定义的业务事件(如数据加载完成、状态变更) |
事件调度流程(核心)
- 程序启动后创建
QApplication对象(Qt应用唯一入口),该对象初始化事件循环(Event Loop) 并进入等待状态; - 用户操作(如点击鼠标)或系统触发(如定时器到期)生成事件,事件被封装为
QEvent子类对象并加入事件队列; - 事件循环从队列中取出事件,根据事件类型和目标对象(QObject子类)分发事件;
- 目标对象通过重写事件处理函数响应事件,处理完成后事件自动销毁,事件循环继续处理下一个事件;
- 事件处理过程中可能生成新事件(如点击按钮弹出对话框),新事件会加入队列等待处理;
- 程序退出时(如调用
qApp->quit()),事件循环终止,程序结束。
事件处理核心接口
class Q_CORE_EXPORT QObject
{
public:
/**
* @brief 事件分发核心函数,所有事件都会先经过此函数
* @param event 待处理的事件对象(QEvent子类,如QMouseEvent、QKeyEvent)
* @return bool 返回true表示事件已处理,不再向下传递;返回false表示未处理,交由父类处理
* @note 子类重写时,未处理的事件必须调用父类的event(),否则会破坏事件传递逻辑
*/
virtual bool event(QEvent *event);
/**
* @brief 事件过滤器函数,用于拦截目标对象的事件
* @param watched 被监视的目标对象(如某个输入框、按钮)
* @param event 被拦截的事件对象
* @return bool 返回true表示拦截事件(不再传递给watched);返回false表示放行
* @note 使用前需通过`watched->installEventFilter(this)`安装过滤器,适用于全局拦截多个对象的事件
*/
virtual bool eventFilter(QObject *watched, QEvent *event);
};
class Q_WIDGETS_EXPORT QWidget : public QObject, public QPaintDevice
{
public:
/**
* @brief 重写QObject::event,扩展Widget专属事件的分发逻辑
* @param event 待处理的事件对象
* @return bool 事件处理状态(同QObject::event)
* @note QWidget会优先处理鼠标、键盘、绘图等可视化相关事件,再传递给父类
*/
bool event(QEvent *event) override;
};
鼠标事件(QMouseEvent/QWheelEvent)
核心概念
QMouseEvent:处理鼠标左键/右键/中键的按下、松开、双击、移动;QWheelEvent:单独处理鼠标滚轮滚动(不属于QMouseEvent);- 鼠标移动事件默认仅在按下鼠标按键时触发,调用
setMouseTracking(true)可开启“无按键移动跟踪”; - 鼠标按下时,Qt会自动“捕捉”鼠标轨迹,直到最后一个按键松开,期间父窗口持续接收鼠标事件。
核心处理函数(QWidget虚函数)
/**
* @brief 鼠标按下事件处理函数,鼠标按键按下时触发
* @param event 鼠标事件对象,包含按键类型、坐标、修饰键(如Ctrl/Shift)等信息
* @return void 无返回值
* @note event->button()返回按下的具体按键(Qt::LeftButton/RightButton/MidButton),仅单次按下有效;
* event->buttons()返回当前所有按下的按键(适用于多键同时按下,如左键+右键)
*/
virtual void mousePressEvent(QMouseEvent *event);
/**
* @brief 鼠标松开事件处理函数,鼠标按键松开时触发
* @param event 鼠标事件对象
* @return void 无返回值
* @note 与mousePressEvent配对,通常用于完成“点击”逻辑(按下+松开)
*/
virtual void mouseReleaseEvent(QMouseEvent *event);
/**
* @brief 鼠标双击事件处理函数,连续快速按下/松开同一按键时触发
* @param event 鼠标事件对象
* @return void 无返回值
* @note 双击的速度由系统设置(如Windows的“鼠标双击速度”)决定,Qt会自动识别
*/
virtual void mouseDoubleClickEvent(QMouseEvent *event);
/**
* @brief 鼠标移动事件处理函数,鼠标位置变化时触发
* @param event 鼠标事件对象,event->pos()返回相对于当前Widget的坐标,event->globalPos()返回屏幕坐标
* @return void 无返回值
* @note 默认需按下按键才触发,调用setMouseTracking(true)后无按键也触发
*/
virtual void mouseMoveEvent(QMouseEvent *event);
/**
* @brief 鼠标滚轮事件处理函数,滚轮滚动时触发
* @param event 滚轮事件对象,Qt5.15+推荐使用angleDelta()(返回QPoint,x=水平滚轮,y=垂直滚轮),传统delta()返回整数值(正向上/负向下,每格120)
* @return void 无返回值
*/
virtual void wheelEvent(QWheelEvent *event);
/**
* @brief 鼠标进入Widget区域事件处理函数
* @param event 通用事件对象(无额外鼠标参数)
* @return void 无返回值
*/
virtual void enterEvent(QEvent *event);
/**
* @brief 鼠标离开Widget区域事件处理函数
* @param event 通用事件对象
* @return void 无返回值
*/
virtual void leaveEvent(QEvent *event);
示例代码
#include <QMainWindow>
#include <QMouseEvent>
#include <QDebug>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
/**
* @brief 构造函数,初始化鼠标跟踪
* @param parent 父窗口指针,用于内存管理
*/
MainWindow(QWidget *parent = nullptr) : QMainWindow(parent)
{
setMouseTracking(true); // 开启无按键鼠标移动跟踪
}
protected:
void mousePressEvent(QMouseEvent *event) override
{
// 判断按下的按键类型
if (event->button() == Qt::LeftButton)
{
qDebug() << "鼠标左键按下,位置:" << event->pos();
}
else if (event->button() == Qt::RightButton)
{
qDebug() << "鼠标右键按下,位置:" << event->pos();
}
else if (event->button() == Qt::MidButton)
{
qDebug() << "鼠标中键按下,位置:" << event->pos();
}
}
void mouseReleaseEvent(QMouseEvent *event) override
{
qDebug() << "鼠标按键松开,按键:" << event->button() << ",位置:" << event->pos();
}
void mouseDoubleClickEvent(QMouseEvent *event) override
{
qDebug() << "鼠标双击,按键:" << event->button();
}
void mouseMoveEvent(QMouseEvent *event) override
{
static int count = 0;
qDebug() << "鼠标移动,次数:" << ++count << ",位置:" << event->pos();
}
void wheelEvent(QWheelEvent *event) override
{
// Qt5.15+推荐使用angleDelta()
QPoint delta = event->angleDelta();
if (delta.y() > 0)
{
qDebug() << "滚轮向上滚动,偏移量:" << delta.y();
}
else
{
qDebug() << "滚轮向下滚动,偏移量:" << delta.y();
}
}
void enterEvent(QEvent *event) override
{
qDebug() << "鼠标进入窗口";
}
void leaveEvent(QEvent *event) override
{
qDebug() << "鼠标离开窗口";
}
};

键盘事件(QKeyEvent)
核心概念
键盘事件触发于按键按下/松开,需重写keyPressEvent/keyReleaseEvent处理;
- 只有获得焦点的Widget才能接收键盘事件(可通过
setFocusPolicy(Qt::StrongFocus)设置焦点策略); QKeyEvent包含按键值、修饰键(Ctrl/Shift/Alt)、是否自动重复(长按按键触发)等信息。
核心处理函数
/**
* @brief 按键按下事件处理函数,按键按下时触发
* @param event 键盘事件对象,包含按键值、修饰键等信息
* @return void 无返回值
* @note event->key()返回按键枚举值(如Qt::Key_Left/Key_Right/Key_A);
* event->modifiers()返回修饰键(如Qt::ControlModifier/Ctrl、Qt::ShiftModifier/Shift);
* event->isAutoRepeat()判断是否为长按自动重复触发(如长按A键连续输入)
*/
virtual void keyPressEvent(QKeyEvent *event);
/**
* @brief 按键松开事件处理函数,按键松开时触发
* @param event 键盘事件对象
* @return void 无返回值
* @note 与keyPressEvent配对,适用于“按下触发动作,松开恢复”的场景(如游戏按键)
*/
virtual void keyReleaseEvent(QKeyEvent *event);
示例代码(窗口移动+快捷键)
#include <QWidget>
#include <QKeyEvent>
#include <QPoint>
#include <QDebug>
class MyWidget : public QWidget
{
Q_OBJECT
public:
/**
* @brief 构造函数,设置焦点策略
* @param parent 父部件指针
*/
MyWidget(QWidget *parent = nullptr) : QWidget(parent)
{
setFocusPolicy(Qt::StrongFocus); // 允许获取焦点,才能接收键盘事件
resize(400, 300);
}
protected:
void keyPressEvent(QKeyEvent *event) override
{
// 跳过自动重复的事件(长按按键)
if (event->isAutoRepeat())
{
return;
}
// 组合键:Ctrl+Q退出程序
if (event->key() == Qt::Key_Q && (event->modifiers() & Qt::ControlModifier))
{
qDebug() << "Ctrl+Q触发,程序退出";
close();
return;
}
// 方向键移动窗口
QPoint pos = this->pos(); // 获取当前窗口位置
if (event->key() == Qt::Key_Left)
{
pos.setX(pos.x() - 10); // 左移10像素
move(pos);
}
else if (event->key() == Qt::Key_Right)
{
pos.setX(pos.x() + 10); // 右移10像素
move(pos);
}
else if (event->key() == Qt::Key_Up)
{
pos.setY(pos.y() - 10); // 上移10像素
move(pos);
}
else if (event->key() == Qt::Key_Down)
{
pos.setY(pos.y() + 10); // 下移10像素
move(pos);
}
else
{
// 未处理的事件交给父类
QWidget::keyPressEvent(event);
}
}
void keyReleaseEvent(QKeyEvent *event) override
{
if (event->isAutoRepeat())
{
return;
}
qDebug() << "按键松开:" << event->key() << "(枚举值)";
}
};

定时器事件(QTimerEvent)
核心概念
QTimerEvent是定时器触发的事件,底层基于startTimer()实现,轻量但功能简单;- 复杂定时场景推荐使用
QTimer(支持信号槽、单次触发、暂停等); - 每个定时器有唯一ID,通过
startTimer()创建,killTimer()销毁,析构时需手动停止避免内存泄漏。
核心接口与函数
/**
* @brief 启动定时器,创建一个定时触发的事件源
* @param interval 定时间隔(毫秒),如1000表示每1秒触发一次
* @return int 成功返回定时器ID(非负整数),失败返回-1
* @note 定时器ID是唯一标识,需保存用于后续停止定时器
*/
int QObject::startTimer(int interval);
/**
* @brief 停止指定ID的定时器,终止定时事件触发
* @param id 要停止的定时器ID(startTimer的返回值)
* @return void 无返回值
* @note 停止不存在的ID不会报错,建议停止前检查ID有效性
*/
void QObject::killTimer(int id);
/**
* @brief 定时器事件处理函数,定时间隔到达时触发
* @param event 定时器事件对象,event->timerId()返回触发的定时器ID
* @return void 无返回值
* @note 需重写此函数区分不同ID的定时器逻辑
*/
virtual void QObject::timerEvent(QTimerEvent *event);
示例代码(多定时器+动态启停)
#include <QObject>
#include <QTimerEvent>
#include <QDebug>
class TimerObject : public QObject
{
Q_OBJECT
public:
/**
* @brief 构造函数,启动两个定时器
* @param parent 父对象指针
*/
TimerObject(QObject *parent = nullptr) : QObject(parent)
{
// 启动两个定时器:1秒/次、5秒/次
m_timer1Id = startTimer(1000); // 1000ms = 1s
m_timer2Id = startTimer(5000); // 5000ms = 5s
qDebug() << "定时器启动,ID1:" << m_timer1Id << ",ID2:" << m_timer2Id;
}
/**
* @brief 析构函数,停止所有定时器
*/
~TimerObject() override
{
// 析构时停止所有定时器(避免内存泄漏)
if (m_timer1Id != -1)
{
killTimer(m_timer1Id);
}
if (m_timer2Id != -1)
{
killTimer(m_timer2Id);
}
}
protected:
void timerEvent(QTimerEvent *event) override
{
// 区分不同定时器ID
if (event->timerId() == m_timer1Id)
{
static int count = 0;
qDebug() << "1秒定时器触发,次数:" << ++count;
// 触发10次后停止该定时器
if (count >= 10)
{
killTimer(m_timer1Id);
m_timer1Id = -1; // 标记为已停止
qDebug() << "1秒定时器已停止";
}
}
else if (event->timerId() == m_timer2Id)
{
static int count = 0;
qDebug() << "5秒定时器触发,次数:" << ++count;
}
else
{
// 未知定时器ID,交给父类处理
QObject::timerEvent(event);
}
}
private:
int m_timer1Id = -1; // 1秒定时器ID,初始值-1表示未启动
int m_timer2Id = -1; // 5秒定时器ID
};
// 主函数调用示例
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
TimerObject timerObj;
return app.exec();
}

自定义事件
核心概念
- 自定义事件是
QEvent的子类,用于实现程序内部的“自定义信号”(比信号槽更灵活,支持事件队列、优先级); - 事件类型需使用
QEvent::User及以上(QEvent::User到QEvent::MaxUser为用户自定义范围); - 发送方式:
postEvent()(异步,加入事件队列)、sendEvent()(同步,立即处理)。
核心接口与完整示例
#include <QApplication>
#include <QWidget>
#include <QEvent>
#include <QDebug>
/**
* @brief 自定义事件类,继承QEvent并指定唯一事件类型
* @note 事件类型需大于等于QEvent::User(推荐QEvent::User + 自定义偏移,避免冲突)
*/
class CustomEvent : public QEvent
{
public:
/**
* @brief 自定义事件构造函数
* @param data 自定义数据(示例:整数型业务数据)
* @note 事件类型固定为QEvent::User + 1,需保证全局唯一
*/
CustomEvent(int data) : QEvent(Type(QEvent::User + 1)), m_data(data) {}
/**
* @brief 获取自定义事件的业务数据
* @return int 存储的整数数据
*/
int getData() const { return m_data; }
private:
int m_data; // 自定义业务数据
};
/**
* @brief 事件接收者类,重写event()或customEvent()处理自定义事件
*/
class EventReceiver : public QWidget
{
Q_OBJECT
public:
EventReceiver(QWidget *parent = nullptr) : QWidget(parent) {}
protected:
/**
* @brief 方式1:重写event()函数处理自定义事件(通用方式)
* @param event 待处理的事件对象
* @return bool 事件是否被处理
*/
bool event(QEvent *event) override
{
// 判断事件类型是否为自定义事件
if (event->type() == QEvent::User + 1)
{
CustomEvent *customEvent = dynamic_cast<CustomEvent*>(event);
if (customEvent)
{
qDebug() << "通过event()接收自定义事件,数据:" << customEvent->getData();
}
return true; // 事件已处理,不再传递
}
// 其他事件交给父类处理
return QWidget::event(event);
}
/**
* @brief 方式2:重写customEvent()函数(Qt提供的自定义事件专用处理函数)
* @param event 待处理的自定义事件对象
* @return void 无返回值
* @note 仅处理类型为QEvent::User及以上的事件,无需手动判断类型
*/
void customEvent(QEvent *event) override
{
CustomEvent *customEvent = dynamic_cast<CustomEvent*>(event);
if (customEvent)
{
qDebug() << "通过customEvent()接收自定义事件,数据:" << customEvent->getData();
}
// 调用父类函数,确保事件传递(可选)
QWidget::customEvent(event);
}
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
/**
* @brief 创建事件接收者对象
* @note EventReceiver是自定义的QWidget子类,重写了event()和customEvent()函数,用于处理自定义事件;
* 该对象创建在栈上,生命周期由作用域管理(main函数结束时自动析构);
* 调用show()后,对象成为可视化Widget,进入Qt的事件循环体系,具备接收和处理事件的能力;
* 只有继承QObject的类(QWidget是QObject子类)才能接收Qt事件,非QObject类无法处理事件。
*/
EventReceiver receiver;
receiver.show();
// 方式1:异步发送事件(推荐)
CustomEvent *asyncEvent = new CustomEvent(100); // 堆上创建,Qt自动销毁
/**
* @brief 异步发送事件(核心函数)
* @param receiver 事件接收对象(必须是QObject子类,如EventReceiver、QWidget等),事件最终会分发到该对象的event()函数
* @param event 待发送的事件对象(需堆上new创建,Qt会在事件处理完成后自动销毁,无需手动delete)
* @param priority 事件优先级,可选值:
* - Qt::HighEventPriority(高优先级,优先处理)
* - Qt::NormalEventPriority(默认,常规优先级)
* - Qt::LowEventPriority(低优先级,最后处理)
* @return void 无返回值
* @note 1. 异步特性:事件不会立即处理,而是加入Qt的全局事件队列,等待事件循环轮询处理;
* 2. 内存安全:事件对象必须堆上创建(new),Qt内部会接管内存,处理完后自动delete,避免内存泄漏;
* 3. 线程安全:可跨线程发送事件,Qt会自动将事件投递到接收者所在线程的事件队列;
* 4. 适用场景:非紧急事件(如自定义业务事件、UI更新事件),避免阻塞主线程。
*/
QCoreApplication::postEvent(&receiver, asyncEvent, Qt::HighEventPriority);
// 方式2:同步发送事件
CustomEvent syncEvent(200); // 栈上创建,无需手动销毁
/**
* @brief 同步发送事件(核心函数)
* @param receiver 事件接收对象(QObject子类),事件会立即分发到该对象的event()函数
* @param event 待发送的事件对象(推荐栈上创建,无需手动销毁,函数返回后可复用或自动析构)
* @return bool 事件是否被接收者处理(即receiver的event()函数返回true表示已处理,false表示未处理)
* @note 1. 同步特性:函数调用后会立即触发receiver的event()函数,直到事件处理完成才返回;
* 2. 内存安全:事件对象推荐栈上创建(如示例中的syncEvent),若堆上创建需手动delete,Qt不会接管;
* 3. 无优先级:同步事件跳过事件队列,直接处理,无视优先级设置;
* 4. 线程限制:若接收者在其他线程,不能跨线程调用sendEvent,会导致未定义行为(需用postEvent);
* 5. 适用场景:紧急事件(如必须立即响应的系统事件),但需避免在主线程同步发送耗时事件(阻塞UI)。
*/
QCoreApplication::sendEvent(&receiver, &syncEvent);
return app.exec();
}

关键说明
postEvent():事件对象需用new创建,Qt会在事件处理完成后自动销毁,避免内存泄漏;sendEvent():事件对象建议用栈创建,同步调用后不会自动销毁,手动delete会崩溃;- 优先级:高优先级事件会先于低优先级事件被处理,适用于紧急业务场景(如系统告警)。
绘图事件(QPaintEvent)与QPainter
核心概念
QPaintEvent:触发于Widget需要重绘时(如显示、被遮挡、调用update()),必须重写paintEvent()处理;QPainter:Qt绘图核心类,支持绘制图形、文本、图像,提供画笔(QPen)、画刷(QBrush)、坐标变换等功能;- 绘图流程:创建
QPainter→ 设置绘图属性(画笔、画刷) → 调用绘制函数 → 结束绘图(自动或手动end())。
QPainter常用接口
#include <QPainter>
#include <QPen>
#include <QBrush>
#include <QFont>
#include <QImage>
/**
* @brief QPainter核心接口示例(结合paintEvent使用)
*/
void paintEvent(QPaintEvent *event) override
{
Q_UNUSED(event);
QPainter painter(this); // 创建绘图对象,绑定当前Widget
// 1. 基础设置:抗锯齿(提升绘制质量)
painter.setRenderHint(QPainter::Antialiasing, true);
// 2. 画笔设置(绘制轮廓、线条)
QPen pen;
pen.setColor(Qt::red); // 颜色
pen.setWidth(2); // 线宽
pen.setStyle(Qt::DashLine); // 线型(实线、虚线等)
painter.setPen(pen);
// 3. 画刷设置(填充图形)
QBrush brush;
brush.setColor(Qt::yellow); // 填充颜色
brush.setStyle(Qt::SolidPattern); // 填充样式(实心、渐变等)
painter.setBrush(brush);
// 4. 绘制基础图形
painter.drawRect(10, 10, 200, 100); // 矩形(x,y,宽,高)
painter.drawEllipse(250, 10, 100, 100); // 椭圆(x,y,宽,高——在矩形内)
painter.drawLine(10, 150, 350, 150); // 直线(起点x,y → 终点x,y)
// 5. 绘制文本
QFont font("Arial", 16, QFont::Bold);
painter.setFont(font);
painter.setPen(Qt::blue);
painter.drawText(QRect(10, 180, 350, 50), Qt::AlignCenter, "Qt绘图示例");
// 6. 绘制图像
QImage image("test.png"); // 加载图像(需确保路径正确)
if (!image.isNull())
{
painter.drawImage(QPoint(10, 250), image.scaled(100, 100)); // 缩放绘制
}
// 7. 坐标变换(平移、旋转、缩放)
painter.save(); // 保存当前绘图状态
painter.translate(200, 300); // 平移坐标系(原点移至(200,300))
painter.rotate(45); // 旋转45度
painter.scale(0.8, 0.8); // 缩放0.8倍
painter.drawRect(-50, -50, 100, 100); // 基于新坐标系绘制
painter.restore(); // 恢复之前的绘图状态
// 8. 绘制路径(复杂图形)
QPainterPath path;
path.moveTo(300, 250);
path.lineTo(350, 300);
path.lineTo(300, 350);
path.closeSubpath(); // 闭合路径
painter.setBrush(Qt::green);
painter.drawPath(path);
}
完整绘图示例(自定义绘图Widget)
#include <QWidget>
#include <QPainter>
#include <QPen>
#include <QBrush>
#include <QFont>
class PaintWidget : public QWidget
{
Q_OBJECT
public:
PaintWidget(QWidget *parent = nullptr) : QWidget(parent)
{
resize(400, 400);
setWindowTitle("Qt绘图示例");
}
protected:
void paintEvent(QPaintEvent *event) override
{
Q_UNUSED(event);
QPainter painter(this);
// 1. 绘制带边框的矩形
painter.setPen(QPen(Qt::red, 2));
painter.setBrush(Qt::yellow);
painter.drawRect(20, 20, 150, 100);
// 2. 绘制圆形(椭圆的特殊情况)
painter.setPen(QPen(Qt::blue, 1));
painter.setBrush(Qt::cyan);
painter.drawEllipse(200, 20, 100, 100);
// 3. 绘制文本
painter.setPen(Qt::black);
painter.setFont(QFont("宋体", 14));
painter.drawText(20, 150, "矩形区域");
painter.drawText(220, 150, "圆形区域");
// 4. 绘制三角形(通过路径)
QPainterPath path;
path.moveTo(50, 200);
path.lineTo(150, 200);
path.lineTo(100, 280);
path.closeSubpath();
painter.setPen(Qt::green);
painter.setBrush(Qt::lightGray);
painter.drawPath(path);
// 5. 绘制渐变背景(线性渐变)
QLinearGradient gradient(0, 300, width(), 300);
gradient.setColorAt(0, Qt::red);
gradient.setColorAt(0.5, Qt::yellow);
gradient.setColorAt(1, Qt::blue);
painter.setBrush(gradient);
painter.setPen(Qt::NoPen);
painter.drawRect(0, 300, width(), 100);
}
};
// 主函数调用
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
PaintWidget widget;
widget.show();
return app.exec();
}

自定义部件(Custom Widget)
核心思路
通过继承QWidget或其子类(如QPushButton),重写paintEvent()(绘图)、mousePressEvent()(交互)等函数,实现自定义功能的部件。
示例1:基础圆形部件
// MyCircleWidget.h
#include <QWidget>
#include <QPainter>
class MyCircleWidget : public QWidget
{
Q_OBJECT
public:
explicit MyCircleWidget(QWidget *parent = nullptr) : QWidget(parent)
{
setMinimumSize(100, 100); // 设置最小尺寸
}
protected:
void paintEvent(QPaintEvent *event) override
{
Q_UNUSED(event);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing); // 抗锯齿
// 绘制黄色圆形,蓝色边框
painter.setPen(QPen(Qt::blue, 3));
painter.setBrush(Qt::yellow);
// 调整圆形位置,与Widget边界有10像素间距
QRect circleRect = rect().adjusted(10, 10, -10, -10);
painter.drawEllipse(circleRect);
}
};
// 使用方式
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MyCircleWidget circleWidget;
circleWidget.resize(200, 200);
circleWidget.show();
return app.exec();
}

示例2:圆形进度条(带百分比)
#include <QWidget>
#include <QPainter>
#include <QTimer>
#include <QFont>
#include <QtMath>
class CircleProgressBar : public QWidget
{
Q_OBJECT
public:
explicit CircleProgressBar(QWidget *parent = nullptr) : QWidget(parent), m_progress(0)
{
setMinimumSize(150, 150);
// 模拟进度增长(实际使用时可通过setProgress()设置)
QTimer *timer = new QTimer(this);
// 修复:捕获列表添加 this 和 timer
connect(timer, &QTimer::timeout, this, [this, timer]() {
setProgress(m_progress + 1);
if (m_progress >= 100) timer->stop();
});
timer->start(50);
}
/**
* @brief 设置进度值
* @param progress 进度(0-100)
*/
void setProgress(int progress)
{
m_progress = qBound(0, progress, 100); // 限制在0-100
update(); // 触发重绘
}
protected:
void paintEvent(QPaintEvent *event) override
{
Q_UNUSED(event);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
int width = this->width();
int height = this->height();
int radius = qMin(width, height) / 2 - 10; // 圆半径(减去边框间距)
QPoint center(width / 2, height / 2); // 圆心
// 1. 绘制背景圆(灰色边框)
painter.setPen(QPen(Qt::lightGray, 8));
painter.setBrush(Qt::NoBrush);
painter.drawEllipse(center, radius, radius);
// 2. 绘制进度圆弧(蓝色)
painter.setPen(QPen(Qt::blue, 8));
// 圆弧范围:从-90度(顶部)开始,顺时针绘制m_progress%的圆弧
int startAngle = -90 * 16; // Qt中角度以1/16度为单位
int spanAngle = m_progress * 3.6 * 16; // 360度 * m_progress% = 进度角度
painter.drawArc(center.x() - radius, center.y() - radius,
radius * 2, radius * 2,
startAngle, spanAngle);
// 3. 绘制百分比文本
painter.setPen(Qt::black);
painter.setFont(QFont("Arial", 18, QFont::Bold));
QString text = QString("%1%").arg(m_progress);
painter.drawText(rect(), Qt::AlignCenter, text);
}
private:
int m_progress; // 进度值(0-100)
};
// 使用方式
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
CircleProgressBar progressBar;
progressBar.resize(200, 200);
progressBar.show();
return app.exec();
}

水波进度条(高级绘图示例)
实现原理
基于正弦函数生成波浪线,通过定时器更新波浪相位实现“波动”效果,结合圆形裁剪实现圆形水波进度条。
完整实现代码
#include <QWidget>
#include <QPainter>
#include <QPainterPath>
#include <QTimer>
#include <cmath>
class WaveProgressBar : public QWidget
{
Q_OBJECT
public:
explicit WaveProgressBar(QWidget *parent = nullptr) : QWidget(parent),
m_progress(0), m_waveOffset(0), m_waveAmplitude(8), m_waveFrequency(0.03)
{
setMinimumSize(150, 150);
// 定时器:每20ms更新一次波浪相位
m_timer = new QTimer(this);
connect(m_timer, &QTimer::timeout, this, [this]() {
m_waveOffset += 0.1;
update();
});
m_timer->start(20);
}
/**
* @brief 设置进度值
* @param progress 进度(0-100)
*/
void setProgress(int progress)
{
m_progress = qBound(0, progress, 100);
update();
}
protected:
void paintEvent(QPaintEvent *event) override
{
Q_UNUSED(event);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
int width = this->width();
int height = this->height();
QRect widgetRect = rect();
// 1. 绘制圆形背景(半透明浅蓝色)
painter.setPen(Qt::NoPen);
painter.setBrush(QColor(0, 180, 255, 50));
painter.drawEllipse(widgetRect.adjusted(5, 5, -5, -5));
// 2. 计算波浪参数
float waveHeight = height * m_progress / 100.0; // 波浪高度(进度对应)
float waveY = height - waveHeight; // 波浪起始Y坐标
// 3. 绘制波浪路径
QPainterPath wavePath;
wavePath.moveTo(0, height); // 左下角起点
wavePath.lineTo(0, waveY); // 左侧波浪起点
// 生成正弦波浪线
for (int x = 0; x <= width; x++)
{
// 正弦函数:y = A*sin(ωx + φ) + 基准Y坐标
float y = waveY + m_waveAmplitude * sin(m_waveFrequency * x + m_waveOffset);
wavePath.lineTo(x, y);
}
wavePath.lineTo(width, waveY); // 右侧波浪终点
wavePath.lineTo(width, height); // 右下角终点
wavePath.closeSubpath();
// 4. 裁剪波浪路径为圆形(与背景圆重合)
QPainterPath circlePath;
circlePath.addEllipse(widgetRect.adjusted(5, 5, -5, -5));
QPainterPath clippedPath = wavePath.intersected(circlePath); // 取交集
// 5. 绘制波浪填充色(半透明蓝色)
painter.setBrush(QColor(0, 180, 255, 150));
painter.drawPath(clippedPath);
// 6. 绘制进度文本
painter.setPen(Qt::white);
painter.setFont(QFont("Arial", 20, QFont::Bold));
QString text = QString("%1%").arg(m_progress);
painter.drawText(widgetRect, Qt::AlignCenter, text);
}
private:
int m_progress; // 进度值(0-100)
float m_waveOffset; // 波浪相位偏移(控制波动)
float m_waveAmplitude; // 波浪振幅(高度)
float m_waveFrequency; // 波浪频率(密度)
QTimer *m_timer; // 波动定时器
};
// 使用方式
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
WaveProgressBar progressBar;
progressBar.resize(200, 200);
progressBar.setProgress(60); // 设置初始进度60%
progressBar.show();
return app.exec();
}

Qt软键盘(自定义事件模拟输入)
核心需求
实现一个软键盘,点击按键时向当前焦点输入框发送键盘事件,模拟真实键盘输入。
完整实现代码
自定义按键类(KeyButton.h)
#ifndef KEYBUTTON_H
#define KEYBUTTON_H
#include <QPushButton>
#include <QKeyEvent>
#include <QApplication>
/**
* @brief 软键盘自定义按键类,点击时发送键盘事件
*/
class KeyButton : public QPushButton
{
Q_OBJECT
public:
explicit KeyButton(QWidget *parent = nullptr) : QPushButton(parent), m_key(-1)
{
initStyle();
}
explicit KeyButton(const QString &text, QWidget *parent = nullptr) : QPushButton(text, parent), m_key(-1)
{
initStyle();
}
/**
* @brief 设置按键对应的键盘键值(如Qt::Key_A、'a')
* @param key 键值(Qt::Key枚举或ASCII码)
*/
void setKey(int key) { m_key = key; }
signals:
/**
* @brief 按键点击信号,传递键值
* @param key 按键对应的键值
*/
void keyClicked(int key);
private slots:
/**
* @brief 按键点击槽函数,发送键盘事件到焦点控件
*/
void onKeyClicked()
{
// 如果未设置键值,从按钮文本获取(如文本为"a",则键值为'a')
if (m_key == -1 && !text().isEmpty())
{
m_key = text().at(0).toLatin1();
}
if (m_key != -1)
{
emit keyClicked(m_key);
// 创建键盘按下事件,发送给当前焦点控件
QKeyEvent *pressEvent = new QKeyEvent(QEvent::KeyPress, m_key, Qt::NoModifier, text());
QApplication::postEvent(QApplication::focusWidget(), pressEvent);
// 创建键盘松开事件(模拟完整按键流程)
QKeyEvent *releaseEvent = new QKeyEvent(QEvent::KeyRelease, m_key, Qt::NoModifier, text());
QApplication::postEvent(QApplication::focusWidget(), releaseEvent);
}
}
private:
/**
* @brief 初始化按键样式(去除焦点边框,设置大小)
*/
void initStyle()
{
setFocusPolicy(Qt::NoFocus); // 去除按键焦点(避免点击后焦点转移)
setFixedSize(60, 60); // 设置按键固定大小
setStyleSheet("QPushButton { font-size: 20px; }");
// 绑定点击信号到槽函数
connect(this, &QPushButton::clicked, this, &KeyButton::onKeyClicked);
}
private:
int m_key; // 按键对应的键盘键值
};
#endif // KEYBUTTON_H
软键盘主窗口(SoftKeyboard.h)
#ifndef SOFTKEYBOARD_H
#define SOFTKEYBOARD_H
#include <QWidget>
#include <QGridLayout>
#include "KeyButton.h"
/**
* @brief 软键盘主窗口,包含多个KeyButton
*/
class SoftKeyboard : public QWidget
{
Q_OBJECT
public:
explicit SoftKeyboard(QWidget *parent = nullptr) : QWidget(parent)
{
setWindowTitle("软键盘");
initLayout();
}
private:
/**
* @brief 初始化键盘布局(3x3按键示例)
*/
void initLayout()
{
QGridLayout *layout = new QGridLayout(this);
layout->setSpacing(10);
// 创建按键(示例:a、b、c、d、e、f、删除、清空、确认)
KeyButton *btnA = new KeyButton("a");
KeyButton *btnB = new KeyButton("b");
KeyButton *btnC = new KeyButton("c");
KeyButton *btnD = new KeyButton("d");
KeyButton *btnE = new KeyButton("e");
KeyButton *btnF = new KeyButton("f");
KeyButton *btnBackspace = new KeyButton("←");
btnBackspace->setKey(Qt::Key_Backspace); // 设置删除键对应的键值
KeyButton *btnClear = new KeyButton("清空");
connect(btnClear, &KeyButton::keyClicked, this, [this]() {
// 向焦点输入框发送清空信号(示例:QLineEdit)
QWidget *focusWidget = QApplication::focusWidget();
if (focusWidget)
{
QMetaObject::invokeMethod(focusWidget, "clear", Qt::QueuedConnection);
}
});
KeyButton *btnConfirm = new KeyButton("确认");
connect(btnConfirm, &KeyButton::keyClicked, this, [this]() {
qDebug() << "确认按钮点击";
// 可添加确认逻辑(如提交输入内容)
});
// 添加按键到布局
layout->addWidget(btnA, 0, 0);
layout->addWidget(btnB, 0, 1);
layout->addWidget(btnC, 0, 2);
layout->addWidget(btnD, 1, 0);
layout->addWidget(btnE, 1, 1);
layout->addWidget(btnF, 1, 2);
layout->addWidget(btnBackspace, 2, 0);
layout->addWidget(btnClear, 2, 1);
layout->addWidget(btnConfirm, 2, 2);
setLayout(layout);
resize(300, 300);
}
};
#endif // SOFTKEYBOARD_H
测试程序(main.cpp)
#include <QApplication>
#include <QWidget>
#include <QLineEdit>
#include <QVBoxLayout>
#include "SoftKeyboard.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
// 创建主窗口,包含输入框和软键盘
QWidget mainWidget;
mainWidget.setWindowTitle("软键盘测试");
QVBoxLayout *layout = new QVBoxLayout(&mainWidget);
// 添加三个输入框(测试焦点切换)
QLineEdit *edit1 = new QLineEdit;
QLineEdit *edit2 = new QLineEdit;
QLineEdit *edit3 = new QLineEdit;
edit1->setPlaceholderText("输入框1");
edit2->setPlaceholderText("输入框2");
edit3->setPlaceholderText("输入框3");
// 添加软键盘
SoftKeyboard *keyboard = new SoftKeyboard;
layout->addWidget(edit1);
layout->addWidget(edit2);
layout->addWidget(edit3);
layout->addWidget(keyboard);
mainWidget.resize(400, 500);
mainWidget.show();
return app.exec();
}

关键说明
- 自定义按键
KeyButton通过setFocusPolicy(Qt::NoFocus)去除焦点,避免点击后输入框失去焦点; - 点击按键时,通过
QApplication::postEvent()向当前焦点控件发送QKeyEvent,模拟真实键盘输入; - 支持焦点切换:点击不同输入框后,软键盘输入会自动指向当前焦点的输入框。
事件过滤器(Event Filter)
核心作用
拦截一个或多个QObject的事件,在事件到达目标对象前进行预处理(如修改、拦截、日志记录)。
完整示例(拦截输入框键盘事件)
#include <QApplication>
#include <QWidget>
#include <QLineEdit>
#include <QVBoxLayout>
#include <QEvent>
#include <QKeyEvent>
#include <QDebug>
/**
* @brief 事件过滤器类,拦截QLineEdit的键盘事件
*/
class InputFilter : public QObject
{
Q_OBJECT
public:
explicit InputFilter(QObject *parent = nullptr) : QObject(parent) {}
protected:
/**
* @brief 重写eventFilter()函数,拦截事件
* @param watched 被监视的对象(如QLineEdit)
* @param event 被拦截的事件
* @return bool true=拦截事件,false=放行事件
*/
bool eventFilter(QObject *watched, QEvent *event) override
{
// 仅拦截QLineEdit的键盘按下事件
if (watched->isWidgetType() && dynamic_cast<QLineEdit*>(watched) &&
event->type() == QEvent::KeyPress)
{
QKeyEvent *keyEvent = dynamic_cast<QKeyEvent*>(event);
if (keyEvent)
{
// 拦截数字键(禁止输入数字)
if (keyEvent->key() >= Qt::Key_0 && keyEvent->key() <= Qt::Key_9)
{
qDebug() << "拦截数字键:" << keyEvent->key();
return true; // 拦截事件,不传递给输入框
}
// 允许其他键(如字母、符号)
qDebug() << "放行键:" << keyEvent->key();
}
}
// 其他事件放行,交给原对象处理
return QObject::eventFilter(watched, event);
}
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QWidget mainWidget;
QVBoxLayout *layout = new QVBoxLayout(&mainWidget);
QLineEdit *edit1 = new QLineEdit;
QLineEdit *edit2 = new QLineEdit;
edit1->setPlaceholderText("禁止输入数字(输入框1)");
edit2->setPlaceholderText("禁止输入数字(输入框2)");
// 创建事件过滤器
InputFilter *filter = new InputFilter;
// 为两个输入框安装事件过滤器
edit1->installEventFilter(filter);
edit2->installEventFilter(filter);
layout->addWidget(edit1);
layout->addWidget(edit2);
mainWidget.resize(300, 150);
mainWidget.show();
return app.exec();
}
关键步骤
- 创建事件过滤器类,重写
eventFilter()函数; - 为目标对象(如QLineEdit)调用
installEventFilter()安装过滤器; - 在
eventFilter()中判断事件类型和目标对象,决定是否拦截; - 不需要时调用
removeEventFilter()移除过滤器。
总结
Qt事件处理是Qt应用的核心,涵盖鼠标、键盘、定时器、绘图等所有交互场景,关键要点:
- 事件通过事件循环分发,
QEvent是所有事件的基类; - 处理事件的三种方式:重写特定事件函数(如
mousePressEvent)、重写event()、使用事件过滤器; - 自定义事件适用于复杂业务场景,支持异步发送和优先级;
- QPainter是绘图核心,结合
paintEvent()可实现自定义部件和复杂绘图效果; - 软键盘、水波进度条等高级组件,本质是事件处理与绘图的结合。
- 掌握Qt事件处理机制,可灵活实现各类交互功能,适配桌面、嵌入式等不同平台的Qt应用开发。

浙公网安备 33010602011771号