信号与槽
信号和槽机制是Qt 的核心机制之一,要掌握Qt 编程就需要对信号和槽有所了解。信号和槽是一种高级接口,它们被应用于对象之间的通信,它们是Qt 的核心特性,也是Qt不同于其它同类工具包的重要地方之一。
在我们所了解的其它GUI 工具包中,窗口小部件(widget)都有一个回调函数用于响应它们触发的动作,这个回调函数通常是一个指向某个函数的指针。在Qt 中用信号和槽取代了上述机制。
信号(signal)
当对象的状态发生改变时,信号被某一个对象发射(emit)。只有定义过这个信号的类或者其派生类能够发射这个信号。当一个信号被发射时,与其相关联的槽将被执行,就象一个正常的函数调用一样。信号-槽机制独立于任何GUI 事件循环。只有当所有的槽正确返回以后,发射函数(emit)才返回。
如果存在多个槽与某个信号相关联,那么,当这个信号被发射时,这些槽将会一个接一个地被执行,但是它们执行的顺序将会是不确定的,并且我们不能指定它们执行的顺序。
信号的声明是在头文件中进行的,并且moc 工具会注意不要将信号定义在实现文件中。Qt 用signals 关键字标识信号声明区,随后即可声明自己的信号。例如,下面定义了几个信号:
signals:
void yourSignal();
void yourSignal(int x);
在上面的语句中,signals 是Qt 的关键字。接下来的一行void yourSignal(); 定义了信号yourSignal,这个信号没有携带参数;接下来的一行void yourSignal(int x);定义了信号yourSignal(int x),但是它携带一个整形参数,这种情形类似于重载。注意,信号和槽函数的声明一般位于头文件中,同时在类声明的开始位置必须加上Q_OBJECT 语句,这条语句是不可缺少的,它将告诉编译器在编译之前必须先应用moc 工具进行扩展。关键字signals 指出随后开始信号的声明,这里signals 用的是复数形式而非单数,siganls没有public、private、protected 等属性,这点不同于slots。另外,signals、slots 关键字是QT 自己定义的,不是C++中的关键字。
还有,信号的声明类似于函数的声明而非变量的声明,左边要有类型,右边要有括号,如果要向槽中传递参数的话,在括号中指定每个形式参数的类型,当然,形式参数的个数可以多于一个。
从形式上讲,信号的声明与普通的C++函数是一样的,但是信号没有定义函数实现。另外,信号的返回类型都是void,而C++函数的返回值可以有丰富的类型。
注意,signal 代码会由moc 自动生成,moc 将其转化为标准的C++语句,C++预处理器会认为自己处理的是标准C++源文件。所以大家不要在自己的C++实现文件实现signal。
槽(slot)
槽是普通的C++成员函数,可以被正常调用,不同之处是它们可以与信号( signal)相关联。当与其关联的信号被发射时,这个槽就会被调用。槽可以有参数,但槽的参数不能有缺省值。
槽也和普通成员函数一样有访问权限。槽的访问权限决定了谁可以和它相连。通常,槽也分为三种类型,即public slots、private slots 和protected slots。
public slots:在这个代码区段内声明的槽意味着任何对象都可将信号与之相连接。
这对于组件编程来说非常有用:你生成了许多对象,它们互相并不知道,把它们的信号和槽连接起来,这样信息就可以正确地传递,并且就像一个小孩子喜欢玩耍的铁路轨道上的火车模型,把它打开然后让它跑起来。
protected slots:在这个代码区段内声明的槽意味着当前类及其子类可以将信号与之相关联。这些槽只是类的实现的一部分,而不是它和外界的接口。
private slots:在这个代码区段内声明的槽意味着只有类自己可以将信号与之相关联。这就是说这些槽和这个类是非常紧密的,甚至它的子类都没有获得连接权利这样的信任。
通常,我们使用public 和private 声明槽是比较常见的,建议尽量不要使用protected 关键字来修饰槽的属性。此外,槽也能够声明为虚函数。
槽的声明也是在头文件中进行的。例如,下面声明了几个槽:
public slots:
void yourSlot();
void yourSlot(int x);
注意,关键字slots 指出随后开始槽的声明,这里slots 用的也是复数形式。
信号与槽的关联
槽和普通的C++成员函数几乎是一样的-可以是虚函数;可以被重载;可以是共有的、保护的或是私有的,并且也可以被其它C++成员函数直接调用;还有,它们的参数可以是任意类型。唯一不同的是:槽还可以和信号连接在一起,在这种情况下,每当发射这个信号的时候,就会自动调用这个槽。
connect()语句看起来会是如下的样子:
connect(sender,SIGNAL(signal),receiver,SLOT(slot));
这里的sender 和receiver 是指向QObject 的指针,signal 和slot 是不带参数的函数
名。实际上,SIGNAL()宏和SLOT()会把它们的参数转换成相应的字符串。
到目前为止,在已经看到的实例中,我们已经把不同的信号和不同的槽连接在了一
起。但这里还需要考虑一些其他的可能性。
⑴ 一个信号可以连接多个槽
connect(slider,SIGNAL(valueChanged(int)),spinBox,SLOT(setValue(int)));
connect(slider,SIGNAL(valueChanged(int)),this,SLOT(updateStatusBarIndicator(int)));
在发射这个信号的时候,会以不确定的顺序一个接一个的调用这些槽。
⑵ 多个信号可以连接同一个槽
connect()
无论发射的是哪一个信号,都会调用这个槽。
⑶ 一个信号可以与另外一个信号相连接
connect(lineEdit,SIGNAL(textChanged(constQstring &)),this,SIGNAL(updateRecord(const Qstring &)));
当发射第一个信号时,也会发射第二个信号。除此之外,信号与信号之间的连接和信号与槽之间的连接是难以区分的。
⑷ 连接可以被移除
disconnect(lcd,SIGNAL(overflow()),this,SLOT(handleMathError()));
这种情况较少用到,因为当删除对象时,Qt 会自动移除和这个对象相关的所有连接。
⑸ 要把信号成功连接到槽(或者连接到另外一个信号),它们的参数必须具有相同的顺序和相同的类型
connect(ftp,SIGNAL(rawCommandReply(int,constQString &)),this,SLOT(processReply(int,const QString &)));
⑹ 如果信号的参数比它所连接的槽的参数多,那么多余的参数将会被简单的忽略掉connect(ftp,SIGNAL(rawCommandReply(int,const Qstring
&)),this,SLOT(checkErrorCode(int)));
还有,如果参数类型不匹配,或者如果信号或槽不存在,则当应用程序使用调试模式构建后,Qt 会在运行时发出警告。与之相类似的是,如果在信号和槽的名字中包含了参数名,Qt 也会发出警告。
信号和槽机制本身是在QObject 中实现的,并不只局限于图形用户界面编程中。这种机制可以用于任何QObject 的子类中。
当指定信号signal 时必须使用Qt 的宏SIGNAL(),当指定槽函数时必须使用宏SLOT()。如果发射者与接收者属于同一个对象的话,那么在connect调用中接收者参数可以省略。
例如,下面定义了两个对象:标签对象label 和滚动条对象scroll,并将
valueChanged()信号与标签对象的setNum()相关联,另外信号还携带了一个整形参数,这样标签总是显示滚动条所处位置的值。
QLabel *label = new QLabel;
QScrollBar *scroll = new QScrollBar;
QObject::connect( scroll, SIGNAL(valueChanged(int)),
label, SLOT(setNum(int)) );
实例剖析
信号的定义只需要声明和在程序体中使用emit就可以了,比如,我们将实现按一个按钮就发送一个信号,这个信号附带有参数。
在头文件中要进行声明:
signals:
void setvalue(const QString &);
在源文件中
void Keypad::enterClicked()//确认键
{
emit setvalue(display->text());
}
此时,将display控件里面的字符串发送出去。
在另外一个源文件中,书写语句
connect(vk, SIGNAL(setvalue(const QString &)), this, SLOT(setPinlv(const QString &)));
意义就是把发送信号跟槽链接起来,也就是说,当vk界面的那个按钮按下之后,发送的信号就会被这个界面的槽收到。
槽的定义需要在头文件中进行声明还有在源文件中书写槽函数
在头文件中:
private slots:
void setPinlv(const QString &q); //声明的时候不需要写是那个控件的函数
在源文件中:
void MainWindow::setPinlv(const QString &q)
{
pinlv = q.toInt(); //注意此处使用的是q
printf("the pinlv is %d",pinlv);
}