qt之信号与槽的原理

只要问到qt的亮点是什么,不得不说就是信号与槽机制。信号与槽类似观察者模式。

在面向对象的编程中,都会创建很多实例,而每个实例都是独立的,要想每个实例能够协同合作,那么就会需要一种对象间传递消息的机制,在很多框架中都采用回调函数来进行对象间信息传递。

回调函数就是一个函数指针,如果想要一个处理函数通知一些事件,你需要将这个指针传递给处理函数。处理函数在适当时间调用回调函数。MFC就是使用的回调函数,但回调可能是不直观的,不易于理解的,并且也不能保证是类型安全的。

(1)信号与槽 与 回调函数的区别

    回调函数的本质是基于“你想让别人的代码执行你的代码,而别人的代码你又不能动”这种产生的。回调函数是函数指针的一种用法,如果多个类都关注某个类的状态变化,此时需要维护一个列表,以存放多个回调函数的地址。对于每一个被关注的类,都需要做类似的工作,因此这种做法效率低,不灵活。

回调函数的例子:

void printWelcome(int len)
{
	printf("欢迎欢迎 -- %d/n", len);
}
void printGoodbye(int len)
{
	printf("送客送客 -- %d/n", len);
}
	
void callback(int times, void(*print)(int))
{
	int i;
	for (i = 0; i < times; ++i)
	{
		print(i);
	}
	printf("/n我不知道你是迎客还是送客!/n/n");
}	
void main(void)
{
	callback(2, printWelcome);
	callback(2, printGoodbye);
}

Qt为了消除回调函数等的弊端,从而开发了一种新的消息传递机制,即信号和槽。

 顺带提一句,Qt提供了一种机制,能够自动、有效的组织和管理继承自QObject的Qt对象,这种机制就是对象树。这种机制在界面变成上是有好处的,能够帮助程序员缓解内存泄露的问题,比如说当应用程序创建了一个具有父窗口部件的对象时,该对象将被加入父窗口部件的孩子列表。当应用程序销毁父窗口部件时,其下的孩子列表中的对象将被一一删除。这让我们在编程时,能够将主要精力放在系统的业务上,提高编程效率,同时也保证了系统的稳健性。所以new了一个父窗口后,只要delete父窗口后,那它的子窗口都会被自动释放,释放顺序(即析构顺序)与这些子对象的构造顺序相反。

例如,当我们要求鼠标点击某个按钮时,对应的窗口就需要关闭,那么这个按钮就会发出一个点击信号,而窗口接收到这个信号后执行关闭窗口。那么,这个信号就是按钮被点击,而槽就是窗口执行关闭函数。

可以将信号和槽理解成“命令-执行”,即信号就是命令,槽就是执行命令。

(2)信号

当一个对象的内部状态发生改变时,如果其它对象对它的状态改变需要作出反应,这时就应该让这个类发出状态改变的信号。

声明信号使用signals关键字。

发送信号使用emit关键字。

信号的注意点:

1.所有的信号声明都是公有的,所以Qt规定不能在signals前面加public,private, protected。

2.所有的信号都没有返回值,所以返回值都用void。

3.所有的信号都不需要定义。

4.必须直接或间接继承自QOBject类,并且开头私有声明包含Q_OBJECT。

5.在同一个线程中,当一个信号被emit发出时,会立即执行其槽函数,等槽函数执行完毕后,才会执行emit后面的代码,如果一个信号链接了多个槽,那么会等所有的槽函数执行完毕后才执行后面的代码,槽函数的执行顺序是按照它们链接时的顺序执行的。不同线程中(即跨线程时),槽函数的执行顺序是随机的。

6.在链接信号和槽时,可以设置链接方式为:在发出信号后,不需要等待槽函数执行完,而是直接执行后面的代码,是通过connect的第5个参数。

7.信号与槽机制要求信号和槽的参数一致,所谓一致,是参数类型一致。如果不一致,允许的情况是,信号的参数可以比槽函数的参数多,即便如此,槽函数存在的那些参数的顺序也必须和信号的前面几个一致起来。这是因为,你可以在槽函数中选择忽略信号传来的数据(也就是槽函数的参数比信号的少),但是不能说信号根本没有这个数据,你就要在槽函数中使用(就是槽函数的参数比信号的多,这是不允许的)。

