Qt的事件和与Widget跨线程交互

来源:互联网 发布:用g71编程内孔 编辑:程序博客网 时间:2024/06/10 02:40

一般gui的程序的模型总有至少两个主要线程,一个是界面的主事件循环所在的线程,另一个是处理工作任务的线程,工作线程看不见,在后台处理事务产生数据,然后显示在界面上。例如一个即时通讯客户端,主界面显示好友列表,工作线程接受来自网络的消息,收到消息后,要通知界面,将对应的好友的头像闪烁显示。这就要跨线程

记得C#中有委托delegate,有InvokeRequired属性判断是否同线程操作,有Invoke来调用跨线程的委托。初学PySide,遇到同样的问题,看了半天文档,发现没有和C#类似的手段。但是,反倒可以用更有条理的方式来处理这样的跨线程调用请求,那就是事件。

QEvent是所有Qt事件的基类,构造函数只接受一个参数,那就是QEvent.Type类型的事件类型参数,如果是内置的事件类型,可以直接使用已定义的类型,例如QEvent.Close,它本质是一个int整数。如果要定义一个自己的事件,就需要一个这样的数字,而且和已有的所有事件类型不重复。

首先,使用QEvent.registerEventType这个静态方法来注册一个自己的类型,自定义的类型的值的返回是(QEvent.User, QEvent.UserMax)之间的一个数字,调用registerEventType时可以指定这样一个自己的数字,也可以不指定,由其自动分配一个数字。例如:

EVENT_INCOMING_MESSAGE = QEvent.registerEventType(QEvent.User + 0x01)

有了这个自己的类型,然后就是构建一个自己的事件类:

class IncomingMessageEvent(QEvent):    def __init__(self, msg):        super(IncomingMessageEvent, self).__init__(            QEvent.Type(EVENT_INCOMING_MESSAGE)        )        self.msg = msg

那么如何在工作线程中将该事件“投递”至窗口?可以使用QCoreApplication的postEvent方法,注意不是sendEvent,是postEvent,他们的区别就在于postEvent能跨线程“投递”,因为sendEvent是直接将事件送至目标Widget来处理,并返回处理结果,而postEvent是将事件插入处理队列之后返回,并不等待至事件获得实际处理

假如app是主界面的一个QApplication对象,dlg是主界面的一个QDialog对象,msg是要投递的消息内容,那么将这个自己的事件投递至dlg的方法就是:

app.postEvent(dlg, IncomingMessageEvent(msg))

事件是投递完成,那么在QDialog中还得处理这个事件,不然dlg就要当SPAM来处理了... 

所有Qt的Widget处理事件,都是由它的event方法来进行,所以只需要在自己的Widget中实现一个自己的event方法,该方法接受单个QEvent类型的参数。

class MainFrame(QDialog):    # ......    def event(self, evt):        if evt.type() == EVENT_INCOMING_MESSAGE:            if hasattr(evt, 'msg'):                msg = evt.msg                self.show_incoming_message(msg) # 在界面显示该消息        return super(MainFrame, self).event(evt)

这时就可以安全的处理要显示的内容了。