Qt 信号槽

写在前面

    在界面搭建时,我们需要很多触发事件。比如,鼠标右击,我们希望弹出对话框;点击退出的按钮,界面就会关闭或者执行其他的操作(如下图)。在Qt当中,提供了信号槽的运行机制。


右键弹出菜单栏
按钮动作

1 元对象系统

    信号和槽机制是Qt 的核心特征,需要借助Qt 的元对象系统实现。除了支持信号槽机制,元对象系统提供的功能有:对象间通信的运行时类型信息和动态属性系统等。

    Qt的元对象系统基于如下三个关键要素:
(1)类——QObject:所有元对象系统的基类

(2)宏——Q_OBJECT:为 Q_OBJECT 宏展开后所声明的成员函数的成生实现代码识别 Qt中特殊的关键字及宏,比如识别出 Q_PROPERTY宏、Q_INVOKABLE宏、slot、signals等

    放在继承自QObject的某个类私有区域中,使其拥有元对象特性:信号槽、动态特性等。

(3)元对象编译器——moc(Meta-Object Compiler):Qt的元对象编译器(MOC)是一个预处理器,在源程序被编译前先将Qt特性的程序转换为标准C++兼容的形式,然后再由标准C++编译器进行编译。(为每个QObject 的子类,提供实现了元对象特性所必须的代码。)

2 信号-槽

(1)槽的参数的类型需要与信号参数的类型相对应,
(2)槽的参数不能多余信号的参数,因为若槽的参数更多,则多余的参数不能接收到信号传递过来的值,若在槽中使用了这些多余的无值的参数,就会产生错误。
(3)若信号的参数多余槽的参数,则多余的参数将被忽略。
(4)一个信号可以与多个槽关联,多个信号也可以与同一个槽关联,信号也可以关联到另一个信号上。
(5)若一个信号关联到多个槽时,则发射信号时,槽函数按照关联的顺序依次执行。
(6)若信号连接到另一个信号,则当第一个信号发射时,会立即发射第二个信号。

2-1简介

1 信号

定义的形式如下:
signals:
void mySignal();
void mySignal(int x);
void mySignalParam(int x,int y);

(1)信号使用 signals 关键字声明,在其后面有一个冒号“:”,在其前面不能有 public、private、 protected 访问控制符,信号默认是 public 的。
(2)信号只需像函数那样声明即可,其中可以有参数,参数的主要作用是用于和槽的通信,这就像普通函数的参数传递规则一样。信号虽然像函数,但是对他的调用方式不一样,信号需要使用 emit 关键字发射。
(3)信号只需声明,不能对其进行定义,信号是由 moc 自动生成的。

2 槽函数

形如:
public slots:
void mySlot();
void mySlot(int x);
void mySignalParam(int x,int y);

(1)声明槽需要使用 slots 关键字,在其后面有一个冒号“:”,且槽需使用 public、private、 protected 访问控制符之一。
(2)槽就是一个普通的函数,可以像使用普通函数一样进行使用,槽与普通函数的主要区别是,槽可以与信号关联。

3 发射信号

(1)发射信号需要使用emit关键字,注意,在 emit 后面不需要冒号。
(2)emit 发射的信号使用的语法与调用普通函数相同,比如有一个信号为 void f(int),则发送的语法为:emit f(3);
(3)当信号被发射时,与其相关联的槽函数会被调用( 注意:信号和槽需要使用
QObject::connect 函数进行关联之后,发射信号后才会调用相关联的槽函数)。
(4)注意:因为信号位于类之中,因此发射信号的位置需要位于该类的成员函数中或该类能见到信号的标识符的位置。

4 自定义信号槽实例

示例:创建信号和槽 
//头文件 m.h 的内容#ifndef    M_H #define M_H #include #include  using namespace std; 
 
class A:public QObject
{ 
   //信号和槽必须继承自 QObject 类
   Q_OBJECT // 必 须 添 加 该 宏 