(3)槽

槽其实就是普通的C++函数,它可以是虚函数,static函数,也可以被重载,可以是公有的、保护的、私有的、也可以被其他C++成员函数调用,它唯一特点就是能和信号链接。当和它链接的信号被发出时,这个槽就会被调用,而且槽所在的类也需要直接或间接继承QObject,然后添加Q_OBJECT,槽函数因为是普通的C++函数,所以需要实现,不然接了信号什么都不做总不太好吧,槽是可以受访问控制的,而信号不行,不然一个private的信号,在类外有不能连接,没有意义。

在qt4中,声明槽可以使用:public/protected/private slots:

在Qt5中你也不需要使用这些声明,每个函数都可以被当作是槽函数,而且还可以使用Lambda表达式来作为槽。不过为了程序的可读性,我还是推荐槽函数要声明一下。

(4)信号与槽的连接

使用connect函数,有两个原型。

原型1:

static QMetaObject::Connection connect(
    const QObject *sender,    //信号发送对象指针
    const char *signal,       //信号函数字符串,使用SIGNAL()
    const QObject *receiver,  //槽函数对象指针
    const char *member,       //槽函数字符串,使用SLOT()
    Qt::ConnectionType = Qt::AutoConnection);

如:connect(pushButton, SIGNAL(clicked()), dialog, SLOT(close()));

Qt4和Qt5都可以使用这种连接方式。

原型2:

static QMetaObject::Connection connect(
    const QObject *sender,      //信号发送对象指针
    const QMetaMethod &signal,  //信号函数地址
    const QObject *receiver,    //槽函数对象指针
    const QMetaMethod &method,  //槽函数地址
    Qt::ConnectionType type = Qt::AutoConnection);

如:connect(pushButton, &QPushButton::clicked, dialog, &QDialog::close);

这是Qt5新增的连接方式,这使得在编译期间就可以进行拼写检查,参数检查,类型检查,并且支持相容参数的兼容性转换。

(5)信号与槽的多种用法

    1、一个信号可以和多个槽相连
    这时槽的执行顺序和在不在同一个线程上有关,同一线程,槽的执行顺序和声明顺序有关,跨线程时,执行顺序是不确定的。
    2、多个信号可以连接到一个槽
    只要任意一个信号发出,这个槽就会被调用。
    3、一个信号可以连接到另外的一个信号
    当第一个信号发出时,第二个信号被发出。除此之外,这种信号-信号的形式和信号-槽的形式没有什么区别。
    4、槽可以被取消链接
    这种情况并不经常出现,因为当一个对象delete之后,Qt自动取消所有连接到这个对象上面的槽。想主动取消连接就用disconnect()函数中添加任何实现。
    5、使用Lambda 表达式
    在使用 Qt 5 的时候,能够支持 Qt 5 的编译器都是支持 Lambda 表达式的。

(6)connect的第5个参数

   第5个参数一般不填,为默认值。

  1、Qt::AutoConnection: 默认值,使用这个值则连接类型会在信号发送时决定。如果接收者和发送者在同一个线程,则自动使用Qt::DirectConnection类型。如果接收者和发送者不在一个线程,则自动使用Qt::QueuedConnection类型。

  2、Qt::DirectConnection:槽函数会在信号发送的时候直接被调用,槽函数和信号发送者在同一线程。效果看上去就像是直接在信号发送位置调用了槽函数,效果上看起来像函数调用,同步执行。

emit语句后面的代码将在与信号关联的所有槽函数执行完毕后才被执行。

  3、Qt::QueuedConnection:信号发出后,信号会暂时被放到一个消息队列中,需等到接收对象所属线程的事件循环取得控制权时才取得该信号,然后执行和信号关联的槽函数,这种方式既可以在同一线程内传递消息也可以跨线程操作。

