【QT】——信号和槽
信号和槽是 Qt 特有的信息传输机制,是 Qt 设计程序的重要基础,它可以让互不干扰的 对象建立一种联系,当信号发出时,被连接的槽函数会自动被回调。这就类似观察者模式:当发生了感兴趣的事件,某一个操作就会被自动触发。
1. 信号和槽的概念
信号和槽是 Qt 特有的信息传输机制,是 Qt 设计程序的重要基础,它可以让互不干扰的 对象建立一种联系,当信号发出时,被连接的槽函数会自动被回调。这就类似观察者模式:当发生了感兴趣的事件,某一个操作就会被自动触发。
1.1 信号的本质
信号是由于用户对窗口或控件进行了某些操作,导致窗口或控件产生了某个特定事件,这时候 Qt 对应的窗口类会发出某个信号,以此对用户的挑选做出反应。
信号的本质是事件:
- 按钮单击、双击
- 窗口刷新
- 鼠标移动、鼠标按下、鼠标释放
- 键盘输入
- ....
1.2 槽的本质
槽的本质是类的成员函数,其参数可以是任意类型的。
- 槽函数和普通 C++成员函数几乎没有区别,它可以是虚函数;也可以被重载;可以是公有的、保护的、私有的、也可以被其他 C++ 成员函数调用
- 唯一区别的是:槽可以与信号连接在一起,每当和槽连接的信号被发射的时 候,就会调用这个槽
1.3 信号和槽的关系
- 在 Qt 中 信号和槽函数 都是独立的个体,本身没有任何联系,但是由于某种特性需求我们可以将二者连接到一起。
- 在 Qt 中我们需要使用 QOjbect类中的 connect 函数进二者的关联。
- 一个信号可以连接到多个槽。
- 一个槽可以被多个信号连接。
- 信号也可以连接到信号,此时前者的发射信号将导致后者的发射。
- 信号的参数类型可以与槽的参数类型对应,信号的参数可以比槽的参数多,但不可以少, 否则连接将失败。
connect(const QObject *sender, &QObject::signal,
const QObject *receiver, &QObject::method);
connect中的参数:
- sender:发送信号的对象
- signal:属于sender对象, 信号是一个函数, 这个参数的类型是函数指针, 信号函数地址
- receIver:信号接收者
- method:属于于receiver对象, 当检测到sender发出了signal信号, receiver对象调用method方法,信号发出之后的处理动作。
使用connect()进行信号槽连接的注意事项:
- connect函数相对于做了信号处理动作的注册。
- 调用conenct函数的sender对象的信号并没有产生, 因此receiver对象的 method 也不会被调用
- method槽函数本质是一个回调函数, 调用的时机是信号产生之后, 调用是Qt框架来执行的
- connect中的sender和recever两个指针必须被实例化了, 否则conenct不会成功
2. 标准信号/槽的使用
在 Qt 提供的很多标准类中都可以对用户触发的某些特定事件进行检测,因此当用户做了这些操作之后,事件被触发类的内部就会产生对应的信号,这些信号都是 Qt 类内部自带的,因此称之为标准信号。
如何查找控件的信号或槽函数?
1.进入QT文档,点击查找,比如我想查找QPushButton的信号和槽函数。
2.槽函数实在Public Slots中,信号函数是在Signals中,如果没有Signals,我们可以到它的基类中查找是否有Signals,也可以使用基类中的信号和槽函数
使用
功能实现:点击按钮,窗口。
// 单击按钮发出的信号
[signal] void QAbstractButton::clicked(bool checked = false)
// 关闭窗口的槽函数
[slot] bool QWidget::close()
// 单击按钮关闭窗口
connect(ui->closewindow, &QPushButton::clicked, this, &MainWindow::close);
- connect()操作一般写在窗口的构造函数中, 相当于在事件产生之前在qt框架中先进行注册。
- 这样在程序运行过程中假设产生了按钮的点击事件, 框架就会调用信号接收者对象对应的槽函数了, 如果信号不产生, 槽函数也就一直不会被调用
3. 自定义信号和槽的使用
除了QT提供的标准信号和槽函数外,我们也可以自定义信号和 槽函数。
自定义信号和槽函数的条件:
- 需要将信号 或槽函数 放在一个自定义类中。
- 自定义的类必须 继承QObject 或者 QObject 的子类。
- 在定义类中头文件加入 Q_Object宏。
3.1 自定义信号
在 Qt 中信号的本质是事件,但是在框架中也是以函数的形式存在的,只不过信号对应的函数只有声明,没有定义。
自定义信号的要求:
- 信号是类的成员函数;
- 返回值必须是void类型
- 信号名称可以根据实际情况进行指定。
- 信号可以支持重载
- 信号需要使用signals关键字进行声明,使用方法类似public等关键字
- 信号只需要声明,不需要定义(没有函数体实现)
- 习惯性在信号函数前加关键字: emit, 但是可以省略不写
emit只是显示的声明一下信号要被发射了, 没有特殊含义
底层 emit == #define emit
如下:
class myWindow:public QObject
{
Q_OBJECT;
signals:
void my_signal1(int a);
void my_signal1();
//信号的参数的作用是传递给 槽函数
};
3.2 自定义槽函数
槽函数就是信号的处理动作,在 Qt 中槽函数可以作为普通的成员函数来使用,自定义槽函数和自定义的普通函数写法是一样的。
自定义槽函数的要求:
- 返回值必须是void类型
- 槽函数支持重载
- Qt 中的槽函数可以是类的成员函数、全局函数、静态函数、Lambda表达式(匿名函数)
- 槽函数的参数是用来接收信号传递的数据的, 信号传递的数据就是信号的参数,信号函数和槽函数的参数类型需要进行匹配。
举例:
信号函数:void my_slots(int a);
槽函数:void my_signal1(int a);
信号的参数可以大于等于槽函数的个数:
//下面的信号和槽函数可以用connect进行链接
//其中槽函数只接受 int类型参数
信号函数: void testsig (int a, double b);
槽函数: void testslot (int a);
//下面的信号和槽函数不能用connect进行链,因为参数不匹配
信号函数: void testsig (QString a);
槽函数: void testslot (int a);
- 槽函数可以使用关键字进行声明: slots (Qt5中slots可以省略不写)
public slots:
private slots: –> 这样的槽函数不能在类外部被调用
protected slots: –> 这样的槽函数不能在类外部被调用
实例:
需求:点击一下按钮,则闹钟响了,我起来做饭
定义闹钟类:
class my_clock:public QObject
{
Q_OBJECT;
signals:
void alarm();
};
定义个人类:
class me:public QObject{
Q_OBJECT;
public slots:
void Do()//槽函数
{
qDebug()<<"起床做饭";
}
};
QMainWindow类:
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
public slots:
void m_trigger(){
c->alarm();
}
private:
Ui::MainWindow *ui;
me* m;
my_clock* c;
};
QMainWindow的构造函数:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
m=new me;
c=new my_clock;
//点击按钮发出的click信号,被QMaindow接收,触发m_trigger槽函数
//m_trigger槽函数中触发 my_clock 发送alarm信号
//me接收到 alarm信号,触发Do槽函数,打印起床做饭
connect(ui->button,&QPushButton::clicked,this,&MainWindow::m_trigger);
connect(c,&my_clock::alarm,m,&me::Do);
}
展示效果:
4. 信号和槽的两种连接方式
connect中有两种连接方式,一种是QT4,另一种是QT5.他们的主要差别是 参数 信号 和 槽函数的差别。
4.1 QT4的连接方式
- QT4中中的信号和槽函数的参数是 字符串类型,信号槽函数通过宏 SIGNAL 和 SLOT 转换为字符串类型。
- 一般是不推荐使用QT4连接方式,因为信号槽函数的转换是通过宏来进行转换的,因此传递到宏函数内部的数据不会被进行检测, 如果使用者传错了数据,编译器也不会报错
- QT4中声明槽函数必须使用slots关键字,不能省略
[static] QMetaObject::Connection QObject::connect(
const QObject *sender, const char *signal,
const QObject *receiver, const char *method,
Qt::ConnectionType type = Qt::AutoConnection);
connect(const QObject *sender,SIGNAL(信号函数名(参数1, 参数2, ...)),
const QObject *receiver,SLOT(槽函数名(参数1, 参数2, ...)));
4.2 QT5的连接方式
QT5中的信号和槽函数的参数是 函数指针:
// 信号和槽函数也就是第2,4个参数传递的是地址, 编译器在编译过程中会对数据的正确性进行检测
connect(const QObject *sender, &QObject::signal,
const QObject *receiver, &QObject::method);
4.3 实例:
场景描述:我肚子饿了,我要吃东西。
QT的连接方式:
class Me:QObject{
Q_OBJECT;
public:
void eat(){
qDebug()<<"我要吃酸菜鱼";
}
void eat(QString s){
qDebug()<<"我要吃"<<s;
}
signals:
void hungry();
void hungry(QString s);
};
Me m;
connect(m,SIGNAL(hungry()),m,SLOT(eat()));
connect(m,SIGNAL(hungry(QString)),m,SLOT(eat(QString)));
QT5的连接方式:
// Qt5处理方式
connect(&m, &Me::eat, &m, &Me::hungury); // error
上面这种连接方式是错误的,,信号和槽都是通过函数名去关联函数的地址, 但是这个同名函数对应两块不同的地址, 一个带参, 一个不带参, 因此编译器就不知道去关联哪块地址了, 所以如果我们在这种时候通过以上方式进行信号槽连接, 编译器就会报错。
正确的做法:
- 通过定义函数指针的方式指定出函数的具体参数,这样就可以确定函数的具体地址了。
- 例如使用将带QString参数的
void (Me::*m_slot)(QString)=&Me::eat; //指定带参数Qtring的 槽函数
void (Me::*m_signal)(QString) = &Me::hungry;//指定带参数Qtring的信号
connect(&m,m_slot,&m,m_signal);//连接
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)