Qt 信号和槽的机制(逻辑清晰的来说清楚信号和槽,呕心沥血之作)

Qt 信号和槽的机制

首先说声对不起,上次在PyQt5中写信号与槽,由于时间原因没有写完。有小伙伴留言说,希望把这章补全。所以,这是一篇迟来的文章,再次向大家说声抱歉。

一、桌面程序的结构

Qt的使用场景,主要是应用于桌面程序来使用,不管你使用的操作系统平台是什么。对于桌面程序来说,最重要的就是交互了。既然有交互,就需要一个窗口系统了。

窗口系统实现了桌面程序的主要逻辑,并提供了一套基于事件驱动的编程框架。Qt同样也提供了这样的一套逻辑。

我们常见的桌面程序的结构,如下图所示:

Qt 信号和槽的机制(逻辑清晰的来说清楚信号和槽,呕心沥血之作)_第1张图片

 所以,我们可以看到,在桌面程序中我们需要对窗口系统的一些操作作出相应,也就是事件。

二、事件

实现事件的机制通常有两种。

一种是事件处理类,是用回调函数来实现。

另一种叫委托,就是事件的处理不是收到事件的人自己来做,而是把它委托给了别人来做。Qt就提供了这样的一种机制:信号和槽。

三、信号和槽

信号与槽是Qt特有的的消息传输机制,在Qt中信号与槽用得十分广泛。在编程的过程中,我们都会遇到消息传递的事情,本质上就是发出命令(信号、消息),执行命令(相应的执行)。

比如单击窗口上一个按钮然后弹出一个对话框,那么就可以将这个按钮的单击信号和自定义的槽关联起来,信号是按钮的单击信号,槽实现了创建一个对话框并显示的功能。

信号与槽就是实现对象之间通信的一种机制,在其他编程语言中也有通过回调机制来实现对象之间的通信。

  • 信号:当对象改变其状态时,信号就由该对象发射 (emit) 出去,而且对象只负责发送信号,它不知道另一端是谁在接收这个信号。
  • 槽:用于接收信号,而且槽只是普通的对象成员函数。一个槽并不知道是否有任何信号与自己相连接。

信号槽是设计模式观察者模式的一种实现:

A、一个信号就是一个能够被观察的事件,或者至少是事件已经发生的一种通知;
B、一个槽就是一个观察者,通常就是在被观察的对象发生改变的时候——也可以说是信号发出的时候——被调用的函数;
C、信号与槽的连接,形成一种观察者-被观察者的关系;
D、当事件或者状态发生改变的时候,信号就会被发出;同时,信号发出者有义务调用所有注册的对这个事件(信号)感兴趣的函数(槽)。


信号和槽是多对多的关系。一个信号可以连接多个槽,而一个槽也可以监听多个信号。

然后,实现信号和槽,就要说一下Qt的元对象系统。

四、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的第五个参数

在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节一起看)。

七、Qt事件

无论是什么桌面操作系统,每个进程都有一个全局的事件队列(Event Queue)。当我们在键盘上按了一个键、移动或者点击鼠标、触摸屏幕等等,都会产生一个事件(Event),并由操作系统负责将它扔到进程的事件队列。

扔到事件队列后,它会等待以后的某一个时刻发送。分配器(dispatcher )会遍历事件队列,并且将入栈的事件发送到它们的目标对象当中,因此它们被称为事件循环(Event loop)。

Qt中是通过运行QCoreApplication::exec()来进入Qt的主体事件循环的;这会引发阻塞,直至QCoreApplication::exit() 或者 QCoreApplication::quit() 被调用,进而结束循环。

写到这里就先结束吧,再次向大家说声抱歉,这篇迟来的文章-关于信号和槽的,欢迎大家一起交流一起进步。

本文原创作者:冯一川([email protected]),未经作者授权同意,请勿转载。

你可能感兴趣的:(Qt学习之路,qt)