emit语句后的代码将在发出信号后立即被执行,无需等待槽函数执行完毕

  4、Qt::BlockingQueuedConnection:槽函数的调用时机与Qt::QueuedConnection一致,不过发送完信号后发送者所在线程会阻塞,直到槽函数运行完。而且接收者和发送者绝对不能在一个线程,否则程序会死锁。在多线程间需要同步的场合可能需要这个。

  5、Qt::UniqueConnection:这个flag可以通过按位或(|)与以上四个结合在一起使用。当这个flag设置时,当某个信号和槽已经连接时,再进行重复的连接就会失败。也就是为了避免重复连接。

(7)在ui中编辑信号与槽

  举个例子,我们想要在ui中实现点击按钮,能够控制进度条的显示与隐藏。

新建GUI项目SignalsAndSlots2,类名SignalsAndSlots2,基类选择QWidget。打开ui文件。在编辑界面中拖入一个Progress Bar和一个PushButton

在编辑窗口中工具栏中选择编辑信号槽(Edit Signals/Slots)或者直接按F4

点击需要发送信号的控件PushButton,并拖动箭头到需要接收信号的控件Progress Bar

此时,会弹出连接窗口,勾选左下角“显示从QWidget中继承的信号和槽”,信号选择toggled(bool),槽选择setVisible(bool)

再选择工具栏中的编辑窗口(Edit Widgets)或直接按F3,选择PushButton,修改按钮的checkable属性为true。checkable表示点击按钮后处于按下状态,若再点击按钮,才会弹起。checked为true,表示按钮已经被按下。

运行程序,弹起按钮,进度条隐藏,按下按钮,进度条显示

此时,我们再打开生成的ui_signalsandslots2.h文件,看到下面这行代码:

QObject::connect(pushButton, SIGNAL(toggled(bool)), progressBar, SLOT(setVisible(bool)));

编辑的信号槽已经被写到这个头文件中了。

(8)通过对象名关联信号与槽

切回到ui界面,在pushButton上右击,选择“转到槽...”,在弹出的对话框中选择toggled(bool)。此时,在头文件和源文件中已经增加了on_pushButton_toggled()函数,在函数体中输入如下代码:

void SignalsAndSlots2::on_pushButton_toggled(bool checked)
{
    if (checked)
    {
        ui->pushButton->setText("隐藏进度条");
    }
    else
    {
        ui->pushButton->setText("显示进度条");
    }
}

然后打开ui_signalsandslots2.h文件,发现又多了一行:

QMetaObject::connectSlotsByName(SignalsAndSlots2);

由此,我们总结信号槽自动关联规则如下:

  1. 使用QObject::setObjectName()方法为对象设置名称。

  2. 调用QMetaObject::connectSlotsByName()启用自动关联。

  3. 用下划线"_"将"on",“对象名”,“信号名”连接后命名的函数,即:on_对象名_信号名(参数)

这样就可以实现信号槽的自动连接啦。

(9)QSignalMapper

  一般点击一个按钮,并且想将预先定好的参数一同发送出去时,由于按钮的点击事件clicked()并没有参数,那么按照一般的做法就会先定义一个槽与clicked()信号关联,然后获取参数,再通过自定义的信号将该参数发送出去。

  这个过程无疑是繁琐的,为此,Qt提供了QSignalMapper这个类来解决这个问题。同时,这个类可以连接多个按钮,匹配发送信号的对象对应的整数、字符串,窗口指针,继承于QObject的对象参数重新发送它们。

现在我们创建一个类似计算器的窗口:

新建GUI项目SignalMapperWidget,类名SignalMapperWidget,基类选择QWidget。在构造函数中添加如下代码:

