信号与槽进阶
信号与槽的机制是 Qt 编程的基础。它使得应用程序程序员将对象与对象无隔阂的绑定在一起。我们已经将一些信号和槽连接在一起,声明了自己的信号和槽并实现了我们自己的槽,发出了自己信号。让我们花一些时间来仔细研究一下这一机制。
槽和普通的 C++ 成员函数几乎完全相同。它们可以使虚函数;可以被重载;可以是公开、保护或私有的,它们可以象其它 C++ 成员函数一样被直接调用;并且参数可以是任何数据类型。不同的是一个槽可以被连接到一个信号,在这种情况下当信号发出时它可以被自动调用。
connect() 语句看起来是这样的:
connect(sender, SIGNAL(signal), receiver, SLOT(slot));
sender 和 receiver 是指向 QObjects 的指针,signal 和 slot 是不带参数名的函数签名。SIGNAL() 和 SLOT() 宏本质上将它们的参数转化成字符串。
在我们已经看到的例子中,我们总是连接不同的信号和不同的槽,其实,还有其它可能的选择。
-
一个信号可以被连接到多个槽上:
connect(slider, SIGNAL(valueChanged(int)),
spinBox, SLOT(setValue(int)));
connect(slider, SIGNAL(valueChanged(int)),
this, SLOT(updateStatusBarIndicator(int)));
当信号发出时,槽会按一个非特定的顺序被轮流调用。
-
多个信号可以被连接到同一个槽上:
connect(lcd, SIGNAL(overflow()),
this, SLOT(handleMathError()));
connect(calculator, SIGNAL(divisionByZero()),
this, SLOT(handleMathError()));
当任何一个信号发出时,槽都会被调用。
connect(lineEdit, SIGNAL(textChanged(const QString &)),
this, SIGNAL(updateRecord(const QString &)));
当地一个信号被发出时,第二个信号也会发出。除此之外,信号与信号的连接和信号与槽的连接没有任何不同。
-
连接可以被取消:
disconnect(lcd, SIGNAL(overflow()),
this, SLOT(handleMathError()));
这不是必需的,因为当对象被删除后 Qt 会自动取消与对象相关的连接。
要成功连接一个信号到一个槽 (或到另一个信号)上,你必需拥相同的参数类型和顺序:
connect(ftp, SIGNAL(rawCommandReply(int, const QString &)),
this, SLOT(processReply(int, const QString &)));
另外,如果一个信号比要连接槽拥有更多的参数,多余的参数会被简单的忽略掉:
connect(ftp, SIGNAL(rawCommandReply(int, const QString &)),
this, SLOT(checkErrorCode(int)));
如果参数类型不兼容,又或者信号或槽不存在,如果应用程序在调试模式下编译Qt 会发出一个运行时警告。同样的,如果参数名被包含进信号或者槽的签名,Qt 会给出一个警告。
到目前为止,我们只用到了物件的信号与槽。但是信号与槽的机制本身是在 QObject 中实现的并且不仅仅用在 GUI 编程上。这个机制可以被 QObject 任何子类使用。:
class Employee : public QObject
{
Q_OBJECT
public:
Employee() { mySalary = 0; }
int salary() const { return mySalary; }
public slots:
void setSalary(int newSalary);
signals:
void salaryChanged(int newSalary);
private:
int mySalary;
};
void Employee::setSalary(int newSalary)
{
if (newSalary != mySalary) {
mySalary = newSalary;
emit salaryChanged(mySalary);
}
}
注意 setSalary () 是如何实现的。 我们仅当newSalary != mySalary成立的时候才发出salaryChanged()信号。这确保循环连接不会进入死循环。