您的位置:首页 > 编程语言 > Qt开发

PyQt学习笔记-开端篇 信号和槽 signal slot

2016-01-21 14:48 405 查看
今天开始打算用三个月时间把python的GUI编程学习下。选定了PySide。

PyQt和PySide都是Qt进行的Python绑定,二者的不同之处在与PySide提供供商用的LGPL协议,PyQt使用开源的GPL协议,商用需要向Riverbank's software购买。PyQt相对比较成熟,提供了Python对Qt5的支持。

PySide由Qt官方维护,PySide目前最新版本是1.2.2,完成了对Qt4.8版本的完整实现

先学习些Qt库相关的基础支持,有了基础再看构造在其上的PySide种种概念,会清晰明了的多。饭要一口口吃,切莫贪多贪快

今天主要看看Qt里特有的信号和槽的概念。signal and slot

信号和槽机制是Qt 的核心机制之一,要掌握Qt 编程就需要对信号和槽有所了解。信号和槽是一种高级接口,它们被应用于对象之间的通信,它们是Qt 的核心特性,也是Qt不同于其它同类工具包的重要地方之一。

在我们所了解的其它GUI 工具包中,窗口小部件(widget)都有一个回调函数用于响应它们触发的动作,这个回调函数通常是一个指向某个函数的指针。在Qt 中用信号和槽取代了上述机制。

1.信号(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。

2.槽(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 用的也是复数形式。

3.信号与槽的关联

槽和普通的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(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 中实现的,并不只局限于图形用户界面编程中。这种机制可以用于任何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)) );

4.信号和槽连接示例

以下是QObject 子类的示例:

class BankAccount : public QObject

{

Q_OBJECT

public:

BankAccount() { curBalance = 0; }

int balance() const { return curBalance; }

public slots:

void setBalance(int newBalance);

signals:

void balanceChanged(int newBalance);

private:

int currentBalance;

};

与多数C++ 类的风格类似,BankAccount 类拥有构造函数、balance() “读取”函数和setBalance() “设置”函数。它还拥有balanceChanged() 信号,帐户余额更改时将发出此信号。发出信号时,与它相连的槽将被执行。

Set 函数是在公共槽区中声明的,因此它是一个槽。槽既可以作为成员函数,与其他任何函数一样调用,也可以与信号相连。以下是setBalance() 槽的实现过程:

void BankAccount::setBalance(int newBalance)

{

if (newBalance != currentBalance)

{

currentBalance = newBalance;

emit balanceChanged(currentBalance);

}

}

语句emit balanceChanged(currentBalance);将发出balanceChanged() 信号,并使用当前新余额作为其参数。

关键字emit 类似于“signals”和“slots”,由Qt 提供,并由C++ 预处理器转换成标准C++ 语句。

以下示例说明如何连接两个BankAccount 对象:

BankAccount x, y;

connect(&x, SIGNAL(balanceChanged(int)), &y, SLOT(setBalance(int)));

x.setBalance(2450);

当x 中的余额设置为2450 时,系统将发出balanceChanged() 信号。y 中的setBalance() 槽收到此信号后(y的setBalance不会继续emit signal吗?),将y 中的余额设置为2450。一个对象的信号可以与多个不同槽相连,多个信号也可以与特定对象中的某一个槽相连。参数类型相同的信号和槽可以互相连接。槽的参数个数可以少于信号的参数个数,这时多余的参数将被忽略。

5.需要注意的问题

信号与槽机制是比较灵活的,但有些局限性我们必须了解,这样在实际的使用过程中才能够做到有的放矢,避免产生一些错误。下面就介绍一下这方面的情况。

⑴ 信号与槽的效率是非常高的,但是同真正的回调函数比较起来,由于增加了灵活性,因此在速度上还是有所损失,当然这种损失相对来说是比较小的,通过在一台i586-133 的机器上测试是10 微秒(运行Linux),可见这种机制所提供的简洁性、灵活性还是值得的。但如果我们要追求高效率的话,比如在实时系统中就要尽可能的少用这种机制。

⑵ 信号与槽机制与普通函数的调用一样,如果使用不当的话,在程序执行时也有可能产生死循环。因此,在定义槽函数时一定要注意避免间接形成无限循环,即在槽中再次发射所接收到的同样信号。

⑶ 如果一个信号与多个槽相关联的话,那么,当这个信号被发射时,与之相关的槽被激活的顺序将是随机的,并且我们不能指定该顺序。

⑷ 宏定义不能用在signal 和slot 的参数中。

⑸ 构造函数不能用在signals 或者slots 声明区域内。

⑹ 函数指针不能作为信号或槽的参数。

⑺ 信号与槽不能有缺省参数。

⑻ 信号与槽也不能携带模板类参数。

6.小结

从QObject 或其子类(例如Qwidget)派生的类都能够使用信号和槽机制。这种机制本身是在QObject 中实现的,并不只局限于图形用户界面编程中:当对象的状态得到改变时,它可以某种方式将信号发射(emit)出去,但它并不了解是谁在接收这个信号。槽被用于接收信号,事实上槽是普通的对象成员函数。槽也并不了解是否有任何信号与自己相连接。而且,对象并不了解具体的通信机制。这实际上是“封装”概念的生动体现,信号与槽机制确保了Qt 中的对象被当作软件的组件来使用,体现了“软件构件化”的思想。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: