Qt信号和槽机制

更新时间:2023-06-14 04:03:49 阅读: 评论:0

Qt信号和槽机制
信号和槽⽤于对象间的通讯。信号/槽机制是Qt的⼀个核⼼特征,也许是Qt与其它框架提供的特性中最不相同的部分。
简介
在GUI编程中,当我们改变⼀个部件时,经常想要其他部件被通知。更⼀般化,我们希望任何⼀类的对象可以和其它对象进⾏通讯。例如,如果我们点击⼀个关闭按钮,我们可能想要窗⼝的clo()函数被调⽤。
其他⼯具包通过回调实现了这种通信。回调是⼀个函数指针,所以如果你希望⼀个处理函数通知你⼀些事件,你可以把另⼀个函数(回调)的指针传递给处理函数。处理函数在适当的时候调⽤回调。尽管⼀些成功的框架使⽤了这个⽅法,但是回调可能是不直观的,并可能在确保回调参数类型正确性上存在问题。
信号和槽
在Qt中我们有⼀种可以替代回调的技术:我们使⽤信号和槽。当⼀个特定事件发⽣的时候,⼀个信号被发射。Qt的⼩部件有很多预定义的信号,但是我们总是可以通过继承来加⼊我们⾃⼰的信号。槽就是⼀
个对应于特定信号的被调⽤的函数。Qt的部件有很多预定义的槽,但同样可以通过⼦类化加⼊⾃⼰的槽来处理感兴趣的信号。
信号和槽机制是类型安全的:⼀个信号的签名(参数类型)必须与它的接收槽的签名相匹配。(实际上,⼀个槽的签名可能⽐它接收到的信号短,因为它可以忽略额外的参数。)因为签名是兼容的,当使⽤基于函数指针的语法时,编译器就可以帮助我们检测类型不匹配。基于字符串的信号和槽语法将在运⾏时检测类型不匹配。信号和插槽是松散耦合的:⼀个发射信号的类不⽤知道也不⽤关⼼哪个槽要接收这个信号。Qt的信号和槽的机制可以保证如果你把⼀个信号和⼀个槽连接起来,槽会在正确的时间使⽤信号的参数被调⽤。信号和槽可以使⽤任何数量、任何类型的参数。它们是完全类型安全的:不会再有回调核⼼转储(core dump)。
从QObject类或者它的⼦类(如QWidget类)继承的所有类可以包含信号和槽。当改变它们的状态的时候,信号被发送。从某种意义上说,即它们对其他对象感兴趣。这就是所有的对象通讯时所做的⼀切。它不知道也不关⼼是否有其他对象接收到了它发射的信号。这就是真正的信息封装,并且确保对象可以⽤作⼀个软件组件使⽤。
槽可以⽤来接收信号,但它们也是普通的成员函数。正如⼀个对象不知道有其他对象收到它的信号⼀样,⼀个槽也不知道它是否被任意信号连接。因此,这种⽅式保证了Qt创建组件的完全独⽴。
你可以连接多个信号到⼀个槽,同样⼀个信号可以被你需要的多个槽连接。甚⾄可以⼀个信号连接到另⼀个信号。(该情况下,只要第⼀个信 号被发射时,第⼆个信号⽴即被发射。)
总体来看,信号和槽构成了⼀个强有⼒的组件编程机制。
信号
当⼀个对象内部状态发⽣改变时发射信号,信号就被发射,在某些⽅⾯对象的客户端和所有者可能感兴趣。信号是⼀个public访问函数并可以在任何地⽅发射,但是我们建议仅在定义了信号的类及他们的⼦类中发射。
当⼀个信号被发射,它所连接的槽会被⽴即执⾏,类似于⼀个普通函数的调⽤。在该情况下,信号/槽机制在任何GUI事件循环中是完全独⽴的。⼀旦所有的槽返回了,emit语句随后的代码将会执⾏。这与queued connections⽅式有些许不同,queued connections⽅式下emit 关键字随后的代码会⽴即继续执⾏,槽在随后执⾏(相当于异步,参考信号与槽的连接类型)。
如果⼏个槽被连接到⼀个信号,当信号被发射时,这些槽就会按它们连接的顺序挨个执⾏。
信号会由moc⾃动⽣成并⼀定不要在.cpp⽂件中实现。它们也不能有任何返回类型(也就是 只能使⽤void)。
关于参数需要注意的:我们的经验表明,如果不使⽤特殊类型,信号和插槽就可重⽤。如果QScrollBar::valueChanged() 使⽤了⼀个特殊的类型,⽐如hypothetical QRangeControl::Range,它就只能被连接到给QScrollBar设计的特定槽。连接到其他不同的输⼊部件是不可能的。
当与槽连接的信号被发射时,该槽被调⽤。槽是普通的C++函数可以正常调⽤;它们唯⼀的特点就是可以被信号连接。
因为槽是普通成员函数,所以被直接调⽤时遵循⼀般的C++规则。然⽽,作为槽,它们能被任何组件调⽤,通过信号槽连接,⽆需考虑访问级别。这意味着从⼀个任意类的实例发出的⼀个信号可以在⼀个不相关的类的实例中调⽤⼀个私有槽。
同样还可以定义虚拟槽,我们在实践中发现它⾮常有⽤。
与回调相⽐,信号和插槽的速度稍慢,因为它们增加了灵活性,尽管在实际应⽤程序中的差异是微不⾜道的。
信号和槽的机制是⾮常有效的,但是它不像“真正的”回调那样快。信号和槽稍微有些慢,这是因为它们所提供的灵活性造成,尽管在实际应⽤中这些性能可以被忽略。通常,发射⼀个和槽相连的信号,
⼤约⽐直接调接收器⾮虚函数的调⽤慢⼗倍。这是定位连接对象所需的开销,为了安全地遍历所有连接(e.g检查随后的接收器在发射过程中没有被销毁),并以通⽤的⽅式安排任意参数。虽然10个⾮虚函数调⽤听起来可能很多,但开销⽐任意new或delete 操作少得多。当执⾏⼀个字符串时,vector或list操作在后⾯的场景中需要new或delete, 但信号和槽在完成函数调⽤的开销中只占很⼩的⼀部分。在⼀个槽中进⾏系统调⽤或者间接地调⽤超过10个函数,情况也是相同的;。信号/插槽机制的简单和灵活性对于时间的开销是值得,⽤户甚⾄不会注意到这点。
注意,与基于qt的应⽤程序⼀起编译时,定义变量的其他库调⽤信号和槽可能会造成编译器警告和错误。要解决这个问题,#undef这些预处理器的冲突符号即可。
⼀个⼩例⼦
⼀个⼩的C++类声明如下:
class Counter
{
public:
Counter() { m_value = 0; }
int value() const { return m_value; }
void tValue(int value);
private:
int m_value;
};
⼀个⼩的基于QObject类如下:
#include <QObject>
class Counter : public QObject
{
Q_OBJECT
public:
Counter() { m_value = 0; }
int value() const { return m_value; }
public slots:
六级听力技巧void tValue(int value);
signals:
void valueChanged(int newValue);
private:
int m_value;
};
这个基于QObject的类有同样的内部状态,并提供公有⽅法来访问状态。除此之外,这个类可以通过发射⼀个valueChanged()信号来告诉外部世界关于它的状态改变,并且它有⼀个槽,其它对象可以发送信号给这个槽。
所有包含信号和/或者槽的类必须在它们的声明中提到Q_OBJECT。它们也必须从QObject继承(直接或间接)。
槽由应⽤程序的编写者来实现。这⾥是Counter::tValue()可能的⼀个槽实现:
void Counter::tValue(int value)
{
if (value != m_value) {
m_value = value;
emit valueChanged(value);
}
}
emit这⼀⾏从对象中发射valueChanged()信号,以⼀个新值作为参数。
在下⾯的⼩⽚断中,我们创建了两个Counter对象,并使⽤QObject::connect()将第⼀个对象的valueChanget()信号连接到第⼆个对旬的tValue槽。
下⾯是把两个对象连接在⼀起的⼀种⽅法:
Counter a, b;母狗般的女教师
QObject::connect(&a, &Counter::valueChanged,
&b, &Counter::tValue);
a.tValue(12);    // a.value() == 12,
b.value() == 12
b.tValue(48);    // a.value() == 12, b.value() == 48
调⽤a.tValue(12)会使a发射⼀个valueChanged(12) 信号,b将会在它的tValue()槽中接收这个信号,也就是b.tValue(12) 被调⽤。接下来b会发射同样的valueChanged()信号,但是因为没有槽被连接到b的valueChanged()信号,所以该信号被忽略。
温柔语录
注意只有当value!= m_value的时候tValue()函数才会设置这个值并发射信号。这样就避免了存在环connections的情况下⽆限循环。(⽐如b.valueChanged() 和a.tValue()连接在⼀起)。
默认情况下,对于你做出的每⼀个连接,⼀个信号被发射;重复连接下两个信号被发射。你可以调⽤disconnect()中断所有连接。如果传递了Qt::UniqueConnection类型,连接只有在不重复的情况下才建⽴。如果已经存在重复连接(同⼀个对象上确定的信号到确定的槽),则连接将会失败,connect返回fa。
这个例⼦说明了对象可以协同⼯作⽽不需要知道彼此的任何信息。为达到这种效果,对像仅需要相互连接,并⽤⼀个简单的
QObject::connect()函数调⽤实现,或者使⽤uic的⾃动连接特性。
⼀个实际例⼦
这是⼀个注释过的简单的例⼦。
#ifndef LCDNUMBER_H
#define LCDNUMBER_H
#include <QFrame>
class LcdNumber : public QFrame
{
Q_OBJECT
QLcdNumber继承于QObject,经由QFrame和QWidget,包含有⼤多数信号和槽知识,它有⼏分类似于内置的QLCDNumber⼩部件。
Q_OBJECT是由预处理器展开,声明⼏个由moc实现的成员函数,如果你得到了⼏⾏ “undefined reference to vtable for LcdNumber”这样的编译器错误信息,你也许忘记运⾏moc或者在链接命令中包含moc输出。
public:
LcdNumber(QWidget *parent = 0);
它明显和moc不相关,但是如果你继承了QWidget,⼏乎肯定希望在构造函数中拥有⽗参数,并将其传递给基类的构造函数。
猜想造句
这⾥省略了⼀些析构函数和成员函数,moc忽略了这些成员函数。
比萨饼
signals:
void    overflow();
当QLCDNumber被要求显⽰⼀个不可能值时,它发射⼀个信号。
如果你不关⼼溢出,或者你认为溢出不会发⽣,你可以忽略overflow()信号, 也就是说你可以不必将该信号连接到任何槽。
另⼀⽅⾯,当数字溢出时,你想调⽤两个不同的错误函数,只需简单地将信号连接到两个不同的槽。Qt将调⽤它个槽(按连接顺序)。
public slots:
void display(int num);
void display(double num);
void display(const QString &str);
void tHexMode();
void tDecMode();
void tOctMode();
void tBinMode();
void tSmallDecimalPoint(bool point);
};
#endif
⼀个槽就是⼀个接收函数,⽤于获得其它窗⼝部件状态改变的信息。LcdNumber 使⽤它,就像上⾯的代码⼀样,设置显⽰的数字。因为display()是这个类和程序其它的部分的⼀个接⼝,所以这个槽是公有的。
⼏个例程把QScrollBar的valueChanged()信号连接到display()槽,所以LCD数字可以连续显⽰滚动条的值。
请注意代码中display()被重载了,当你把⼀个信号和这个槽相连的时候,Qt将会选择适当的版本。如果使⽤回调,你不得不查找五个不同的名称并且⾃⼰记录类型。
⼀些不相关的成员函数已从例⼦中省略。
带有默认参数的信号和槽
信号和槽的签名可能包含参数,参数可以有默认值。考虑QObject::destroyed()函数。
void destroyed(QObject* = 0);
当⼀个QObject对象被删除,它发射QObject::destroyed()信号。我们想捕获此信号,⽆论在我们可能有⼀个悬空的引⽤指向删除的QObject对象的情况下,因此我们可以把它清理⼲净。⼀个合适的槽签名可能是这样的:
void objectDestroyed(QObject* obj = 0);
为连接⼀个信号到此槽,我们使⽤QObject::connect。这⾥有⼏个⽅式连接。第⼀种⽅式是使⽤函数指针:
connect(nder,&QObject::destroyed,this,
&MyObject::objectDestroyed);
使⽤QObject::connect()和函数指针有⼏个优点。⾸先,它允许编译器检查信号的参数是否与槽的参数兼容。如果需要,参数也可以由编译器隐式转换。
也能连接到仿函数或C++11匿名函数:
connect(nder, &QObject::destroyed, \
[=](){ this-  >ve(nder); });
需要注意的是,如果编译器不⽀持C++11变量模板,那么这个语法只有在信号和槽有6个参数或更少的情况下才有效。
连接信号到槽的另⼀种⽅法是使⽤QObject::connect(),SIGNAL和SLOT宏。关于是否在SIGNAL()和SLOT()宏中包含参数的规则,如果参数有默认值,传递给SIGNAL()宏的签名必须不能少于传递给SLOT()宏签名的参数。
下述这些都会⽣效:
connect(nder,SIGNAL(destroyed(QObject*)),this,
SLOT(objectDestroyed(Qbject*)));
connect(nder,SIGNAL(destroyed(QObject*)),this,
雨滴的声音
SLOT(objectDestroyed()));
connect(nder, SIGNAL(destroyed()),this,
SLOT(objectDestroyed()));
以下这个不会⽣效:
connect(nder,SIGNAL(destroyed()),this,
SLOT(objectDestroyed(QObject*)));
因为槽期望⼀个QObject,⽽信号并不会发送此参数。该连接将会报运⾏错误。
投资规划注意,当使⽤这个QObject::connect()重载时,编译器不会检查信号和槽参数。
信号和槽的进⼀步使⽤
对于可能需要信号发送者信息的情况, Qt提供了QObject::nder()函数,它返回⼀个指向发送信号的对象的指针。
QSignalMapper类为⼀种情形提供,该情况下多个信号连接到同⼀个槽,⽽槽需要以不同的⽅式处理每个信号。
假设有三个按钮来决定将打开哪个⽂件: “Tax File”, “Accounts File”, “Report File”.
为了打开正确的⽂件,使⽤QSignalMapper:tMapping()来映射所有QPushButton:clicked()到QSignalMapper对象。然后将⽂件的QPushButton::clicked()信号连接到QSignalMapper::map()槽。
signalMapper = new QSignalMapper(this);
signalMapper->tMapping(taxFileButton,  QString(""));
signalMapper->tMapping(accountFileButton,
QString(""));
signalMapper->tMapping(reportFileButton,
QString(""));
connect(taxFileButton, &QPushButton::clicked,
signalMapper, &QSignalMapper::map);
connect(accountFileButton, &QPushButton::clicked,
signalMapper, &QSignalMapper::map);
connect(reportFileButton, &QPushButton::clicked,
signalMapper, &QSignalMapper::map);
然后,将mapped()信号连接到readFile(),根据按下的按钮,readFile()打开不同的⽂件。
connect(signalMapper, SIGNAL(mapped(QString)),
this, SLOT(readFile(QString)));
和第三⽅库信号/槽使⽤Qt
和Qt使⽤3rd party信号槽机制是可能的。甚⾄可以在同⼀个项⽬中使⽤这两种机制。只需添加下述⾏到你的qmake⼯程的(.pro)⽂件中。
晨会小故事
CONFIG += no_keywords

本文发布于:2023-06-14 04:03:49,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/82/949745.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:信号   连接   参数   函数   对象
相关文章
留言与评论(共有 0 条评论)
   
验证码:
推荐文章
排行榜
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图