尽管 Qt 已经提供了很多事件,但对于更加千变万化的需求来说,有限的事件都是不够的。例如,我要支持一种新的设备,这个设备提供一种崭新的交互方式,那么,这种事件如何处理呢?所以,允许创建自己的事件 类型也就势在必行。即便是不说那种非常极端的例子,在多线程的程序中,自定义事件也是尤其有用。当然,事件也并不是局限在多线程中,它可以用在单线程的程序中,作为一种对象间通讯的机制。那么,为什么我需要使用事件,而不是信号槽呢?主要原因是,事件的分发既可以是同步的,又可以是异步的,而函数的调用或者说是槽的回调总是同步的。事件的另外一个好处是,它可以使用过滤器。
Qt 自定义事件很简单,同其它类库的使用很相似,都是要继承一个类进行扩展。在 Qt 中,你需要继承的类是QEvent
。
继承QEvent
类,最重要的是提供一个QEvent::Type
类型的参数,作为自定义事件的类型值。回忆一下,这个 type 是我们在处理事件时用于识别事件类型的代号。比如在event()
函数中,我们使用QEvent::type()
获得这个事件类型,然后与我们定义的实际类型对比。
QEvent::Type
是QEvent
定义的一个枚举。因此,我们可以传递一个 int 值。但是需要注意的是,我们的自定义事件类型不能和已经存在的 type 值重复,否则会有不可预料的错误发生。因为系统会将你新增加的事件当做系统事件进行派发和调用。在 Qt 中,系统保留 0 - 999 的值,也就是说,你的事件 type 要大于 999。这种数值当然非常难记,所以 Qt 定义了两个边界值:QEvent::User
和QEvent::MaxUser
。我们的自定义事件的 type 应该在这两个值的范围之间。其中,QEvent::User
的值是 1000,QEvent::MaxUser
的值是 65535。从这里知道,我们最多可以定义 64536 个事件。通过这两个枚举值,我们可以保证我们自己的事件类型不会覆盖系统定义的事件类型。但是,这样并不能保证自定义事件相互之间不会被覆盖。为了解决这个问题,Qt 提供了一个函数:registerEventType()
,用于自定义事件的注册。该函数签名如下:
static int QEvent::registerEventType ( int hint = -1 );
这个函数是 static 的,因此可以使用QEvent
类直接调用。函数接受一个 int 值,其默认值是 -1;函数返回值是向系统注册的新的 Type 类型的值。如果 hint 是合法的,也就是说这个 hint 不会发生任何覆盖(系统的以及其它自定义事件的),则会直接返回这个值;否则,系统会自动分配一个合法值并返回。因此,使用这个函数即可完成 type 值的指定。这个函数是线程安全的,不必另外添加同步。
我们可以在QEvent
子类中添加自己的事件所需要的数据,然后进行事件的发送。Qt 中提供了两种事件发送方式:
1.
static bool QCoreApplication::sendEvent(QObject *receiver, QEvent *event);
直接将event
事件发送给receiver
接受者,使用的是QCoreApplication::notify()
函数。函数返回值就是事件处理函数的返回值。在事件被发送的时候,event
对象并不会被销毁。通常我们会在栈上创建event
对象,例如:
QMouseEvent event(QEvent::MouseButtonPress, pos, 0, 0, 0); QApplication::sendEvent(mainWindow, &event);
2.
static void QCoreApplication::postEvent(QObject *receiver, QEvent *event);
将event
事件及其接受者receiver
一同追加到事件队列中,函数立即返回。
因为 post 事件队列会持有事件对象,并且在其 post 的时候将其 delete 掉,因此,我们必须在堆上创建event
对象。当对象被发送之后,再试图访问event
对象就会出现问题(因为 post 之后,event
对象就会被 delete)。
当控制权返回到主线程循环时,保存在事件队列中的所有事件都通过notify()
函数发送出去。
事件会根据 post 的顺序进行处理。如果你想要改变事件的处理顺序,可以考虑为其指定一个优先级。默认的优先级是Qt::NormalEventPriority
。
这个函数是线程安全的。
Qt 还提供了一个函数:
static void QCoreApplication::sendPostedEvents(QObject *receiver, int event_type);
这个函数的作用是,将事件队列中的接受者为receiver
,事件类似为 event_type 的所有事件立即发送给 receiver 进行处理。需要注意的是,来自窗口系统的事件并不由这个函数进行处理,而是processEvent()
。详细信息请参考 Qt API 手册。
现在,我们已经能够自定义事件对象,已经能够将事件发送出去,还剩下最后一步:处理自定义事件。处理自定义事件,同前面我们讲解的那些处理方法没有什么区别。我们可以重写QObject::customEvent()
函数,该函数接收一个QEvent
对象作为参数:
void QObject::customEvent(QEvent *event);
我们可以通过转换 event 对象类型来判断不同的事件:
void CustomWidget::customEvent(QEvent *event) { CustomEvent *customEvent = static_cast<CustomEvent *>(event); // ... }
当然,我们也可以在event()
函数中直接处理:
bool CustomWidget::event(QEvent *event) { if (event->type() == MyCustomEventType) { CustomEvent *myEvent = static_cast<CustomEvent *>(event); // processing... return true; } return QWidget::event(event); }
32 评论
😯 能不能讲讲udev->dbus->event的工作过程,嵌入式设备都是走的这种方案(dfb除外)
这个以后会考虑的,因为我对嵌入式不是很熟悉,所以一直没有这方面的计划
你好,看完这篇文章后我就想亲自实现一回,不过实在不知要如何开头...
LZ能出个示例吗?
简单的示例,你可以尝试实现比如数据改变之类的事件——虽然这种事件可以使用信号槽来实现。只要了解了具体过程,在实际应用中再详细设计就好了。
您好,能不能讲解一下 processEvent()。我做了个程序 有一个按钮和一个进度条,我想按下按钮进度条每一秒前进1%,但是我按右上角关闭键的时候,程序界面是退出了,进程还在等待循环结束才退出,这怎么办,按钮按下对应的槽函数如下 void F() {
QElapsedTimer t;
for (int i = 1; i setValue(i);
t.start();
while (t.elapsed() < 1000)
{
QCoreApplication::processEvents();
}
}
}
QElapsedTimer t;
for (int i = 1; i setValue(i);
t.start();
while (t.elapsed() < 1000)
{
QCoreApplication::processEvents();
}
}
for 那里怎么少了一段
具体不大清楚,不是不可以在点击关闭按钮的时候调用下 QApplication::quit() 槽?
”事件的分发既可以是同步的,又可以是异步的,而函数的调用或者说是槽的回调总是同步的。“这句话怎么理解?
同步就是调用者必须等到被调用者执行完毕之后才能继续执行;异步就是被调用者在被调用之后立刻返回给调用者,调用者继续执行。函数调用和槽的执行都是同步的,就是要等到被调用的函数或槽执行完毕,才返回给调用者继续执行,可以认为调用者被阻塞了。对于事件,使用 sendEvent 函数就是同步调用,使用 postEvent 就是异步调用。
你好,我写了一个事件监控类,
//事件监控者
class MyWatcher: public QObject
{
public:
bool eventFilter(QObject *watched, QEvent *event)
{
if(event->type() == MyCustomEventType)
{
qDebug()<<"I don't wanna filter MyEventType";
return false;
}
return QObject::eventFilter(watched,event);
}
};
该类可以正常输出I don't wanna filter MyEventType字串,可是
当在类前面加入Q_OBJECT宏后,该字串反倒不输出了,事件也不在继续传递了。请问Q_OBJECT不能用在事件相关类中么
没有这个限制,可能是哪里出错了。添加过 Q_OBJECT 宏之后最好重新 qmake 一下再编译。
当控制权返回到主线程循环是
是->时
多谢指出
豆子你好,我是个Qt新手,非常感谢你的分享,我从中学到了很多。
我在自定义事件中遇到了些问题,希望能得到你的帮助:
下面是我的代码:
//customevent.h
const QEvent::Type MY_EVENT_TYPE = (QEvent::Type)QEvent::registerEventType(QEvent::User + 10);
class SytEvent:public QEvent{
public:
SytEvent(QEvent::Type type = MY_EVENT_TYPE):QEvent(type)
{
qDebug()<<"in constructor, MY_EVENT_TYPE == "<<MY_EVENT_TYPE;
qDebug()<type() == "<type();
qDebug()<<"send";
}
};
//mainwindow.h
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
bool event(QEvent *event);
};
//mainwindow.cpp
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
}
MainWindow::~MainWindow()
{
}
bool MainWindow::event(QEvent *event)
{
qDebug()<type() == "<type();
qDebug()<<"in event(), MY_EVENT_TYPE == "<type() == QEvent::Type(1010)){
qDebug()<<"got sytEvent";
return true;
}else{
qDebug()<<"didn't get";
return false;
}
}
//main.cpp
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
SytEvent sytEvent;
qDebug()<<"in main before send, MY_EVENT_TYPE == "<<MY_EVENT_TYPE;
QCoreApplication::sendEvent(&w,&sytEvent);
qDebug()<<"in main after send, MY_EVENT_TYPE == "<type() == QEvent::Type(1010)
send
in main before send, MY_EVENT_TYPE == QEvent::Type(1010)
in event(), event->type() == QEvent::Type(1010)
in event(), MY_EVENT_TYPE == QEvent::Type(MaxUser)
got sytEvent
in main after send, MY_EVENT_TYPE == QEvent::Type(1010)
其中我不明白的是,在进入event()函数后,我的 MY_EVENT_TYPE 为什么变成了QEvent::Type(MaxUser), 而不是我之前自己定义的QEvent::Type(User + 10)?
最后的main.cpp和输出结果内容缺失了,我再补充下:
//main.cpp
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
SytEvent sytEvent;
qDebug()<<"in main before send, MY_EVENT_TYPE == "<<MY_EVENT_TYPE;
QCoreApplication::sendEvent(&w,&sytEvent);
qDebug()<<"in main after send, MY_EVENT_TYPE == "<type() == QEvent::Type(1010)
send
in main before send, MY_EVENT_TYPE == QEvent::Type(1010)
in event(), event->type() == QEvent::Type(1010)
in event(), MY_EVENT_TYPE == QEvent::Type(MaxUser)
got sytEvent
in main after send, MY_EVENT_TYPE == QEvent::Type(1010)
这里的代码贴出来少了很多东西,语句都不能通过编译。方便的话还是发到邮箱比较好一些
你好,豆子前辈,我不太明白,动作事件和信号之间怎么区分使用哪个,就是他们是不是各有各的用途?
信号是使用组件时会用到的,事件一般是自定义组件时会用到。由于事件处理函数都是 protected 的,因此需要继承实现。不过这个也没有一个精准的答案,还是看需求而定
请问豆子哥,我写了一个QEvent的子类CustomEvent,字类调用了registerEventType,但是当我发送事件时并没有在窗口中发现我要的自定义事件,反倒是直接在构造函数里指定事件号,不调用registerEventType就可以在窗口中发现我要的自定义事件,这是我的qevent子类构造
CustomEvent(Type type = (Type)1010) :
QEvent(type)
{ }
然后在窗口中重新实现了按键, _e是QEvent *, this是窗口Qapplication的子类
void keyPressEvent(QKeyEvent *)
{
QCoreApplication::sendEvent(this, _e);
}
然后窗口类的event,t是 typename QEvent::Type _t;
bool event(QEvent *e)
{
if(e->type() == _t)
{
qDebug() << "my custom event: 1010";
return true;
}
return QWidget::event(e); // by default
}
不知道registerEventType函数怎么用?
豆哥,学习了一段时间您的写的qt文章,收获还是蛮大的,不知道您最近是不是太忙了,这边一直没有回复,不过我自己讲下我自己的一些想法吧,您这边给了很多有一些QT文档中一些英文看不太懂的解释,但是我个人感觉一些都是停留在文档中的解释上,没有实际的应用,就像前面notify函数还有这边的sendPostedEvents()函数等等,都是一些与QT文档中的解释相关的,我学习的时候只是感觉有这个函数及函数的意思和作用,但是不知道要怎么实际去用它,我觉得这一点在我看到的目前为止的文章里面没有看到的,我觉着您对于解释,可以多增加些要怎么用,与哪些函数一起配合使用什么的,这样可以加强理解,对于我这种相对比较笨的学习者来说我的感受就这些吧,但是这些只是我的一些想法,没有任何偏见。
我是想先介绍这些函数,在用到的时候知道有这么个东西,然后去查阅文档的时候能够知道查哪方面的,看具体怎么使用。因为内容太多了,不可能把一些实际使用都罗列出来。
自定义事件一般在什么情况下才需要用阿?我看的一些代码很少看到自定义事件的
什么时候添加Q_OBJECT宏会报错呢
请问豆子哥,最近要改一个GUI pong游戏。在mainwindow.cpp 中ui->boardView->installEventFilter(*evtflt), 需要将原来evtfilt中的keypress事件type换成了自定义事件MyEvent (大体上需要使用遥感信号(通过UDP)代替左右键),可是自定义事件需要在main.cpp中通过 QCoreApplication::sendEvent函数手动发送给目标(qt自带的事件类型好像不用,按键,鼠标等似乎是自动在触发后发送的). main.cpp中定义了mainwindow w, ui在w内, boardView又在ui内,测试后发现事件根本没有被发送到evtflt,求解
更进一步,能不能不从主函数发送事件,比如说,定义接受信号的udpreceiver.cpp, 当socket发现有信号时readyread()自动发送信号给绑定的receive()槽,然后再receive中直接实现自定义事件的声明 (MyEvent myevent)并循环发送给eventfilter?
代码在stackoverflow https://stackoverflow.com/questions/60424216/how-to-use-eventfilter-under-child-widget-to-catch-self-define-event
最后的代码,CustomEvent 到底是函数还是类呢
弄懂了。。。
所以处理自定义事件的第一种方法,customEvent是需要手动调用吗,否则如何能确保传入的QEvent *event就是CustomEvent *类型的呢
customEvent() 不需要手动调用,Qt 会自己调用;我们需要根据 event 的 type 或者强制转换来判断是哪种事件类型
总结:
1、通过派生QEvent实现自定义事件,registerEventType 注册新事件类型。
2、信号槽是同步的,信号发生立刻就会调用槽函数,事件则可以sendEvent同步立即处理事件可以postEvent加到事件队列由事件队列异步处理。
3、响应时重写QObject::customEvent即可,QObject::event中对于用户自定义事件会自动调用customEvent。处理可以用事件类型转换来判断,可以获取事件type做判断。
亲爱的豆子先生,你讲述的QT方面知识特别深入,难得的文章,为什么不考虑这书呢,你要这书我肯定买。
亲爱的豆子先生,你讲述的QT方面知识特别深入,难得的文章,为什么不考虑出书呢,你要出书我肯定买。你的讲解特别清晰。