02Qt信号与槽(1)

信号与槽
1.概述

​ 信号和槽机制是 Qt 的核心机制,信号和槽是一种高级接口,应用于对象之间的通信,它是 Qt 的核心特性,也是 Qt 区别于其他工具包的重要地方。信号和槽是 Qt 自行定义的一种通信机制,它独立于标准的 C/C++ 语言,因此要正确的处理信号和槽,必须借助一个呗称为 moc (Meta Object Compiler) 的 Qt 工具,该工具是个 C++ 预处理程序,它为高层次的事件处理自动生成所需要的附加代码。

​ 在我们所熟知的非常多的 GUI 工具包中,串口小部件(wedget)都有一个回调函数,用于响应他们能触发的每个动作,这个回调函数通常都是指向某个函数的指针。不过,在 Qt 中信号和槽取代了这些凌乱的函数指针,使得我们编写这些通信程序更为简洁明了。信号和槽能携带任意数量和任意类型的参数,他们是类型完全安全的,不会像回调函数那样产生 core dups。

​ 所有从 QObject 或其子类(例如 QWidget)派生的类都能够包含信号和槽。当对象改动其状态时,信号就由该对象发射(emit)出去,这就是真正的信息封装,它确保对象被当做一个真正的软件组件来使用。槽用于接收信号,但他们是普通的对象成员函数。一个槽并不知道是否有信号和自己连接。而且对象并不了解具体的通信机制。

​ 你能将非常多的信号和单个槽进行连接,也能将单个信号和非常多的槽进行连接,甚至于将一个信号和另外一个信号相连接也是可能的,这时无论第一个信号什么时候发射系统都将及时发射第二个信号。总之,信号和槽构造了一个强大的部件编程机制。

2. 信号

​ 当某个信号对其客户或所有者的内部状态发生改动,信号被一个对象发射。只有定义过这个信号的类及其派生类能够发射这个信号。但一个信号被发射时,和其相关联的槽将被即时执行,就像一个正常的函数调用一样。信号-槽机制完全独立于所有的 GUI 事件循环。只有当所有的槽返回以后发射函数(emit)才返回。如果存在多个槽和某个信号相关联,那么当这个信号被发射时,这些槽将会一个接一个地执行,不过他们执行的顺序将会是随机的、不确定的,我们不能人为地指定哪个先执行,哪个后执行。

​ 信号的声明是在头文件中进行的,Qt 的 signals 关键字指出进入了信号声明区,随后即可声明自己的信号。例如,下面定义了三个信号:

signals:
	void mySignal;
	void mySignal(int x);
	void mySignalParam(int x, int y);

​ 在上面的定义中,signals 是 Qt 的关键字,而非 C/C++ 的。接下来一行的 void mySginal 定义了信号 mySignal,这个信号没有携带参数;接下来的一行 void mySignal(int x) 定义了重名信号 mySginal,不过他携带一个整形参数,这有点类似于 C++ 的虚函数。从形式上讲信号的声明和普通的 C++ 函数声明是相同的,不过信号却没有函数体定义,另外,信号的返回值都是 void,不要指望能从信号返回什么有用信息。

​ 信号由 MOC 自动产生,他们不应该在 .cpp 文件中出现。

3. 槽

​ 槽是普通 C++ 成员函数,能被正常调用,他们唯一的特别性就是非常多信号能和其相关联。当和其关联的信号被发射时,这个槽就会被调用。槽能有参数,但槽的参数不能有缺省值。

​ 既然槽是普通的成员函数,因此他其他的函数相同,他们也有存取权限。槽的存取权限决定了谁能够和其相关联。同普通的 C++ 成员函数相同,槽函数也分为三种类型,即 public slots、private slots和 protected slots。

public slots: 在这个区内声明的槽意味着所有对象都可将信号与之连接。这对应组件编程非常有用。你能创建彼此互不了解的对象,经他们的信号和槽进行连接以便信息能够正确地传递。
protected slots: 在这个区内声明的槽意味着当前类及其子类能将消息与之相连接。这适用于那些槽 —— 他们是类实现的一部分,不过其界面接口却面向外部。
private slots: 在这个区内声明的槽意味着只有类自己能将信号与之相连接。着适用于联系非常紧密的类。

​ 槽也能够声明为虚函数,这也是非常有用的。

​ 槽的声明也是在头文件中进行的。例如,下面声明了三个槽:

public slots:
	void mySlot;
	void mySlot(int x);
	void mySlotParam(int x, int y);
4.信号和槽的关联

​ 通过调用 QObject 对象的 connect 函数来将某个对象的信号和另外一个对象的槽函数相关联,这样当发射者发射信号时,接受者的槽函数将被调用。该函数的定义如下:

