signal&slot - Qt的交互响应机制

Qt的信号和槽机制,不同于传统的callback机制,signal&slot机制,使得信号的触发与处理变得更加灵活且安全。

signal&slot - Qt的交互响应机制

1 原理

1.1 传统的callback机制

传统的callback是事件触发后,就去调用被触发对象的callback函数。

例如,一个Button被按下后,该Button的回调函数(callback function)就会被调用执行。

Matlab的GUI编程下通过编写callback函数,Java环境下通过设置事件监听函数。其实都是去重载原本默认为空的事件处理函数。

1.2 Qt的signal与slot机制

Qt采用 信号(signal)(slot) 的机制来实现交互响应。

信号(signal)(slot) 都是Qt中一个类的成员函数。

首先,信号需要和接收槽相连接。运行时,被触发的对象会释放(emit)信号,如果此信号有已连接的接收槽,那么该接收槽就会收到信号,以及其附带的参数,并进行事件处理。


2 connect

2.1 函数原型

1
connect(sender, SIGNAL(signal), receiver, SLOT(slot));
  • sender 是信号发送对象的指针
  • signal 是信号发送对象的signal函数
    • 例如,PushButton:
      • 按下时,会释放pressed()的信号;
      • 抬起时,会释放released()的信号;
      • 单击时,会释放clicked()的信号;
      • ……
  • receiver 是接收槽对象的指针
  • slot 是接收槽对象的slot函数
    • 此函数用于处理接收到signal函数信号之后,需要执行的内容
  • 另外,SIGNALSLOT 都是Qt定义的宏函数,用于实现参数转换。

2.2 示例

1
2
3
4
5
6
7
8
9
10
11
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);

connect(ui->listWidget_users, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(onUserClicked(QListWidgetItem*)));

……

}
  • 此处,我在MainWindow的构造函数(construct func) 中,使用了connect函数来实现用户列表组件的点击信号与点击处理槽函数的连接。

    • 当用户列表组件(ListWidget) 中的一个项目被单击后,就是释放出 itemClicked(QListWidgetItem*) 信号,这个信号会导致相连接的接收槽函数 onUserClicked(QListWidgetItem*) 被调用。前者的参数QListWidgetItem*会被传递给后者。
  • MainWindow的ui对象在拷贝构造函数开始执行之前已经构造生成。(C++冒号语法,不再赘述)

  • 通过ui对象指针,可以获取当前的界面组件。

  • 接收槽函数——onUserClicked(QListWidgetItem*)在接受类中先声明,再实现。

    • 声明:

      • ```c++ class **** { ****

        public slots: void onUserClicked(QListWidgetItem*);


        }

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10

        - 实现:

        - ```c++
        void MainWindow::onUserClicked(QListWidgetItem *item)
        {
        QString currentUser = item->text();

        ****
        }

2.3 进一步理解connect

  1. signal和slot之间的关系没有限制,可以一对多,多对一;

    1
    2
    connect(sender, SIGNAL(signal), receiverA, SLOT(slotA));
    connect(sender, SIGNAL(signal), receiverB, SLOT(slotB));
    • 一对多
    1
    2
    connect(senderA, SIGNAL(signalA), receiver, SLOT(slot));
    connect(senderB, SIGNAL(signalB), receiver, SLOT(slot));
    • 多对一
  2. signal和signal之间也可以互相连接,由一个signal的释放,触发后一个signal的释放。

    1
    2
    connect(sender, SIGNAL(signal), receiverA, SLOT(slotA));
    connect(sender, SIGNAL(signal), receiverB, SLOT(slotB));
    • 一对多
  3. 连接关系可以动态连接,也可以动态断开

    1
    2
    connect(sender, SIGNAL(signal), receiver, SLOT(slot));		// 动态连接
    disconnect(sender, SIGNAL(signal), receiver, SLOT(slot)); // 动态断开连接
    • 通过disconnect函数即可断开信号与槽的连接关系

3 进一步理解

3.1 自定义Qt类

尝试编写包含signal和slot的Qt类模型:

以下代码来源《Qt C++跨平台图形界面程序设计基础》 殷立峰 主编 清华大学出版社 245页。

(有改动)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Circle:Public QObject
{
Q_OBJECT
public:
Circle()
{
circleRadius=0;
}
int getRadius()
{
return circleRadius;
}
public slots:
void setRadius(int newRadius);
signals:
void radiusChanged(int newRadius);
private:
int circleRadius;
};

void Circle::setRadius(int newRadius)
{
if(newRadius != circleRadius)
{
circleRadius = newRadius;
emit radiusChanged(circleRadius);
}
}
  • 自定义的Circle类继承自QObject;
  • 对外提供slots函数 setRadius,此函数用于设置圆形的新半径。
    • 例如,可以根据用户的操作,触发setRadius函数,判断是否有变化后,更新圆形半径。
    • 此函数特点在于,圆形半径更新后,它又释放出了一个信号——emit radiusChanged(circleRadius);。此信号函数也在Circle类中定义,是Circle类的信号函数。此信号函数携带着参数,会触发开发者编程时连接的槽函数,并将参数对应地传递给槽函数。实际编程中,槽函数例如:界面绘制刷新函数。这样就可以实现数据结构更新时的界面更新重绘。不需要盲目地轮询式查询数据结构,实时重绘显示。

3.2 总结

Qt的信号和槽机制,使得信号触发与信号处理的关系变得灵活且安全。

  • 安全:各个封装好的组件与组件之间可以定向联系,而不打破封装性,不需要互访数据结构。
  • 灵活:connect和disconnect可以动态执行,灵活地实现signal到slot,也即不同对象之间的函数到函数的动态连接和断开。