`
mmdev
  • 浏览: 13234823 次
  • 性别: Icon_minigender_1
  • 来自: 大连
文章分类
社区版块
存档分类
最新评论

信号(signals)和槽(slots)

阅读更多

信号(signals)和槽(slots)

信号和信号槽被用于对象(object)之间的通信。信号和槽机制是QT的重要特征并且也许是QT与其他框架最不相同的部分。

前言

在GUI程序设计中,通常我们希望当对一个窗口部件(widget)进行改变时能告知另一个对此改变感兴趣的窗口部件。更一般的,我们希望任何一类的对象(object)都能和其他对象进行通信。例如,如果用户单击一个关闭按钮,我们可能就希望窗口的 close() 函数被调用。

早期的工具包用回调(backcalls)的方式实现上面所提到的对象间的通信。回调是指一个函数的指针,因此如果你希望一个处理函数通知你一些事情,你可以传递另一个函数(回调函数)指针给这个处理函数。这个处理函数就会在适当的时候调用回调函数。回调有两个重要的缺陷:首先,它们不是类型安全的。我们无法确定处理函数是用正确的参数调用这个回调函数。其次,回调与处理函数紧密的联系在一起以致处理函数必须知道调用哪个回调。

消息和槽

在QT中,我们使用一种可替代回调的技术:信号和槽机制。当一个特别的事件产生时则发出一个信号。QT的窗口部件有很多已经预定义好的信号,我们也可以通过继承,给窗口部件的子类添加他们自己信号。槽就是一个可以被调用处理特定信号的函数。QT的窗口部件有很多预定义好的槽,但是通常的做法是给子类窗口部件添加自己的信号,这样就可以操纵自己加入的信号了。


信号和槽机制是类型安全的:一个信号的签名必须和该信号接受槽的签名相匹配。(事实上以一个槽的签名可以比他可接受的信号的签名少,因为它可以忽略一些签名)。因此签名是一致的,编译器就可以帮助我们检测类型匹配。信号和槽是松耦合的:一个类不知道也不关心哪个槽接受了它所发出的信号。QT的信号和槽机制确保他们自生的正确连接,槽会在正确的时间使用信号参数而被调用。信号和槽可以使用任何数量、任何类型的参数。他们完全是类型安全的。

所有继承至QObject或是其子类(如 QWidget)的类都可包含信号和槽。当对象改变它们自身状态的时候,信号被发送,从某种意义上讲,它们也许对外面的世界感兴趣。这就是所有对象在通讯时所做的一切。它不知道也不关心有没有其他的东西接受它发出的信号。这就是真正的消息封装,并且确保对象可用作一个软件组件。

槽被用于接收信号,但是他们也是正常的成员函数。正如一个对象不知道是否有东西接受了他信号,一个槽也不知道它是否被某个信号连接。这就确保QT能创建真正独立的组件。

你可以将任意个信号连接到你想连接的信号槽,并且在需要时可将一个信号连接到多个槽。将信号直接连接到另一个信号也是可能的(这样无论何时当第一个信号被发出后会立即发出第二个)。

总体来看,信号和槽构成了一个强有力的组件编程机制。

简单示例

一个极小的 C++ 类 声明如下:

class Counter
{
public:
Counter() {m_value = 0;}

int value() const {return m_value;}
void setValue(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 setValue(int value);
signals:
void valueChanged(int newValue);

private:
int m_value;
};

QObject版本的类与前一个C++类有着相同的域,并且提供公有函数接受这个域,但是它还增加了对信号和槽(signals-slots)组件编程的支持。这个类可以通过valueChanged()发送信号告诉外部世界他的域发生了改变,并且它有一个可以接受来自其他对象发出信号的槽。

所有包含信号和槽的类都必须在他们声明中的最开始提到Q_OBJECT。并且他们必须继承至(直接或间接)QObject。

槽可以由应用程序的编写者来实现。这里是Counter::setVaule()的一个可能的实现:

void Counter::setValue(int value)
{
if(value != m_value)
{
m_value = value;
emit valueChanged(value);
}
}

emit所在的这一行从对象发出valueChanged信号,并使用新值做为参数。

在下面的代码片段中,我们创建两个Counter对象并且使用QObject::connect()函数将第一个对象的valueChanged()信号连接到第二个对象的setValue()槽。

Counter a, b;
QObject::connect (&a, SIGNAL(valueChanged(int)),
&b, SLOT(setValue(int)));
a.setValue(12); // a.value() == 12, b.value() == 12
b.setValue(48); // a.value() == 12, b.value() == 48

函数a.setValue(12)的调用导致信号valueChange(12)被发出,对象b的setValue()槽接受该信号,即函数setValue()被调用。然后b同样发出信号valueChange(),但是由于没有槽连接到b到valueChange()信号,所以该信号被忽略。

注意,只有当 value != m_value 时,函数 setValue() 才会设置新值并发出信号。这样就避免了在循环连接的情况下(比如b.valueChanged() 和a.setValue()连接在一起)出现无休止的循环的情况。

信号将被发送给任何你建立了连接的槽;如果重复连接,将会发送两个信号。总是可以使用QObject::disconnect()函数断开一个连接。

这个例子说明了对象之间可以不需要知道相互间的任何信息而系协同工作。为了实现这一目的,只需要将对象通过函数QObject::connect()的调用相连接(connect),或者利用uic的automatic connections的特性。

编译这个示例

C++预编译器会改变或去除关键字signals,slots,和emit,这样就可以使用标准的C++编译器。

在一个定义有信号和槽的类上运行moc,这样就会生成一个可以和其它对象文件编译和连接成应用程序的C++源文件。如果使用qmake工具,将会在你的makefile文件里加入自动调用moc的规则。

信号

当对象的内部状态发生改变,信号就被发射,在某些方面对于对象代理或者所有者也许是很有趣的。只有定义了信号的对象或其子对象才能发射该信号。

当一个信号被发出,被连接的槽通常会立刻运行,就像执行一个普通的函数调用。当这一切发生时,信号和槽机制是完全独立于任何GUI事件循环之外的。槽会在emit域下定义的代码执行完后返回。当使用队列连接(queued connections)时会有一些不同;这种情况下,关键字emit后的代码会继续执行,而槽在此之后执行。

如果几个槽被连接到一个信号,当信号被发出后,槽会以任意顺序一个接一个的执行。

关于参数需要注意:我们的经验显示如果信号和槽不使用特殊的类型将会变得更具重用性。如果QScrollBar::valueChanged() 使用了一个特殊的类型,比如hypothetical QRangeControl::Range,它就只能被连接到被设计成可以处理QRangeControl的槽。再没有象教程5这样简单的例子。



当一个信号被发出时连接他的槽被调用。槽是一个普通的C++函数并按普通方式调用;他的特点仅仅是可以被信号连接。

由于槽只是普通的成员函数,当调用时直接遵循C++规则。然而,对于槽,他们可以被任何组件通过一个信号-槽连接(signal-slot connection)调用,而不管其访问权限。也就是说,一个从任意的类的实例发出的信号可导致一个不与此类相关的另一个类的实例的私有槽被调用。

你还可以定义一个虚拟槽,在实践中被发现也是非常有用的。

由于增加来灵活性,与回调相比,信号和槽稍微慢一些,尽管这对真实的应用程序来说是可以忽略掉的。通常,发出一连接了某个槽的信号,比直接调用那些非虚拟调用的接受器要慢十倍。这是定位连接对象所需的开销,可以安全地重复所有地连接(例如在发射期间检查并发接收器是否被破坏)并且可以按一般的方式安排任何参数。当十个非虚函数调用听起来很多时,实际上他比任何new和delete操作的开销都少,例如,当你执行一个字符串、矢量或列表操作时,就需要用到new和delete,而信号和槽的开销只是全部函数调用花费的一小部分。

无论何时你用槽进行一个系统调用和间接的调用超过10个以上的函数时间都是一样的。在i586-500机器上,每秒钟你可以发送超过2,000,000个信号给一个接受者,或者每秒发送1,200,000个信号给两个接受者。相对于信号和槽机制的简洁性和灵活性,他的时间开销是完全值得的,你的用户甚至察觉不出来。

注意:若其他的库将变量定义为signals和slots,可能导致编译器在连接基于QT的应用程序时出错或警告。为了解决这个问题,请使用#undef预处理符号。

元对象信息

元对象编译器(moc)解析一个C++文件中的类声明并且生成初始化元对象的C++代码。元对象包括信号和槽的名字,和指向这些函数的指针。

if (widget->inherits("QAbstractButton")) {
QAbstractButton *button = static_cast<QAbstractButton *>(widget);
button->toggle();
}

元对象信息的使用也可以是qobject_cast<T>(), 他和QObject::inherits() 相似,但更不容易出错。

if (QAbstractButton *button = qobject_cast<QAbstractButton *>(widget))
button->toggle();

查看Meta-Object系统可获取更多信息。

一个实例

这是一个注释过的简单的例子(代码片断选自qlcdnumber.h)。

#ifndef LCDNUMBER_H
#define LCDNUMBER_H

#include <QFrame>

class LcdNumber : public QFrame
{
Q_OBJECT

LcdNumber通过QFrame和QWiget继承至QObject,它包含了大部分signal-slot知识。这是有点类似于内置的QLCDNumber部件。

Q_OBJECT宏由预处理器展开,用来声明由moc实现的机个成员函数;如果你的编译器出现错误如下"undefined reference to vtable for LcdNumber", 你可能忘了运行moc或者没有用连接命令包含moc输出。

public:
LcdNumber(QWidget *parent = 0);

LcdNumber并不明显的与moc相关,但是如果你继承了QWidege,那么可以几乎肯定在你的构造函数中有父对象的变量,并且希望把它传给基类的构造函数。

析构函数和一些成员函数在这里省略;moc会忽视成员函数。

signals:
void overflow();

当LcdNumbe被要求显示一个不可能的值时,便发出信号。

如果你没有留意溢出,或者你知道溢出不会出现,你可以忽略overflow()信号,比如不将其连接到任何槽。

如果另一方面,当有数字溢出时你想调用两个不同的错误处理函数,可以将这个信号简单的连接到两个不同的槽。QT将调用两个函数(无序的)。

public slots:
void display(int num);
void display(double num);
void display(const QString &str);
void setHexMode();
void setDecMode();
void setOctMode();
void setBinMode();
void setSmallDecimalPoint(bool point);
};

#endif

一个槽是一个接受函数,用于获得其他窗口部件的信息变化。LcdNumber使用它,就像上面的代码一样,来设置显示的数字。因为display()是这个类和程序的其它的部分的一个接口,所以这个槽是公有的。

几个例程把QScrollBar的valueChanged()信号连接到display()槽,所以LCD数字可以继续显示滚动条的值。

请注意display()被重载了,当将一个信号连接到槽时QT将选择一个最适合的一个。而对于回调,你会发现五个不同的名字并且自己来跟踪类型。

一个不相干的成员函数在例子中被忽略。

高级信号和槽的使用

在当你需要信号发送者的信息时,QT提供了一个函数QObject::sender(),他返回指向一个信号发送对象的指针。

当有几个信号被连接到同一槽上,并且槽需要处理每个不同的信号,可使用 QSignalMapper类。

假设你用三个按钮来决定打开哪个文件:Tax File", "Accounts File", or "Report File"。

为了能打开真确的文件,你需要分别将它们的信号 QPushButton::clicked()连接到 readFile()。然后用QSignalMapper 的 setMapping()来映射所有 clicked()信号到一个 QSignalMapper对象。

signalMapper = new QSignalMapper(this);
signalMapper->setMapping(taxFileButton, QString("taxfile.txt"));
signalMapper->setMapping(accountFileButton, QString("accountsfile.txt"));
signalMapper->setMapping(reportFileButton, QString("reportfile.txt"));

connect(taxFileButton, SIGNAL(clicked()),
signalMapper, SLOT (map()));
connect(accountFileButton, SIGNAL(clicked()),
signalMapper, SLOT (map()));
connect(reportFileButton, SIGNAL(clicked()),
signalMapper, SLOT (map()));

然后,连接信号 mapped()到 readFile() ,根据被按下的按钮,就可以打开不同的文件。

connect(signalMapper, SIGNAL(mapped(const QString &)),
this, SLOT(readFile(const QString &)));

在QT中使用第三方signals slots

在QT中使用第三方signals slots是可能的。你甚至可以在同一类中使用两种机制。仅仅需要在你的qmake工程文件(.pro)中加入下面语句:

CONFIG += no_keywords

它告诉QT不要定义moc关键字signals,slots和emit,因为这些名字可能将被用于第三方库,例如Boost。你只需简单的用QT宏将他们替换为 Q_SIGNALS, Q_SLOTS,和 Q_EMIT,就可以继续使用信号和槽了。

分享到:
评论

相关推荐

    Qt信号和槽

    - 使用`signals:`和`slots:`关键字分别声明信号和槽。 **2. 信号与槽的连接** - 使用`QObject::connect`函数来建立信号与槽之间的连接。 - 在`connect`函数中需要使用`SIGNAL()`和`SLOT()`宏来指定信号和槽。 - 一...

    Qt 信号和槽机制

    Qt 信号和槽机制 Qt 信号和槽机制是 Qt 的核心机制,要精通 Qt 编程就必须对信号和槽有所了解。信号和槽是一种高级接口,应用于对象之间的通信,它是 Qt 的核心特性,也是 Qt 区别于其它工具包的重要地方。 信号和...

    Qt 源代码 - 06_信号和槽(二)自定义信号和槽的实例

    在C++中,我们可以使用`Q_OBJECT`宏定义一个包含元对象系统的类,然后在类声明中使用`signals:`和`slots:`关键字分别声明信号和槽。例如: ```cpp class MyClass : public QObject { Q_OBJECT public: explicit ...

    03-信号和槽02:自定义信号和槽的重载(带参数的信号和槽的使用方法

    在Qt库中,信号和槽是其核心特性之一,用于对象间的通信。它们提供了一种安全、类型安全的方式来实现事件驱动编程,使得不同组件之间能够有效地协调和交互。本节我们将深入探讨如何自定义信号和槽,特别是带有参数的...

    QT信号与槽机制浅析

    - **类型安全性**:在Qt中,信号和槽的参数类型必须严格匹配。这意味着编译器可以帮助开发者检查类型错误,从而避免运行时错误。 - **松散耦合**:信号与槽之间是松散耦合的。发射信号的对象无需关心哪些槽会接收...

    信号和槽的练习.zip

    2. **槽(Slots)**:槽是响应信号的函数,可以有返回值,也可以没有。当你连接一个信号到一个槽时,当信号被发射时,槽就会被调用。槽函数可以是Qt定义的,也可以是用户自定义的。这种机制使得代码解耦,提高了模块...

    3.QTdesigner信号与槽工作流程.zip

    在C++代码中,使用Q_OBJECT宏声明类,并使用Q_SIGNALS和Q_SLOTS宏分别定义信号和槽。 3. 连接信号与槽:在QTDesigner中创建的连接仅是元对象系统中的预定义,实际的信号与槽连接需要在运行时完成。这通常在应用的...

    Qt 源代码 - 05 信号和槽(一)

    在Qt中,定义信号和槽主要通过 moc(Meta-Object Compiler)工具来完成,moc会扫描源代码中的特定宏(如Q_OBJECT、Q_SIGNALS和Q_SLOTS)并生成元对象系统所需的额外代码。下面是一个简单的例子: ```cpp class ...

    03-信号和槽01:自定义信号和槽函数的创建及使用

    在Qt库中,信号和槽机制是其核心特性之一,用于实现对象间的通信。这个机制使得对象可以在适当的时候通知其他对象,而无需了解接收者是谁,这种解耦的设计模式极大地提高了代码的灵活性和可维护性。本教程将深入探讨...

    QT 的信号与槽机制介绍

    在实际使用过程中,需要注意一些问题,例如信号和槽的连接、信号的发射和槽的调用、信号和槽的参数传递等。同时,QT 也提供了一些元对象工具,例如 moc 工具,可以帮助我们更方便地使用信号和槽机制。 信号与槽机制...

    Qt不同类进行信号与槽建立,并传递参数

    在"Qt不同类进行信号与槽建立,并传递参数"这个主题中,我们将深入探讨如何在Qt的不同类中设置信号和槽,以及如何通过它们来传递参数。 首先,理解信号(Signal)和槽(Slot)的基本概念是至关重要的。信号是当特定...

    QT的信号与槽机制介绍

    根据访问权限,槽分为public slots、private slots和protected slots。public slots允许任何对象与其连接,private slots只能被同一类的对象连接,protected slots允许子类及同一类的对象连接。当信号被发射时,关联...

    msg.rar_Qt 信号 槽_信号和槽

    首先,理解Qt中的“信号”(Signals)和“槽”(Slots)的概念至关重要。“信号”表示对象状态的改变,当这种变化发生时,可以触发特定的操作。而“槽”则是响应这些信号的函数,它们定义了在接收到信号时要执行的...

    QT信号和槽机制

    当使用`signals`和`slots`关键字定义信号和槽时,moc会分析源代码并生成相应的代码。 **moc的作用:** - **代码生成:** 自动生成信号和槽所需的代码。 - **类型检查:** 确保信号和槽的参数类型匹配。 #### 六、...

    Qt信号和槽机制 适合初学者

    Qt信号和槽机制是Qt框架的核心特性,是对象间通信的一种高效方式,尤其对初学者而言,理解这一机制至关重要。信号和槽的概念是Qt自定义的,与标准C++语言不同,它们允许对象之间无侵入式的交互。 1. 信号(Signals...

    QT实现信号与槽机制

    信号和槽都需在类声明中使用`Q_OBJECT`宏,并通过`emit`关键字来发射信号,如`emit clicked();`。槽函数则如同普通成员函数,但前面可加上`slots`关键字(可选)。 5. **信号与槽的动态连接与静态连接** 动态连接...

    qt 信号与槽

    在Qt应用开发中,信号与槽机制扮演着至关重要的角色,尤其在事件驱动编程和界面设计中。下面我们将详细探讨这一主题。 1. **信号与槽的概念** - **信号(Signals)**:当某个事件发生时,Qt对象会发出一个信号。...

    QT信号与槽.pdf

    - 这些代码用于实现信号与槽机制的核心功能,如信号的发射和槽的调用。 #### 六、程式样例 为了更好地理解信号与槽的工作原理,以下是一个简单的示例程序: ```cpp #include #include #include class ...

    信号和槽方面的技术文档

    ### 信号和槽技术文档知识点总结 #### 一、概述 - **信号和槽机制**:作为Qt框架的核心机制之一,信号与槽提供了一种高效且类型安全的方式来进行对象间的通信。 - **特点**: - 简化事件处理过程。 - 支持任意...

    qt信号和槽的简单介绍

    在QT中,定义信号和槽通常使用`emit`关键字声明信号,`slots`关键字声明槽。例如,一个简单的按钮点击事件可能如下所示: ```cpp class MyWidget : public QWidget { Q_OBJECT public: MyWidget(QWidget *parent ...

Global site tag (gtag.js) - Google Analytics