SignalMapperWidget::SignalMapperWidget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::SignalMapperWidget)
{
    ui->setupUi(this);
    //创建垂直布局,将垂直布局作为主布局
    QVBoxLayout* vLayout = new QVBoxLayout(this);

    //创建编辑框,用于显示点击按钮的文字,并且文字在右边显示
    QLineEdit* edit = new QLineEdit;
    edit->setAlignment(Qt::AlignRight);
    vLayout->addWidget(edit);//将编辑框加入到垂直布局中

    //创建信号匹配器
    QSignalMapper* signalMapper = new QSignalMapper(this);

    //创建0-9数字键,并都加入到网格布局中
    QGridLayout *gridLayout = new QGridLayout;
    for (int i = 0; i < 10; ++i)
    {
        QString txt = QString::number(i);
        QPushButton *button = new QPushButton(txt);
        connect(button, SIGNAL(clicked()), signalMapper, SLOT(map()));
        signalMapper->setMapping(button, txt);//将按钮和要发送的字符串配对
        gridLayout->addWidget(button, i / 3, i % 3);//一行显示3列
    }

    //连接配对信号和设置文字槽
    connect(signalMapper, SIGNAL(mapped(QString)),
            edit, SLOT(setText(QString)));

    vLayout->addLayout(gridLayout);
    resize(200, 200);
}

编译并运行。

(10)相关函数

  1、怎么获取信号发送者

  当多个信号连接一个槽时,有时需要判断是哪个对象发来的,那么可以调用sender()函数获取对象指针,返回为QObject指针。

QObject* sender() ;

  2、解绑定信号槽

  不需要信号槽连接时,可使用disconnect()进行解绑定。其写法和connect一样,只需要将connect换成disconnect即可。

  (11)现在来看看信号与槽怎么到底怎么实现的

  新建控制台应用程序,再添加一个新类SignalsAndSlots3,各自定义一个信号和槽,代码如下:

  在 signalsandslots3.h 文件中

class SignalsAndSlots3 : public QObject
{
    Q_OBJECT
public:
    explicit SignalsAndSlots3(QObject *parent = 0);

signals:
    void sigPrint(const QString& text);

public slots:
    void sltPrint(const QString& text);
};

在 signalsandslots3.cpp 文件中

#include <QDebug>
#include "signalsandslots3.h"

SignalsAndSlots3::SignalsAndSlots3(QObject *parent) : QObject(parent)
{
    connect(this, SIGNAL(sigPrint(QString)), 
            this, SLOT(sltPrint(QString)));
    emit sigPrint("Hello");
}

void SignalsAndSlots3::sltPrint(const QString &text)
{
    qDebug() << text;
}

在 main.cpp  文件中

#include <QCoreApplication>
#include "signalsandslots3.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    SignalsAndSlots3 s;
    return a.exec();
}

这里为了说明信号与槽的原理,写了最简单的信号与槽,编译运行程序。

然后打开Qt自动生成的Makefile.Debug文件。找到下面这一行:

SOURCES = F:\Study\Qt\Projects\QtShareCode\chapter2\2-3\SignalsAndSlots3\main.cpp \
F:\Study\Qt\Projects\QtShareCode\chapter2\2-3\SignalsAndSlots3\signalsandslots3.cppdebug\moc_signalsandslots3.cpp

快看加粗的两个玩意:由此可知,Qt又额外生成了moc_signalsandslots3.cpp文件,其名称为,同名源文件前加上了moc前缀。

这里就讲到了moc元对象预编译器。

(12)moc预编译器

moc(Meta-Object Compiler)元对象预编译器。

moc读取一个c++头文件。如果它找到包含Q_OBJECT宏的一个或多个类声明,它会生成一个包含这些类的元对象代码的c++源文件,并且以moc_作为前缀。

信号和槽机制、运行时类型信息和动态属性系统需要元对象代码。

由moc生成的c++源文件必须编译并与类的实现联系起来。

通常,moc不是手工调用的,而是由构建系统自动调用的,因此它不需要程序员额外的工作。

(13)Q_OBJECT 宏

#define Q_OBJECT \

public: \    
    Q_OBJECT_CHECK \    
    QT_WARNING_PUSH \    
    Q_OBJECT_NO_OVERRIDE_WARNING \    
    static const QMetaObject staticMetaObject; \    
    virtual const QMetaObject *metaObject() const; \    
    virtual void *qt_metacast(const char *); \    
    virtual int qt_metacall(QMetaObject::Call, int, void **); \    
    QT_TR_FUNCTIONS \private: \    
    Q_OBJECT_NO_ATTRIBUTES_WARNING \    
    Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \    
    QT_WARNING_POP \    struct QPrivateSignal {}; \    
    QT_ANNOTATE_CLASS(qt_qobject, "")

都知道宏会在预编译期被具体的字符串所代替,那么我们在头文件中用到的Q_OBJECT宏就会被展开为上面的代码。

你也可以在signalsandslots3.h中用上面的代码替换掉Q_OBJECT ,但你会发现还需要实现Q_OBJECT扩展后所带来的变量和函数的定义。而这些定义都已经被写入到了moc_signalsandslots3.cpp文件中了,这也就是为什么在Makefile中需要将moc_signalsandslots3.cpp一起编译的原因了。否则,这个类是不完整的,那肯定也是不可能编译通过的。

扒开生成的moc_signalsandslots3.cpp看看:

从头文件中得出,我们首先需要定义

static const QMetaObject staticMetaObject;

你需要从下往上看代码:

/*6.存储类中的函数及参数信息*/
struct qt_meta_stringdata_SignalsAndSlots3_t
{
    QByteArrayData data[5]; //函数加参数共5个
    char stringdata0[41];   //总字符串长41
};

/*5.切分字符串*/
#define QT_MOC_LITERAL(idx, ofs, len) \    
    Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \ 
    qptrdiff(offsetof(qt_meta_stringdata_SignalsAndSlots3_t, stringdata0) + ofs \        - idx * sizeof(QByteArrayData)) \    )
    
/*4.初始化qt_meta_stringdata_SignalsAndSlots3,并且将所有函数拼接成字符串,中间用\0分开*/

static const qt_meta_stringdata_SignalsAndSlots3_t qt_meta_stringdata_SignalsAndSlots3 = {
    {
        QT_MOC_LITERAL(0, 0, 16), // "SignalsAndSlots3" (索引,偏移量,偏移长度),类名
        QT_MOC_LITERAL(1, 17, 8), // "sigPrint"
        QT_MOC_LITERAL(2, 26, 0), // ""
        QT_MOC_LITERAL(3, 27, 4), // "text"
        QT_MOC_LITERAL(4, 32, 8)  // "sltPrint"    
    },
    "SignalsAndSlots3\0sigPrint\0\0text\0"   //注意这是一行字符串
    "sltPrint"
};
#undef QT_MOC_LITERAL

/*3.存储元对象信息,包括信号和槽机制、运行时类型信息和动态属性系统*/
static const uint qt_meta_data_SignalsAndSlots3[] = {
 // content:
       7,       // revision
       0,       // classname
       0,    0, // classinfo
       2,   14, // methods
       0,    0, // properties
       0,    0, // enums/sets
       0,    0, // constructors
       0,       // flags
       1,       // signalCount

 // signals: name, argc, parameters, tag, flags
              1,    1,      24,       2,  0x06    /* Public */,

 // slots:   name, argc, parameters, tag, flags
              4,    1,      27,       2,  0x0a    /* Public */,

 // signals: parameters
    QMetaType::Void, QMetaType::QString,    3,

 // slots: parameters
    QMetaType::Void, QMetaType::QString,    3,

       0        // eod
};

/*2.执行对象所对应的信号或槽,或查找槽索引*/
void SignalsAndSlots3::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id,         
                                             void **_a)
{
    if (_c == QMetaObject::InvokeMetaMethod) 
    {
        SignalsAndSlots3 *_t = static_cast<SignalsAndSlots3 *>(_o);
        Q_UNUSED(_t)
        switch (_id) 
        {
           case 0: _t->sigPrint((*reinterpret_cast< const QString(*)>(_a[1]))); break;
           case 1: _t->sltPrint((*reinterpret_cast< const QString(*)>(_a[1]))); break;
           default: ;
        }
    } 
    else if (_c == QMetaObject::IndexOfMethod) 
    {
        int *result = reinterpret_cast<int *>(_a[0]);
        void **func = reinterpret_cast<void **>(_a[1]);
        {
            typedef void (SignalsAndSlots3::*_t)(const QString & );
            if (*reinterpret_cast<_t *>(func) == static_cast<_t> 
                 (&SignalsAndSlots3::sigPrint)) 
            {
                *result = 0;
                return;
            }
        }
    }
}

