首先说声对不起,上次在PyQt5中写信号与槽,由于时间原因没有写完。有小伙伴留言说,希望把这章补全。所以,这是一篇迟来的文章,再次向大家说声抱歉。
Qt的使用场景,主要是应用于桌面程序来使用,不管你使用的操作系统平台是什么。对于桌面程序来说,最重要的就是交互了。既然有交互,就需要一个窗口系统了。
窗口系统实现了桌面程序的主要逻辑,并提供了一套基于事件驱动的编程框架。Qt同样也提供了这样的一套逻辑。
我们常见的桌面程序的结构,如下图所示:
所以,我们可以看到,在桌面程序中我们需要对窗口系统的一些操作作出相应,也就是事件。
实现事件的机制通常有两种。
一种是事件处理类,是用回调函数来实现。
另一种叫委托,就是事件的处理不是收到事件的人自己来做,而是把它委托给了别人来做。Qt就提供了这样的一种机制:信号和槽。
信号与槽是Qt特有的的消息传输机制,在Qt中信号与槽用得十分广泛。在编程的过程中,我们都会遇到消息传递的事情,本质上就是发出命令(信号、消息),执行命令(相应的执行)。
比如单击窗口上一个按钮然后弹出一个对话框,那么就可以将这个按钮的单击信号和自定义的槽关联起来,信号是按钮的单击信号,槽实现了创建一个对话框并显示的功能。
信号与槽就是实现对象之间通信的一种机制,在其他编程语言中也有通过回调机制来实现对象之间的通信。
信号槽是设计模式观察者模式的一种实现:
A、一个信号就是一个能够被观察的事件,或者至少是事件已经发生的一种通知;
B、一个槽就是一个观察者,通常就是在被观察的对象发生改变的时候——也可以说是信号发出的时候——被调用的函数;
C、信号与槽的连接,形成一种观察者-被观察者的关系;
D、当事件或者状态发生改变的时候,信号就会被发出;同时,信号发出者有义务调用所有注册的对这个事件(信号)感兴趣的函数(槽)。
信号和槽是多对多的关系。一个信号可以连接多个槽,而一个槽也可以监听多个信号。
然后,实现信号和槽,就要说一下Qt的元对象系统。
Qt 的元对象系统叫 Mate-Object-System,提供了对象之间通信的信号与槽机制、运行时类型信息和动态属性系统。
但是,元对象是基于三个条件的:
1、该类必须继承自Qobject类
2、必须在类的私有声明区声明Q_OBJECT宏(在类定义的时候,如果没有指定public,
则默认为private,用来启用元对象功能,比如动态属性、信号和槽)。
3、 元对象编译器Meta-Object Compiler(moc)为 QObject的子类实现元对象
特性提供必要的代码。
有了元对象系统后,我们就可以使用Qt的信号和槽了。
信号与槽关联是用 QObject::connect() 函数实现的,其基本格式是:
QObject::connect(sender, SIGNAL(signal()), receiver, SLOT(slot()));
在Qt 5中提供了一种新的格式:
connect(sender, &Sender::valueChanged,receiver, &Receiver::updateValue);
具体的使用哪种格式,就看个人的喜好了。
注:在日常的项目编程中,如果第一种的connect方式出现报错,你可以尝试使用第二种connect方式。
在connect函数中除了上面介绍的四个参数外,还有第五个参数,是缺省的参数。我们打开qobject.h文件可以看到它的定义,如下所示:
static QMetaObject::Connection connect(const QObject *sender, const char *signal,
const QObject *receiver, const char *member, Qt::ConnectionType = Qt::AutoConnection);
static QMetaObject::Connection connect(const QObject *sender, const QMetaMethod &signal,
const QObject *receiver, const QMetaMethod &method,
Qt::ConnectionType type = Qt::AutoConnection);
inline QMetaObject::Connection connect(const QObject *sender, const char *signal,
const char *member, Qt::ConnectionType type = Qt::AutoConnection) const;
最后一个参数所表示的意思:
Qt::AutoConnection:信号的发送者与信号的接收者在同一线程,则默认使用Qt::DirectConnection:如果不在同一线程,则默认 使用Qt::QueuedConnection。
Qt::DirectConnection:信号的发送者与信号的接收者在同一线程中执行,当发出信号后,会马上进入槽函数,看上去就像在信号 发送位置调用了槽函数,在多线程下会比较危险,容易造成崩溃。
Qt::QueuedConnection:信号的发送者与信号的接收者不在同一线程中执行,槽函数运行于信号的接收者线程,当发送信号后, 槽函数不会马上被调用,等待信号的接收者把当前函数执行完,进入事件循环之后,槽函数才会被调用。多线程环境下一般用这个。
Qt::BlockingQueuedConnection:槽函数的调用时机与Qt::QueuedConnection一致,不过发送完信号后发送者所在线程会阻塞,直到槽函数运行完。接收者和发送者绝对不能在一个线程,否则程序会死锁。在多线程间需要同步的场合可能需要这个。
Qt::UniqueConnection:可以通过按位或(|)与以上四个结合在一起使用。当设置此参数时,当某个信号和槽已经连接时,再进行重复的连接就会失败。也就是避免了重复连接。
介绍到这里,信号和槽的基本内容就写完了。但是,还是得说下Qt事件(你可以结合着上面第2节一起看)。
无论是什么桌面操作系统,每个进程都有一个全局的事件队列(Event Queue)。当我们在键盘上按了一个键、移动或者点击鼠标、触摸屏幕等等,都会产生一个事件(Event),并由操作系统负责将它扔到进程的事件队列。
扔到事件队列后,它会等待以后的某一个时刻发送。分配器(dispatcher )会遍历事件队列,并且将入栈的事件发送到它们的目标对象当中,因此它们被称为事件循环(Event loop)。
Qt中是通过运行QCoreApplication::exec()来进入Qt的主体事件循环的;这会引发阻塞,直至QCoreApplication::exit() 或者 QCoreApplication::quit() 被调用,进而结束循环。
写到这里就先结束吧,再次向大家说声抱歉,这篇迟来的文章-关于信号和槽的,欢迎大家一起交流一起进步。
本文原创作者:冯一川([email protected]),未经作者授权同意,请勿转载。