  //public signals:void s1(int); //错误 signals 前不能有访问控制符。 
     signals:void s(); //使用 signals 关键字声明信号,信号的语法与声明函数相同。 
     signals:void s(int,int); //正确,信号可以有参数,也可以重载。 
        //void s2(){} //错误,信号只需声明,不能定义。 
       void s3(); //注意:这仍是声明的一个信号 
public: //信号声明结束后,重新使用访问控制符,表示以下声明的是成员函数。 
        void g()
      { 
    emit s3();  /*发射信号,其语法与调用普通函数相同,在信号与槽关联之前,发射的信号不会调用相应的槽函数。*/ 
    // emit: s3(); //错误,emit 后不能有冒号。 
       }
}; 
 
class B:public QObject
{ 
Q_OBJECT 
public slots: // 使 用 slots 关 键 字 声 明 槽 
void x(){cout<<"X"<

5 补充说明

  1. 信号和槽是用于对象之间的通信的,这是 Qt 的核心。为此 Qt 引入了一些关键字,他们是slots、signals、emit,这些都不是 C++关键字,是 Qt 特有的,这些关键字会被 Qt 的moc 转换为标准的 C++语句。

  2. 只有QObject 及其派生类才能使用信号和槽机制,且在类之中还需要使用Q_OBJECT 宏。

  3. Qt 的部件类中有一些已经定义好了的信号和槽,也可以子类化部件类,然后添加自已的信号和槽。

  4. 信号与槽本质上式回调函数。信号与槽的效率是非常高的,但是同真正的回调函数比较起来,由于增加了灵活性,因此在速度上还是有所损失。

  5. 信号与槽机制与普通函数的调用一样,如果使用不当的话,在程序执行时也有可能产生死循环。因此,在定义槽函数时一定要注意避免间接形成无限循环,即在槽中再次发射所接收到的同样信号。例如在 mySlot() 槽函数中加上语句 emit mySignal() 即可形成死循环。

  6. 如果一个信号与多个槽相联系的话,那么,当这个信号被发射时,与之相关的槽被激活的顺序将是随机的。

2-2连接信号和槽——connect

1 表达形式

static QMetaObject::Connection connect(const QObject *sender, const char *signal,
const QObject *receiver, const char *method,Qt::ConnectionType type = Qt::AutoConnection)
各参数意义如下:
(1)sender:表示需要发射信号的对象。
(2)signal:表示需要发射的信号,该参数必须使用 SIGNAL()宏。
(3)receiver:表示接收信号的对象。
(4)method:表示与信号相关联的槽函数,这个参数也可以是信号,从而实现信号与信号的关联。该参数若是槽,需使用 SLOT()宏,若是信号需使用 SIGNAL 宏。
(4) type:用于指明信号和槽的关联方式,它决定了信号是立即传送到一个槽还是在稍后时间排队等待传送。关联方式使用枚举 Qt::ConnectionType 进行描述,下表为其取值及意义:


type取值说明

或者写成QObject::connect(......),或者connect(......)

connect有三种写法:

  • Qt4的写法
    QPushButton *button = new QPushButton("Quit");
    connect(button, SIGNAL(clicked()), &a, SLOT(quit()));

  • Qt5的写法
    QPushButton button("Quit");
    QObject::connect(&button, &QPushButton::clicked,&app, &QApplication::quit);** //即使quit有多个形参,也是这种形式**

  • Lambda


    Lambda表达式
Lambda示意图

函数对象参数:
(1)[],标识一个Lambda的开始,这部分必须存在,不能省略。函数对象参数是传递给编译器自动生成的函数对象类的构造函数的。函数对象参数只能 使用那些到定义Lambda为止时Lambda所在作用范围内可见的局部变量(包括Lambda所在类的this)。函数对象参数有以下形式:

  • 空。没有使用任何函数对象参数。
  • =。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量)。
  • &。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是引用传递方式(相当于编译器自动为我们按引用传递了所有局部变量)。
  • this。函数体内可以使用Lambda所在类中的成员变量。
  • a。将a按值进行传递。按值进行传递时,函数体内不能修改传递进来的a的拷贝,因为默认情况下函数是const的。要修改传递进来的a的拷贝,可以添加mutable修饰符。
  • &a。将a按引用进行传递。
  • a, &b。将a按值进行传递,b按引用进行传递。
  • =,&a, &b。除a和b按引用进行传递外,其他参数都按值进行传递。
  • &, a, b。除a和b按值进行传递外,其他参数都按引用进行传递。
      int m = 0, n = 0;
      [=] (int a) mutable { m = ++n + a; }(4);
      [&] (int a) { m = ++n + a; }(4);
      [=,&m] (int a) mutable { m = ++n + a; }(4);
      [&,m] (int a) mutable { m = ++n + a; }(4);
      [m,n] (int a) mutable { m = ++n + a; }(4);
      [&m,&n] (int a) { m = ++n + a; }(4);

(2)操作符重载函数参数;
标识重载的()操作符的参数,没有参数时,这部分可以省略。参数可以通过按值(如:(a,b))和按引用(如:(&a,&b))两种方式进行传递。

(3)可修改标示符;
mutable声明,这部分可以省略。按值传递函数对象参数时,加上mutable修饰符后,可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)。