/*1.首先初始化静态变量staticMetaObject,并为QMetaObject中的无名结构体赋值*/
const QMetaObject SignalsAndSlots3::staticMetaObject = 
{
    { 
      &QObject::staticMetaObject,   //静态变量地址
      qt_meta_stringdata_SignalsAndSlots3.data,
      qt_meta_data_SignalsAndSlots3,  
      qt_static_metacall,           //用于执行对象所对应的信号或槽,或查找槽索引
      Q_NULLPTR, 
      Q_NULLPTR
    }
};

从上面的代码中,我们得知Qt的元对象系统:信号槽,属性系统,运行时类信息都存储在静态对象staticMetaObject中。

接下来是对另外三个公有接口的定义,在你的代码中也可以直接调用下面的函数:

//获取元对象,可以调用this->metaObject()->className();获取类名称
const QMetaObject *SignalsAndSlots3::metaObject() const
{
    return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
}

//这个函数负责将传递来到的类字符串描述,转化为void*
void *SignalsAndSlots3::qt_metacast(const char *_clname)
{
    if (!_clname) return Q_NULLPTR;
    if (!strcmp(_clname, qt_meta_stringdata_SignalsAndSlots3.stringdata0))
        return static_cast<void*>(const_cast< SignalsAndSlots3*>(this));
    return QObject::qt_metacast(_clname);
}

//调用方法
int SignalsAndSlots3::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{
    _id = QObject::qt_metacall(_c, _id, _a);
    if (_id < 0)
        return _id;
    if (_c == QMetaObject::InvokeMetaMethod) {
        if (_id < 2)
            qt_static_metacall(this, _c, _id, _a);
        _id -= 2;
    } else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {
        if (_id < 2)
            *reinterpret_cast<int*>(_a[0]) = -1;
        _id -= 2;
    }
    return _id;
}

接下来,我们发现在头文件中声明的信号,其真正定义是在这里,这也是为什么signal不需要我们定义的原因。