bool QObject::connect(const QObject* sender, const char* signal, const QObject* receiver, const char* member) [static]

​ 这个函数的作用就是将发射者 sender 对象中的信号 signal 和接受者 receiver 中单的 member 槽函数关联起来。当指定信号 signal 时必须使用 Qt 的宏 SIGNAL,当指定槽函数时必须使用 SLOT。如果发射者和接受属于同一个对象的话,那么在 connect 调用中接收者参数能省略。

QLabel* label = new QLabel;
QScrollBar* scroll = new QScrollBar;
QOBject::connect(scroll, SIGNAL(valueChanged(int)), label, SLOT(setNum(int)));

​ 一个信号甚至能够和另一个信号相关联。

class MyWidget:public QWidget
{
public:
	MyWidget;
	...
signals:
	void aSignal;
	...
private:
	QPushButton* aButton;
};
MyWidget::Muwidget
{
	aButton = new QPushButton(this);
	connect(aButton, SIGNAL(clicked), SIGNAL(aSignal));
}

​ 在上面的构造函数中,MyWidget 创建了一个私有的按钮 aButton, 按钮的单击事件产生的信号 clicked 和另外一个信号 aSignal 也接着被发射。当然,也可以直接将单击事件和某个私有的槽函数相关联,然后在槽中发射 aSignal 信号,这样的话似乎有点多余。

​ 当信号和槽 没有必要继续保持关联时,我们能使用 disconnect 函数来断开连接。其定义如下:

bool QObject::disconnect(const QObject* sender, const char* signal, const Object* receiver, const char* member) [static]

​ 这个函数断开发射者中的信号和接收者中槽函数之间的关联。有三种情况必须使用 disconnect 函数。

(1)断开和某个对象相关联的所有对象。这似乎有点不可理解,事实上,当我们在某个对象中定义了一个或多个信号时,这些信号和另外若干个对象中的槽相关联,如果我们要切断这些关联的话,就能利用这个方法,非常之简洁。

disconnect(myObject, 0, 0, 0);
或者
myObject->disconnect;

(2)断开和某个特定信号的所有关联。

disconnect(myObject, SIGNAL(mySignal), 0, 0);
或者
myObject->disconnect(SIGNAL(mySignal));

(3)断开两个对象之间的关联

disconnect(myObject , 0, myReceiver, 0);
或者
myObject->disconnect(myReceiver);

​ 在 disconnect 函数中 0 能用作一个通配符,分别表示所有信号、所有接受对象、接受对象中的所有槽函数。不过发射者 sender 不能为 0,其他三个参数的值都能等于 0 。

5. 信号槽如何传递参数

​ 利用 Qt 进行程序开发时,有时需要信号槽来 完成参数传递。带参数的信号槽在使用时,有几点需要注意的地方。

(1)当信号与槽函数的参数数量相同时,它们的参数类型要完全一致。

//mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private:
    Ui::MainWindow *ui;

signals:
	//自定义信号
    void iSignal(int b);

private slots:
	//自定义槽函数
    void iSlot(int b);
    void on_pushButton_clicked();
};

#endif // MAINWINDOW_H
//mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    //信号与槽的绑定
    connect(this, SIGNAL(iSignal(int)), this, SLOT(iSlot(int)));
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::iSlot(int b)
{
    qDebug() << b;
}

void MainWindow::on_pushButton_clicked()
{
    emit iSignal(500);
}

​ 运行,点击按钮,则打印出传入的参数。

image

(2)当信号的参数与槽函数的参数数量不同时,只能是信号的参数数量多于槽函数的参数数量,且前面相同数量的参数类型应一致,信号中多于的参数会被忽略。

//信号
void iSignal(int a, float b);
//槽
void MainWindow::iSlot(int b)
{
    qDebug(b);
}
//信号槽绑定
connect(this, SIGNAL(iSignal(int, float)), this, SLOT(iSlot(int)));
//发送信号
emit iSigal(5, 0.3);
//结果打印出“5”

(3)此外,在不进行参数传递时,信号槽绑定时也是要求信号的参数数量大于等于槽函数的参数数量。这种情况一般是一个带参数的信号去绑定一个屋参数的槽函数。

//信号
void iSignal(int a, float b);
//槽
void MainWindow::iSlot
{
    QString qString  = "Hello world!";
    qDebug(qString);
}
//信号槽绑定
connect(this, SIGNAL(iSignal(int, float)), this, SLOT(iSlot));
//发送信号
emit iSigal(5, 0.3);
//结果打印出“Hello world!”
posted @ 2018-07-24 15:27  洛克十年  阅读(213)  评论(0编辑  收藏  举报