(4) 错误抛出标示符;
exception声明,这部分也可以省略。exception声明用于指定函数抛出的异常,如抛出整数类型的异常,可以使用throw(int)

(5) 函数返回值;
->返回值类型,标识函数返回值的类型,当返回值为void,或者函数体中只有一处return的地方(此时编译器可以自动推断出返回值类型)时,这部分可以省略。

(6)函数体;
{},标识函数的实现,这部分不能省略,但函数体可以为空。

2多种连接情况

信号与槽函数的连接有多种情况,如:一个信号连接一个槽、一个信号连接多个槽及多个信号连接同一个槽等等。


信号槽连接示意图
1. 一个信号连接一个槽
    connect(slider, &QSlider::valueChanged, spin_box, &QSpinBox::setValue);
2. 一个信号连接多个槽
    connect(slider, &QSlider::valueChanged, spin_box, &QSpinBox::setValue);
    connect(slider, &QSlider::valueChanged, this, &QWidget::showValue);
3. 多个信号连接同一个槽
    connect(push_button, &QPushButton::clicked, this, &QWidget::show);
    connect(tool_button, &QToolButton::clicked, this, &QWidget::show);
4. 一个信号连接另一个信号
    connect(push_button, &QPushlButton::clicked, this, &QWidget::buttonClicked);

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

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

2-3断开信号和槽

取消函数为disconnect,参数形式与connect相对应。断开的情况可分为如下三种:

  1. 断开与某个对象相关联的任何对象
disconnect(&ma, 0, 0, 0);   断开与对象 ma 中的所有信号相关联的所有槽。
当我们在某个对象中定义了一个或者多个信号,这些信号与另外若干个对象中的槽相关联,如果我们要切断这些关联的话,就可以利用这个方法,非常之简洁
  1. 断开与某个特定信号的任何关联
disconnect( myObject, SIGNAL(mySignal()), 0, 0 ) 
或者myObject->disconnect( SIGNAL(mySignal()) )
  1. 断开两个对象之间的关联
disconnect( myObject, 0, myReceiver, 0 ) 
或者 myObject->disconnect(  myReceiver )

2-4总结

  1. 简单介绍了支持信号槽机制的元对象系统。
  2. 信号的标志符为signals,槽函数的标志符为slots,发射信号需要使用关键字emit。
  3. 通过函数connect连接信号与槽,通过disconnect取消信号与槽的连接。

参考资料
[1] https://www.ibm.com/developerworks/cn/linux/guitoolkit/qt/signal-slot/index.html
[2] https://blog.csdn.net/hyongilfmmm/article/details/83015045
[3] https://www.jianshu.com/p/d6fc0bb0689a
[4] https://blog.csdn.net/alzzw/article/details/99628409

你可能感兴趣的:(Qt 信号槽)