// SIGNAL 0
void SignalsAndSlots3::sigPrint(const QString & _t1){
    void *_a[] = { Q_NULLPTR, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
    QMetaObject::activate(this, &staticMetaObject, 0, _a);
}

(14)关键字

  1、signals

# define QT_ANNOTATE_ACCESS_SPECIFIER(x)
# define Q_SIGNALS public QT_ANNOTATE_ACCESS_SPECIFIER(qt_signal)
# define signals Q_SIGNALS

看到了吗,如果signals被展开的话就是public,所以所有的信号都是公有的,也不需要像槽一样加public,protected,private的限定符。

  2、slots

# define QT_ANNOTATE_ACCESS_SPECIFIER(x)
# define Q_SLOTS QT_ANNOTATE_ACCESS_SPECIFIER(qt_slot)
# define slots Q_SLOTS

slots和signals一样,只是没有了限定符,所以它是否可以被对象调用,就看需求了。

  3、emit

它的宏定义:# define emit

mit后面也没有字符串!当它被替换的时候,程序其实就是调用了sigPrint()函数,而不是真正意义上的发送一个信号,有很多初学者都是认为当emit的时候,Qt会发信号,所以才会有很多人问“当emit之后,会不会立即执行其后面的代码”。当然,如果想让emit后面的代码不需要等槽函数执行完就开始执行的话,可以设置connect第5个参数。

Qt之所以使用# define emit,是因为编译器并不认识emit啊,所以把它定义成一个空的宏就可以通过编译。

(15)信号与槽的实际流程

通过以上的代码和一顿操作,我们来总结一下信号与槽的具体流程。

  1. moc查找头文件中的signals,slots,标记出信号和槽。

  2. 将信号槽信息存储到类静态变量staticMetaObject中,并且按声明顺序进行存放,建立索引。

  3. 当发现有connect连接时,将信号槽的索引信息放到一个map中,彼此配对。

  4. 当调用emit时,调用信号函数,并且传递发送信号的对象指针,元对象指针,信号索引,参数列表到active函数

  5. 通过active函数找到在map中找到所有与信号对应的槽索引

  6. 根据槽索引找到槽函数,执行槽函数。

以上,便是信号槽的整个流程,总的来说就是一个“注册-索引”机制,并不存在发送系统信号之类的事情。

(16)信号与槽的注意点

1、槽的属性

public slots:在这个区内声明的槽意味着所有对象都可将信号和之相连接。这对于组件编程非常有用,你能创建彼此互不了解的对象,将他们的信号和槽进行连接以便信息能够正确的传递。 
protected slots:在这个区内声明的槽意味着当前类及其子类能将信号和之相连接。这适用于那些槽,他们是类实现的一部分,不过其界面接口却面向外部。
private slots:在这个区内声明的槽意味着只有类自己能将信号和之相连接。这适用于联系非常紧密的类。

2、如果发射者和接收者属于同一个对象的话,那么在connect调用中接收者参数能省略。

3、有三种情况可使用disconnect()函数:
(1)断开和某个对象相关联的所有对象。事实上,当我们在某个对象中定义了一个或多个信号,这些信号和另外若干个对象中的槽相关联,如果我们要切断这些关联的话,就能利用这个方法,非常之简洁。 
disconnect( myObject, 0, 0, 0 ) 或 myObject->disconnect() 

(2)断开和某个特定信号的所有关联。
disconnect( myObject, SIGNAL(mySignal()), 0, 0 ) 或 myObject->disconnect( SIGNAL(mySignal()) ) 

(3)断开两个对象之间的关联。 
disconnect( myObject, 0, myReceiver, 0 ) 或 myObject->disconnect( myReceiver ) 

在disconnect函数中0能用作一个通配符,分别表示所有信号、所有接收对象、接收对象中的所有槽函数。不过发射者sender不能为0,其他三个参数的值能等于0。

4、定义不能用在signal和slot的参数中。 
    因为moc工具不扩展#define,所以在signals和slots中携带参数的宏就不能正确地工作。

#define SIGNEDNESS(a) unsigned a
	signals:
		void someSignal( SIGNEDNESS(a) ); 

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

6、函数指针不能直接作为信号或槽的参数,是不合法的,可以取巧,用typedef,如下:

typedef void (*ApplyFunctionType)(QList*, void*); 
	public slots: 
		void apply( ApplyFunctionType, char *); 

7、信号和槽不能有缺省参数:因为signal与slot绑定是发生在运行时。

8、信号和槽也不能携带模板类参数

如果将信号、槽声明为模板类参数的话,即使moc工具不报告错误,也不可能得到预期的结果,也可以取巧,用typedef

typedef pair IntPair;
	public slots: 
	void setLocation (IntPair location); 

9、嵌套的类不能位于信号或槽区域内,也不能有信号或槽。即类b嵌套在类a内,想在类b中声明信号与槽是不行的。

10、友元声明不能位于信号或槽声明区内。相反,他们应该在普通C++的private、protected或public区内进行声明

(17)槽的执行时间大于信号发送间隔怎么办?

有两种情况:
(1)如果需要对每个发来的信号都做出处理,那么有两种方式来解决,即在信号与槽的connect函数中明确第五个参数,将其设置成DirectConnection方式阻塞时编程,或者设置成BlockingQueuedConnection阻塞的方式都可以很好的解决;
(2)如果只需要对最新的信号做处理,那么这里也给出两种方案来处理:
   a、槽所在线程设置bool状态,信号所在线程通过判定这个bool的状态来确定是否发送信号; 
   b、槽执行完毕,则向信号所在线程发送返回值,信号所在线程通过判定发来的这个返回值来判定是否继续对槽所在线程发送新的信号。

(18)连接断开一段时间后,然后又想正常连接的怎么办?

可以用阻塞或者断开信号槽的方法来处理。

阻塞方法:bool QObject::blockSignals(bool block)

在阻塞模式下,这个对象发送的信号都会被阻塞,解除阻塞后则不再阻塞。返回值为之前状态的阻塞情况。

断开信号槽的方法:

bool QObject::disconnect(const QObject * sender, const char * signal, const QObject * receiver, const char * method)
disconnect将断开sender和receiver,成功返回true
    

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值