信号与槽
信号和槽机制是Qt的核心机制之一,要掌握Qt编程就需要对信号和槽有所了解。信号和槽是一种高级接口,它们被应用于对象之间的通信,它们是Qt的核心特性,也是Qt不同于其它同类工具包的重要地方之一。
在我们所了解的其它GUI工具包中,窗口小部件(widget)都有一个回调函数用于响应它们触发的动作,这个回调函数通常是一个指向某个函数的指针。在Qt中用信号和槽取代了上述机制。
1.信号(signal)
当对象的状态发生改变时,信号被某一个对象发射(emit)。只有定义过这个信号的类或者其派生类能够发射这个信号。当一个信号被发射时,与其相关联的槽将被执行,就象一个正常的函数调用一样。信号-槽机制独立于任何GUI事件循环。只有当所有的槽正确返回以后,发射函数(emit)才返回。
如果存在多个槽与某个信号相关联,那么,当这个信号被发射时,这些槽将会一个接一个地被执行,但是它们执行的顺序将会是不确定的,并且我们不能指定它们执行的顺序。
信号的声明是在头文件中进行的,并且moc工具会注意不要将信号定义在实现文件中。Qt用signals关键字标识信号声明区,随后即可声明自己的信号。
注意,信号和槽函数的声明一般位于头文件中,同时在类声明的开始位置必须加上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。
代码widgt.h信号模块
//信号声明区
signals:
/*
* 当某个信号对其客户或所有者发生的内部状态发生改变,
* 信号被一个对象发射。只有 定义过这个信号的类及其派生类能够发射这个信号。
* 当一个信号被发射时,与其相关联的槽将被立刻执行,
* 就象一个正常的函数调用一样。信号-槽机制完全独立于任何GUI事件循环。
* 只有当所有的槽返回以后发射函数(emit)才返回。
*/
//声明信号mySignal()
void mySignal();
//声明信号mySignal(int)
void mySignal(int x);
//声明信号mySignalParam(int,int)
void mySignalParam(int x,int y);
2.槽(slot)
槽是普通的C++成员函数,可以被正常调用,不同之处是它们可以与信号(signal)相关联。当与其关联的信号被发射时,这个槽就会被调用。槽可以有参数,但槽的参数不能有缺省值。
槽也和普通成员函数一样有访问权限。槽的访问权限决定了谁可以和它相连。通常,槽也分为三种类型,即public slots、private slots和protected slots。
public slots:在这个代码区段内声明的槽意味着任何对象都可将信号与之相连接。这对于组件编程来说非常有用:你生成了许多对象,它们互相并不知道,把它们的信号和槽连接起来,这样信息就可以正确地传递,并且就像一个小孩子喜欢玩耍的铁路轨道上的火车模型,把它打开然后让它跑起来。
protected slots:在这个代码区段内声明的槽意味着当前类及其子类可以将信号与之相关联。这些槽只是类的实现的一部分,而不是它和外界的接口。
private slots:在这个代码区段内声明的槽意味着只有类自己可以将信号与之相关联。这就是说这些槽和这个类是非常紧密的,甚至它的子类都没有获得连接权利这样的信任。
通常,我们使用public和private声明槽是比较常见的,建议尽量不要使用protected关键字来修饰槽的属性。此外,槽也能够声明为虚函数。
槽的声明也是在头文件中进行的。
代码槽函数模块 widget.h
//槽声明区
public slots:
/*槽是普通的C++成员函数,可以被正常调用,它们唯一的特殊性就是很多信号可以与其相关联。
* 当与其关联的信号被发射时,这个槽就会被调用。槽可以有参数,但槽的参数不能有缺省值。
既然槽是普通的成员函数,因此与其它的函数一样,它们也有存取权限。
槽的存取权限决定了谁能够与其相关联。同普通的C++成员函数一样,
槽函数也分为三种类型,即public slots、private slots和protected slots。
public slots:在这个区内声明的槽意味着任何对象都可将信号与之相连接。
这对于组件编程非常有用,你可以创建彼此互不了解的对象,
将它们的信号与槽进行连接以便信息能够正确的传递。
protected slots:在这个区内声明的槽意味着当前类及其子类可以将信号与之相连接。
这适用于那些槽,它们是类实现的一部分,但是其界面接口却面向外部。
private slots:在这个区内声明的槽意味着只有类自己可以将信号与之相连接。
这适用于联系非常紧密的类。
槽也能够声明为虚函数,这也是非常有用的。
*/
//声明槽函数mySlot()
void mySlot();
//声明槽函数mySlot(int)
void mySlot(int x);
//声明槽函数mySignalParam (int,int)
void mySlotParam(int x,int y);
一个信号对应一个槽函数
代码widget.h 全部
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QMessageBox>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
void slotFileNew();
//信号声明区
signals:
/*
* 当某个信号对其客户或所有者发生的内部状态发生改变,
* 信号被一个对象发射。只有 定义过这个信号的类及其派生类能够发射这个信号。
* 当一个信号被发射时,与其相关联的槽将被立刻执行,
* 就象一个正常的函数调用一样。信号-槽机制完全独立于任何GUI事件循环。
* 只有当所有的槽返回以后发射函数(emit)才返回。
*/
//声明信号mySignal()
void mySignal();
//声明信号mySignal(int)
void mySignal(int x);
//声明信号mySignalParam(int,int)
void mySignalParam(int x,int y);
//槽声明区
public slots:
/*槽是普通的C++成员函数,可以被正常调用,它们唯一的特殊性就是很多信号可以与其相关联。
* 当与其关联的信号被发射时,这个槽就会被调用。槽可以有参数,但槽的参数不能有缺省值。
既然槽是普通的成员函数,因此与其它的函数一样,它们也有存取权限。
槽的存取权限决定了谁能够与其相关联。同普通的C++成员函数一样,
槽函数也分为三种类型,即public slots、private slots和protected slots。
public slots:在这个区内声明的槽意味着任何对象都可将信号与之相连接。
这对于组件编程非常有用,你可以创建彼此互不了解的对象,
将它们的信号与槽进行连接以便信息能够正确的传递。
protected slots:在这个区内声明的槽意味着当前类及其子类可以将信号与之相连接。
这适用于那些槽,它们是类实现的一部分,但是其界面接口却面向外部。
private slots:在这个区内声明的槽意味着只有类自己可以将信号与之相连接。
这适用于联系非常紧密的类。
槽也能够声明为虚函数,这也是非常有用的。
*/
//声明槽函数mySlot()
void mySlot();
//声明槽函数mySlot(int)
void mySlot(int x);
//声明槽函数mySignalParam (int,int)
void mySlotParam(int x,int y);
private slots:
private:
Ui::Widget *ui;
QString input="0";
};
#endif // WIDGET_H
/*信号 补充
* 如果存在多个槽与某个信号相关联,那么,当这个信号被发射时,
* 这些槽将会一个接一个地 执行,
* 但是它们执行的顺序将会是随机的、不确定的,
* 我们不能人为地指定哪个先执行、哪 个后执行。
*
* 从形式上 讲信号的声明与普通的C++函数是一样的,
* 但是信号却没有函数体定义,另外,信号的返回 类型都是void,
* 不要指望能从信号返回什么有用信息。
信号由moc自动产生,它们不应该在.cpp文件中实现。
moc 全称是 Meta-Object Compiler,也就是“元对象编译器” 其实就是预处理
只有用到信号signals和槽slots时才会用到MOC,
因为采用信号signals和槽slots是QT的特性,
而C++没有,所以采用了MOC(元对象编译器)把信号signals和槽slots部分编译成C++语言.
用信号signals和槽slots需注意的基本问题是:
1)在类class声明中必须加入Q_OBJECT;
2)在CPP文件中要把信号signals和槽slots联系起来,
即使用connect,例connect( iv, SIGNAL(clicked (QIconViewItem *)), this, SLOT( draw()));
* */
3.信号与槽的关联
槽和普通的C++成员函数几乎是一样的-可以是虚函数;可以被重载;可以是共有的、保护的或是私有的,并且也可以被其它C++成员函数直接调用;还有,它们的参数可以是任意类型。唯一不同的是:槽还可以和信号连接在一起,在这种情况下,每当发射这个信号的时候,就会自动调用这个槽。
定义widget.cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
//将信号mySignal()与槽mySlot()相关联
connect(this,SIGNAL(mySignal()),SLOT(mySlot()));
//将信号mySignal(int)与槽mySlot(int)相关联
connect(this,SIGNAL(mySignal(int)),SLOT(mySlot(int)));
//将信号mySignalParam(int,int)与槽mySlotParam(int,int)相关联
connect(this,SIGNAL(mySignalParam(int,int)),SLOT(mySlotParam(int,int)));
}
//static void about(QWidget *parent, const QString &title, const QString &text);
// 定义槽函数mySlot()
void Widget::mySlot()
{
QMessageBox::about(this,"信号类", "这是一个没有参数的信号/槽.");
}
// 定义槽函数mySlot(int)
void Widget::mySlot(int x)
{
QMessageBox::about(this,"信号类", "这是有一个参数的信号/槽.");
}
// 定义槽函数mySlotParam(int,int)
void Widget::mySlotParam(int x,int y)
{
char s[256];
sprintf(s,"x:%d y:%d",x,y);
QMessageBox::about(this,"信号类", s);
}
void Widget::slotFileNew()
{
//发射信号mySignal(int)
emit mySignal(5);
//发射信号mySignalParam(5,100)
emit mySignalParam(5,100);
//发射信号mySignal()
emit mySignal();
}
Widget::~Widget()
{
delete ui;
}
最后补上 widget.ui代码
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Widget</class>
<widget class="QWidget" name="Widget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Widget</string>
</property>
<widget class="QLabel" name="result">
<property name="geometry">
<rect>
<x>40</x>
<y>10</y>
<width>121</width>
<height>41</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QPushButton" name="btn_one">
<property name="geometry">
<rect>
<x>280</x>
<y>80</y>
<width>89</width>
<height>25</height>
</rect>
</property>
<property name="text">
<string>1</string>
</property>
</widget>
<widget class="QPushButton" name="btn_two">
<property name="geometry">
<rect>
<x>280</x>
<y>120</y>
<width>89</width>
<height>25</height>
</rect>
</property>
<property name="text">
<string>2</string>
</property>
</widget>
<widget class="QPushButton" name="btn_three">
<property name="geometry">
<rect>
<x>280</x>
<y>160</y>
<width>89</width>
<height>25</height>
</rect>
</property>
<property name="text">
<string>3</string>
</property>
</widget>
<widget class="QPushButton" name="btn_four">
<property name="geometry">
<rect>
<x>280</x>
<y>200</y>
<width>89</width>
<height>25</height>
</rect>
</property>
<property name="text">
<string>4</string>
</property>
</widget>
<widget class="QPushButton" name="close">
<property name="geometry">
<rect>
<x>280</x>
<y>240</y>
<width>89</width>
<height>25</height>
</rect>
</property>
<property name="text">
<string>close</string>
</property>
</widget>
<widget class="QPushButton" name="btn_three_2">
<property name="geometry">
<rect>
<x>180</x>
<y>160</y>
<width>89</width>
<height>25</height>
</rect>
</property>
<property name="text">
<string>3</string>
</property>
</widget>
<widget class="QPushButton" name="btn_two_2">
<property name="geometry">
<rect>
<x>180</x>
<y>120</y>
<width>89</width>
<height>25</height>
</rect>
</property>
<property name="text">
<string>2</string>
</property>
</widget>
<widget class="QPushButton" name="btn_four_2">
<property name="geometry">
<rect>
<x>180</x>
<y>200</y>
<width>89</width>
<height>25</height>
</rect>
</property>
<property name="text">
<string>4</string>
</property>
</widget>
<widget class="QPushButton" name="btn_one_2">
<property name="geometry">
<rect>
<x>180</x>
<y>80</y>
<width>89</width>
<height>25</height>
</rect>
</property>
<property name="text">
<string>1</string>
</property>
</widget>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources/>
<connections/>
</ui>
代码贴完了,我们再来张效果图
需要注意的问题
信号与槽机制是比较灵活的,但有些局限性我们必须了解,这样在实际的使用过程中才能够做到有的放矢,避免产生一些错误。下面就介绍一下这方面的情况。
⑴ 信号与槽的效率是非常高的,但是同真正的回调函数比较起来,由于增加了灵活性,因此在速度上还是有所损失,当然这种损失相对来说是比较小的,通过在一台i586-133的机器上测试是10微秒(运行Linux),可见这种机制所提供的简洁性、灵活性还是值得的。但如果我们要追求高效率的话,比如在实时系统中就要尽可能的少用这种机制。
⑵ 信号与槽机制与普通函数的调用一样,如果使用不当的话,在程序执行时也有可能产生死循环。因此,在定义槽函数时一定要注意避免间接形成无限循环,即在槽中再次发射所接收到的同样信号。
⑶ 如果一个信号与多个槽相关联的话,那么,当这个信号被发射时,与之相关的槽被激活的顺序将是随机的,并且我们不能指定该顺序。
⑷ 宏定义不能用在signal和slot的参数中。
⑸ 构造函数不能用在signals或者slots声明区域内。
⑹ 函数指针不能作为信号或槽的参数。
⑺ 信号与槽不能有缺省参数。
⑻ 信号与槽也不能携带模板类参数。
通过上面代码 我们了解了信号与槽可以传递参数等 我们看一下下面的案例
此处 直接贴代码
MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QPushButton>
#include <QLabel>
#include <QLineEdit>
#include <QTextEdit>
#include <QPlainTextEdit>
#include <QDebug>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
void lineEditText(QString);
private:
Ui::MainWindow *ui;
signals:
void signalo11(int n=0);//object1-1的信号
void signalo12(int n=0);//object1-2的信号
void signalo21(int n=0);//object2-1的信号
void signalo22(int n=0);//object2-2的信号
void signalo31(int n=0);//object3-1的信号
void signalo32(int n=0);//object3-2的信号
private slots:
void slotso22(int n=0);//object2-2的槽
void slotso31(int n=0);//object3-1的槽
void on_Object1_clicked();
void on_Object2_clicked();
void on_Object3_clicked();
};
#endif // MAINWINDOW_H
.h头文件给出了信号与槽,那么我们去把槽与信号定义连接起来
具体还是看代码:MainWindows.cpp
#include "MainWindow.h"
#include "ui_MainWindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
//此处用this,是一个窗体,如果不同的窗体,可以用不同的 对象
#if 0
//1.一个信号可以与另一个信号相连
connect(this,SIGNAL(signalo11(int)),this,SIGNAL(signalo21(int)));
connect(this,SIGNAL(signalo21(int)),this,SLOT(slotso22(int)));
#endif
#if 0
//2.表示一个信号可以与多个槽相连
connect(this,SIGNAL(signalo12(int)),this,SLOT(slotso22(int)));
connect(this,SIGNAL(signalo12(int)),this,SLOT(slotso31(int)));
#endif
#if 1
//3.表示同一个槽可以响应多个信号
connect(this,SIGNAL(signalo12(int)),this,SLOT(slotso22(int)));
connect(this,SIGNAL(signalo32(int)),this,SLOT(slotso22(int)));
#endif
}
void MainWindow::slotso22(int n)//object2-2的槽
{
if(n==0)
{
ui->lineEdit->setText("1.我是Object1点击 object2-2槽出现的");
}
else if(n==1)
{
ui->lineEdit->setText("2.我是Object1点击 object2-2槽出现的");
}
else if(n==2)
{
qDebug()<<"click Object1";
ui->lineEdit->setText("3.我是Object1点击 object2-2槽出现的");
} else if(n==3)
{
qDebug()<<"click Object3";
ui->lineEdit->setText("3.我是Object3点击 object2-2槽出现的");
}else
{
ui->lineEdit->setText("我是Object1点击 object2-1槽出现的");
}
}
void MainWindow::slotso31(int n)//object3-1的槽
{
ui->textEdit->setText("2.我是Object1点击 object3-1槽出现的");
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_Object1_clicked()
{
#if 0
emit signalo11(0);
#endif
#if 0//2
emit signalo12(1);
#endif
#if 1//3
emit signalo12(2);
#endif
}
void MainWindow::on_Object2_clicked()
{
ui->lineEdit->setText("我是Object2点击按钮 出现的");
}
void MainWindow::on_Object3_clicked()
{
emit signalo32(3);
}
最后补充上ui
MainWindows.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>522</width>
<height>460</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<widget class="QPushButton" name="Object1">
<property name="geometry">
<rect>
<x>30</x>
<y>30</y>
<width>113</width>
<height>32</height>
</rect>
</property>
<property name="text">
<string>Object1</string>
</property>
</widget>
<widget class="QLineEdit" name="lineEdit">
<property name="geometry">
<rect>
<x>40</x>
<y>90</y>
<width>371</width>
<height>51</height>
</rect>
</property>
<property name="text">
<string>单行文本输入,一般用于用户名、密码等少量文本交互地方</string>
</property>
</widget>
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>40</x>
<y>70</y>
<width>411</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>我是LineEdit</string>
</property>
</widget>
<widget class="QLabel" name="label_2">
<property name="geometry">
<rect>
<x>40</x>
<y>10</y>
<width>81</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>我是button</string>
</property>
</widget>
<widget class="QTextEdit" name="textEdit">
<property name="geometry">
<rect>
<x>40</x>
<y>170</y>
<width>371</width>
<height>91</height>
</rect>
</property>
<property name="html">
<string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
p, li { white-space: pre-wrap; }
</style></head><body style=" font-family:'.SF NS Text'; font-size:13pt; font-weight:400; font-style:normal;">
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">多行文本,也可以显示HTML格式文本</p></body></html></string>
</property>
</widget>
<widget class="QLabel" name="label_3">
<property name="geometry">
<rect>
<x>40</x>
<y>150</y>
<width>101</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>我是textEdit</string>
</property>
</widget>
<widget class="QPlainTextEdit" name="plainTextEdit">
<property name="geometry">
<rect>
<x>40</x>
<y>300</y>
<width>371</width>
<height>91</height>
</rect>
</property>
<property name="plainText">
<string>QTextEdit很像,但它多用于需要与文本进行处理的地方,而QTextEdit多用于显示,可以说,QPlainTextEdit对于plain text处理能力比QTextEdit强</string>
</property>
</widget>
<widget class="QLabel" name="label_4">
<property name="geometry">
<rect>
<x>40</x>
<y>270</y>
<width>101</width>
<height>21</height>
</rect>
</property>
<property name="text">
<string>我是QTextEdit</string>
</property>
</widget>
<widget class="QPushButton" name="Object2">
<property name="geometry">
<rect>
<x>190</x>
<y>30</y>
<width>113</width>
<height>32</height>
</rect>
</property>
<property name="text">
<string>Object2</string>
</property>
</widget>
<widget class="QLabel" name="label_5">
<property name="geometry">
<rect>
<x>190</x>
<y>10</y>
<width>81</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>我是button_2</string>
</property>
</widget>
<widget class="QLabel" name="label_6">
<property name="geometry">
<rect>
<x>350</x>
<y>10</y>
<width>81</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>我是button_3</string>
</property>
</widget>
<widget class="QPushButton" name="Object3">
<property name="geometry">
<rect>
<x>340</x>
<y>30</y>
<width>113</width>
<height>32</height>
</rect>
</property>
<property name="text">
<string>Object3</string>
</property>
</widget>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>522</width>
<height>22</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>
这个案例的结果来张效果图:
好了通过这两案例 我们总结了如下图:
1.一个信号可以与另一个信号相连
connect(Object1,SIGNAL(signal1),Object2,SIGNAL(signal1))
即表示Object1的信号1发射可以触发Object2的信号1发射。
2.表示一个信号可以与多个槽相连
connect(Object1,SIGNAL(signal2),Object2,SLOT(slot2))
connect(Object1,SIGNAL(signal2),Object3,SLOT(slot1))
3.表示同一个槽可以响应多个信号
connect(Object1,SIGNAL(signal2),Object2,SLOT(slot2))
connect(Object3,SIGNAL(signal2),Object2,SLOT(slot2))
代码下载地址:
https://gitee.com/caryhua/QtSlignalSlot.git
如有问题 QQ联系:97968684
谢谢欣赏~~