事件类型详解:QEvent类、事件类型枚举、自定义事件类型
好,咱们接着聊事件类型。上一节我讲了事件是怎么产生、怎么分发的,那这一节咱们就深入看看事件本身——说白了,就是QEvent这个类到底长什么样,Qt里有哪些现成的事件类型,以及如果这些不够用,我们怎么自己造一个。
QEvent类:所有事件的祖宗
QEvent是Qt事件体系的基类。嗯,你可以把它想象成一个快递包裹的包装盒——不管里面装的是鼠标点击、键盘按键还是定时器消息,外面这个盒子都是一样的结构。
这个类里最重要的成员就两个:
- 事件类型(type):一个int值,标识这是啥事件
- 是否已处理(accepted):一个bool值,标记事件有没有被消费掉
我记得刚学Qt那会儿,总以为事件是个很复杂的东西。后来看了源码才发现,QEvent本身其实很轻量,核心代码也就几十行。它就是个带类型标记的基类,真正干活的是它的子类们。
核心要点:QEvent本身不存储具体的事件数据。比如鼠标位置、按键码这些,都在QMouseEvent、QKeyEvent这些子类里。QEvent只负责两件事——告诉你是谁,以及你有没有被处理。
事件类型枚举:Qt内置的“身份证号”
每个QEvent对象都有一个type()方法,返回一个QEvent::Type枚举值。这个枚举值就是事件的身份证号,Qt靠它来区分不同的事件。
我列几个最常用的,你感受一下:
| 枚举值 | 数值 | 说明 |
|---|---|---|
| QEvent::MouseButtonPress | 2 | 鼠标按下 |
| QEvent::MouseMove | 5 | 鼠标移动 |
| QEvent::KeyPress | 6 | 键盘按下 |
| QEvent::Paint | 12 | 重绘事件 |
| QEvent::Timer | 1 | 定时器触发 |
| QEvent::Close | 19 | 窗口关闭 |
你可能会问:为什么是2、5、6这些数字?其实没什么特殊含义,就是Qt内部按顺序分配的。不过有一点要注意——Qt保留了0到999这个范围给自己用,自定义事件必须从1000开始。
小技巧:在event()函数里做类型判断时,我习惯用switch而不是if-else链。因为事件类型是枚举值,switch编译出来是跳转表,性能更好。尤其是高频事件(比如鼠标移动),这点性能差异还是能感觉到的。
自定义事件类型:当内置的不够用
说实话,大部分场景下Qt内置的事件类型已经够用了。但如果你在做一个自定义控件,或者需要跨线程传递一些特殊消息,那就得自己定义事件了。
怎么做?三步走:
- 选一个类型ID:从QEvent::User(1000)开始往上加
- 继承QEvent:写一个子类,带上你的数据
- 用QCoreApplication::postEvent()发送:或者用sendEvent()同步发送
来看个例子。假设我在做一个视频播放器,需要自定义一个“播放进度更新”事件:
// 1. 定义事件类型ID
static const QEvent::Type MyProgressEventType =
static_cast<QEvent::Type>(QEvent::User + 1);
// 2. 自定义事件类
class MyProgressEvent : public QEvent
{
public:
MyProgressEvent(int progress)
: QEvent(MyProgressEventType), m_progress(progress) {}
int progress() const { return m_progress; }
private:
int m_progress;
};
// 3. 发送事件
void VideoPlayer::onProgressChanged(int value)
{
// 异步发送
QCoreApplication::postEvent(
m_progressWidget,
new MyProgressEvent(value));
}
这里有个坑,我曾经踩过——postEvent发送的事件,内存由Qt管理,千万别手动delete。而sendEvent是同步的,事件处理完才返回,所以可以传栈对象。两种方式各有适用场景,后面章节我会细讲。
注意:自定义事件类型ID不要冲突。如果你在多个模块里都用了QEvent::User + 1,那事件处理时就乱套了。我个人的做法是:每个模块从User + 100、User + 200这样分段分配,留出余量。
事件类型的判断与分发
事件到了event()函数里,Qt是怎么知道该调用哪个处理函数的?其实就是靠type()这个枚举值做switch-case。你看QWidget::event()的源码,里面就是一大串switch:
bool QWidget::event(QEvent *event)
{
switch (event->type()) {
case QEvent::MouseButtonPress:
// 调用mousePressEvent()
...
case QEvent::KeyPress:
// 调用keyPressEvent()
...
// 几十个case...
}
}
所以,当你重写event()函数时,本质上就是在Qt的switch之前插了一脚。你可以先处理自己的逻辑,没处理完再交给基类。这也是为什么event()比具体的xxxEvent()函数更底层、更灵活。
我画了一张图,帮你理清事件类型在整个事件处理流程中的位置:
从这张图你能看到,事件类型就是整个分发机制的“路标”。没有它,Qt根本不知道把事件往哪儿送。
几个实用建议
最后,分享几个我在项目中积累的经验:
- 能用内置类型就别自定义:Qt内置了100多种事件类型,覆盖了绝大多数场景。自定义事件意味着你要自己管理生命周期和分发逻辑,增加了复杂度。
- 自定义事件记得注册类型:如果你用QEvent::registerEventType()来获取ID,可以避免冲突。这个函数会从User开始自动分配一个未使用的ID。
- 调试时打印事件类型:我经常在event()函数开头加一行qDebug() << event->type(),看看实际收到了哪些事件。有时候你会发现一些意想不到的事件在乱飞。
一句话总结:QEvent是事件的骨架,事件类型是骨架上的标签。搞懂了这两样,你就掌握了Qt事件处理的“身份证系统”。后面不管遇到什么事件,你都能一眼看穿它的本质。
可可精品资料库