Qt编程中的信号和槽机制
在使用自定义类创建一个按钮之后,只能看到一个按钮的图形,但是使用鼠标点击并无任何反应,下面想要实现一个“点击按钮可以关闭窗口”的功能。
关闭窗口的功能可以被抽象为以下对象和操作,即:按钮,点击按钮,窗口,关闭窗口。在qt
中使用connect
实现这个过程。
connet(信号的发送者,发送的具体信号,信号的接受者,信号的处理(槽))
信号槽的一个优点:松散耦合,即信号的发送端和接收端本身是没有关联的,通过connect
连接,将两端耦合在一起。
// myself button
MyPushButton *myBtn = new MyPushButton;
myBtn->setText("mybtn");
myBtn->setParent(this);
myBtn->move(10, 0);
myBtn->resize(200, 100);
// close the window
connect(myBtn, &QPushButton::clicked, this, &myWidget::close);
// 通过帮助文档查找按钮对应的信号 signals & slots
以上即 信号和槽 的简单功能演示。其中用到的信号和槽是父类中已经定义好的用来实现特定的功能,下面来实现自定义的信号和槽功能。过程为:跑完步,张三口渴拿起杯子喝水。新建一个类,在自动生成的类声明位置,可以看到其中包含放置自定义信号的位置,信号只需要声明,不需要实现,可以包含参数但不需要返回值。如下,信号就定义好了。
class Person : public QObject
{
Q_OBJECT
public:
explicit Person(QObject *parent = nullptr);
signals:
void thirsty();
};
接下来是实现接收方的槽函数,早期版本槽函数必须写在类中的特定位置,5.4版本之后也可以将槽函数写在public
中。槽函数既需要声明又需要实现,返回值为void
,同样可以传入参数。
class Cup : public QObject
{
Q_OBJECT
public:
explicit Cup(QObject *parent = nullptr);
signals:
public slots:
void drink(); // 还需到源文件中去定义
};
此时将信号和槽连接起来还不能看到效果,是因为信号未触发,只有信号触发后,与该信号连接的槽才能做出反应。
void Widget::overRun()
{
emit zhangsan->thirsty();
}
信号发出后,与之相连的槽做出反应。
zhangsan = new Person(this);
beizi = new Cup(this);
connect(zhangsan, &Person::thirsty, beizi, &Cup::drink);
overRun();
信号和槽都可以传入参数,可以根据需要分别对其进行重载。注意,引用重载后需要利用函数指针明确指向具体函数。
// class Person
signals:
void thirsty(QString WaterName);
// class Cup
public slots:
void drink(QString WaterName);
// cup 定义
void Cup::drink(QString Watername)
{
qDebug() << "drink water, " << Watername;
}
// widget 定义
void Widget::overRun()
{
emit zhangsan->thirsty("Nongfu Water");
}
void (Person:: *zhangsanSignal)(QString) = &Person::thirsty;
void (Cup:: *beiziSlots)(QString) = &Cup::drink;
connect(zhangsan, zhangsanSignal, beizi, beiziSlots);
overRun();
输出结果为:drink water, "Nongfu Water"
,可以看到Nongfu Water
被引号引起来了,想要去掉引号可以这样做:
qDebug() << "drink water, " << Watername.toUtf8().data();
// toUtf8()将QString转化为QByteArray,再使用data()将其转化为 char *
上面的实现中张三想要喝水的信号是调用函数触发的,下面想通过点击按钮的方式触发张三喝水的动作。
void (Person:: *zhangsanSignal)(QString) = &Person::thirsty;
void (Cup:: *beiziSlots)(QString) = &Cup::drink;
connect(zhangsan, zhangsanSignal, beizi, beiziSlots);
QPushButton *btn = new QPushButton("跑步结束", this);
btn->move(50, 50);
resize(300, 200);
connect(btn, &QPushButton::clicked, this, &Widget::overRun);
上面的实现中,相当于点击按钮后将信号传递给了widget
下的槽函数overRun
,也可以通过信号传递给信号的方式,点击按钮直接触发张三口渴的信号,如下:
void (Person:: *zhangsanSignal)(void) = &Person::thirsty;
void (Cup:: *beiziSlots)(void) = &Cup::drink;
connect(zhangsan, zhangsanSignal, beizi, beiziSlots);
connect(btn, &QPushButton::clicked, zhangsan, zhangsanSignal);
信号连接之后,也可以通过disconnect()
断开连接,其参数与想要断开的连接中的参数相同,例如断开张三拿起杯子喝水的连接:
disconnect(zhangsan, zhangsanSignal, beizi, beiziSlots);
信号和槽要注意的几个点:
1、信号可以连接信号;
2、一个信号可以连接多个槽函数;
3、多个信号可以连接同一个槽函数;
4、信号和槽函数的参数需要一一对应;
5、在参数对应的前提下,信号的参数可以多于槽函数的参数个数。
补充:Qt4信号槽连接方式connect(zhangsan, SIGNAL(thirsty()), beizi, SLOT(drink()));
,相对于更高版本,QT4连接参数直观,但编辑器不进行参数类型检测。因为QT4底层实现时相当于将thirsty()
转化成字符串然后去搜索该函数,没有对字符串进行比较。