0%

Qt信号和槽

信号和槽是Qt的一个核心特点,也是区别于其他框架的重要特性。因为有了信号和槽的编程机制,Qt中处理界面各个组件的交互操作时变得更加直观和简单。信号与槽是对象间通信的机制,由Qt的元对象系统的支持才能实现。
信号(Signal)会在某种特定情况或动作下被触发。就是特殊情况下被发射的事件,而GUI程序设计的主要内容就是对界面上各组件信号的响应。
槽(Slot)则等同于接收并处理信号的函数。它跟一般的C++函数是一样的,可以直接被调用。但槽函数与普通函数不同的是,槽函数与一个信号关联,当信号被发射时,关联的槽函数会自动执行。
对于这种对象间通信,Qt隐藏了复杂的底层实现。Qt的信号与槽机制类似于“事件——响应”,但更灵活。


1.connect函数的不同参数形式

  • QObject::connect()函数多重函数形式,一种参数原型是:
    1
    2
    3
    4
    5
    6
    7

    sender是发送信号的对象名称
    signal()是信号名称,根据SIGNAL宏展开成相应字符串
    receiver是接受信号的对象名称
    slot()是槽函数名称,根据SLOT宏展开成字符串
    ×/
    QMetaObject::Connection QObject::connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection)
    一般语法如下:
    1
    2
    3
    4
    5
    // connect()是QObject类中的一个静态函数,QObject又是所有Qt类的基类,实际调用可以忽略前面的限定符
    // 宏SIGNAL()指信号,SLOT()指槽函数
    QObject::connection(sender, SIGNAL(signal()), receiver, SLOT(slot()));
    // 如果带有参数,标明参数类型
    QObject::connection(spinNum, SIGNAL(valueChanged(int)), this, SLOT(updateStatus(int));

  • 另一种参数形式的connect()函数原型是:
    1
    QMetaObject::Connection QObject::connect(const QObject *sender, const QMetaMethod &signal, const QObject *receiver, const QMetaMethod, &method, Qt::ConnectionType type = Qt::Auto Connection)
    这种具有默认参数,信号名称是唯一的,可以使用这种函数指针形式进行关联,语法如下:
    1
    2
    // QLineEdit只有一个textChanged(QString)信号,通过在窗体类widget里定义一个槽函数可以将它们关联,语法如下:
    QObject::connection(lineEdit, &QLineEdit::textChanged, this, &widget::on_textChanged);
    但如果说类中定义有不同参数的同名信号就不能用函数指针的方式来关联信号和槽了:
    1
    2
    3
    // QSpinBox有两个valueChanged()信号
    void QSpinBox::valueChanged(int i);
    void QSpinBox::valueChanged(const QString &text);
    1
    2
    // 在窗体中定义一个槽函数
    void onValueChanged(int i);
    Ctrl+B编译时将会出错:
    1
    QObject::connect(spinNum, &QSpinBox::valueChanged, this, &widget::onValueChanged); // error

connect()函数最后都有一个Qt::ConnectionType type,缺省参数是Qt::AutoConnectionQt::ConnectionType是枚举类型,表示信号与槽的关联方式:

1
2
3
4
5
6
7
8
9
10
11
12
// 缺省值,在信号触发时自动确定关联方式。如果发射者和接受者在同一线程,使用Qt::DirectConnection;否则使用Qt::QueuedConnection
Qt::AutoConnection type;

// 信号被发射时槽函数立刻执行
Qt::DirectConnection type;

// 事件循环回到接受者线程后执行槽函数
Qt::QueuedConnection type;

// 与Qt::QueuedConnection相似,但信号线程会阻塞到槽函数执行完毕。
// 注意在同一线程时不用这种方式,会死锁
Qt::BlockingQueuedConnection type;

2.使用sender获得信号发送者

在槽函数里,使用QObject::sender()可以获取信号发送者的指针,知道发射者的类型就可以将它类型转换成确定的类型。

1
2
// QSpinBox的valueChanged(int)信号里可以通过sender()和qobject_cast去的发射者指针然后对发射者进行操作:
QSpinBox *spinBox = qobject_cast<QSpinBox *>(sender());
1
2
3
4
5
// 通过sender返回并打印Button的text值
QObject::connect(button1, &QPushButton::clicked, [this]{
QPushButton *button = qobject_cast<QPushButton *>(sender());
qDebug() << button->text();
});

3.自定义信号及其使用

通过在自己设计的类中自定义信号,这个信号就是声明的函数,但这个函数不用实现,只需要发射(emit):

1
2
3
4
5
6
7
8
9
10
11
12
// 在类中定义一个 ageChanged(int)
class QPerson : public QObject
{
Q_OBJECT
private:
int m_age = 10;
public:
void incAge();
signals:
// void返回类型,可以多参数,但函数无需实现
void ageChanged(int value);
}
1
2
3
4
5
6
// 在incAge()函数中发射信号,当m_age改变时,就emit信号。至于有无信号关联的槽函数,发射者不管
void QPerson::incAge()
{
m_age++;
emit ageChanged(m_age); // emit signal
}

4.关于信号和槽的使用上的规则

  1. 一个信号可以连接多个槽
  2. 多个信号可以连接同一个槽
  3. 一个信号可以连接另一个信号
  4. 信号与槽的个数和类型需要一致,至少信号的参数不能少于槽的参数
  5. 使用信号和槽的类中,需要在类定义中加入宏Q_OBJECT
  6. 当信号被发射时,关联的槽函数会被立即执行,直到执行完毕才会执行余下的程序流程。