c++ Qt信号槽原理

1、说明

使用Qt已经好几年了,一直以为自己懂Qt,熟悉Qt,使用起来很是熟练,无论什么项目,都喜欢用Qt编写。但真正去看Qt的源码,去理解Qt的思想也就近两年的事。

本次就着重介绍一下Qt的核心功能--信号槽机制,相信接触过Qt的人都能很熟悉地使用,甚至,大部分人还能轻松地说出信息槽的几种用法。但是信号槽的核心可不是简单说说就能说清楚的。

那么,本次,就从Qt的源码中讲解一下信号槽的机制。

其实,直到写这篇文章,我也没有完全看明白相关的源码,只是明白了其中的大部分以及使用机制,其中还有很多细节的,留待以后整理。

如果错误还请大家指正。

2、环境以及知识点

Qt版本:Qt 5.5.1

系统:windows 10

在阅读本文前,希望你能:

  1. 熟练使用C++,了解make的编译方法和过程;
  2. 熟练使用Qt的信号槽功能,对信号槽的写法以及4和5的区别了如指掌;
  3. QMetaObject元数据系统;
  4. 懂一些设计模式,能理解观察者模式;

3、信号槽源码分析

以下将按照SIGNAL/SLOT宏定义连接信号槽的方式做讲解

接下来将会从按照以下的步骤来进行分析:

  1. Qt元数据系统;
  2. moc预编译;
  3. Q_OBJECT宏;
  4. signals和slots关键字以及emit;
  5. SIGNAL()和SLOT()宏;
  6. connect 方法;
  7. 触发信号;

3.1、Qt的元数据系统

没看过Qt源码的同学可能会对QMetaObject有些陌生,我们打开Qt手册,查看此类的说明,介绍如下:

The QMetaObject class contains meta-information about Qt objects.

The Qt Meta-Object System in Qt is responsible for the signals and slots inter-object communication mechanism, runtime type information, and the Qt property system. A single QMetaObject instance is created for each QObject subclass that is used in an application, and this instance stores all the meta-information for the QObject subclass. This object is available as QObject::metaObject().

这里是说,QMetaObject包含了Qt的元对象信息。元对象机制类似Java的反射机制。通过继承QObject,并在定义类是添加一定Qt内置宏,能在运行时动态获取Qt的信号槽、类型信息以及相关属性。

一个简答的例子

void MainWindow::onClickButton()
{
  qDebug()<<"on click button";
  const QMetaObject* metaObject = this->metaObject();
  qDebug()<<metaObject->className();
  qDebug()<<metaObject->superClass()->className();

  int methodIndex = metaObject->indexOfMethod("testFunction()");
  qDebug()<<methodIndex;
  qDebug()<<metaObject->method(methodIndex).name();
  metaObject->method(methodIndex).invoke(this);

  QMetaObject::invokeMethod(this, "testFunction");
}

如上,一个简单的例子,通过QMetaObject,我们得到了该对象的类名、父类名、方法并调用了该方法

怎么样,熟悉Java的小伙伴已经发现了,这不就是Java的反射吗,谁说C++没有反射呢

那么,Qt是如何实现”反射“的呢?答案是使用moc预编译

3.2、moc编译

moc全称Meta-Object Compiler,即元对象编译器。我们可以在Qt的安装目录的bin文件下看到moc工具,moc.exe。Qt的构建的时候,会调用该工具生成moc文件,我们在编译目录下看到的moc_xxx.cpp文件就是该工具生成的。

Qt的MinGW版本使用的是qmake进行项目管理,它和cmake功能类似,但没有后者强大。使用qmake生成Makefile后,我们打开Makefile文件,我们可以狠清楚地看到有一个调用moc.exe工具的地方,代码太多,就不列出来了。

此外,我们还发现,并不是所有的代码都会生成moc_xxx.cpp文件的,只有使用了 Q_OBJECT 宏的类文件,才会生成。没有错,moc工具就是根据 Q_OBJECT 宏来生成moc_xxx.cpp文件的,而实现“反射”的元数据系统的也是依靠Q_OBJECT的。

到此,我们其实已经能够大概理清qmake项目的构建步骤了。步骤和常用的cmake项目类似,区别就是,qmake生成的Makefile文件种,会写有调用moc工具的指令,以达到moc_xxx.cpp文件的生成。

我们可以使用moc工具手动生成moc_xxx.cpp,使用指令 moc.exe mainwindow.h,即会在控制台打印moc文件信息,也可以使用 -o 参数来将生成的内容写入文件,其余参数可以使用 moc.exe -h 来查看

3.3、Q_OBJECT

我们可以从源代码中查看 Q_OBJECT 的内容,这里调整一个格式,使用 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_WARNING_POP
  QT_TR_FUNCTIONS
private:
  Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **);
  struct QPrivateSignal {};

可以看到,这里多出几个方法和一些变量

  1. 属性staticMetaObject,元数据对象,可以从中获取当前类的元数据;
  2. 方法metaObject(),获取元数据对象指针,大多数情况下,返回staticMetaObject指针;
  3. 方法qt_metacast(),原数据对象类型转换,转换成指定的类型,使用时一般传入父类的名称字符串;
  4. 方法qt_metacall(),执行函数的回调,信号触发;
  5. 方法qt_static_metacall(),回调函数,被qt_metacall()调用,内部执行槽;

这里的几个方法都没有实现体,因为实现部分会有 moc 工具生成,在moc_xxx.cpp 文件中可以查看这些方法的实现体

3.4、signals和slots

signals 用于声明自定义信号,slots 用于声明槽函数,emit 用于发送信号,我们可以从源码中查看这三个宏定义

define slots
define signals public
define emit

可以看出,这三个宏几乎什么都没有做,signals 就是声明所谓的信号是public方法,而slots和emit更是为空,标准C++在编译的时候,根本不受这三个宏的影响,那么它们的用处在哪里呢?在moc工具调用和connect连接的时候。

打开moc_xxx.cpp文件,对比查看信号

signals:
  void clickButton(int value);

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

// SIGNAL 1
void MainWindow::clickButton2()
{
  QMetaObject::activate(this, &staticMetaObject, 1, Q_NULLPTR);
}

其实,信号就是方法,而 emit clickButton() 发送信号,就是调用 clickButton() 方法,换言之,触发信号,就算不要emit也无妨

3.5、SIGNAL()和SLOT()

查看源码

# define SLOT(a)   qFlagLocation("1"#a QLOCATION)
# define SIGNAL(a)  qFlagLocation("2"#a QLOCATION)

qFlagLocation() 源码如下:

const char *qFlagLocation(const char *method)
{
  QThreadData *currentThreadData = QThreadData::current(false);
  if (currentThreadData != 0)
    currentThreadData->flaggedSignatures.store(method);
  return method;
}

store() 方法

void store(const char* method)
{ locations[idx++ % Count] = method; }

所以 SIGNAL(clickButton()) 宏展开为 qFlagLocation("2"clickButton(int) QLOCATION)

SLOT() 同理,这里的1和2,最后会添加到信号槽的前面,其实是为了区分信号和槽,源码中还有一个0在 METHOD() 宏

qFlagLocation 方法的作用是将信号槽转换成字符串保存起来,store 方法中,locations是个二维数组,而 idx 每次都加一,保证信号和槽的不同的方法存储在不同的数组中。

我们也可以在代码中打印出来看下:

qDebug()<<SIGNAL(clickButton(int));	//2clickButton(int)
qDebug()<<SLOT(onClickButton());	//1onClickButton()

3.6、connect方法

最后,就是最关键的connect方法,做了一些简单的注释

QMetaObject::Connection QObject::connect(const QObject *sender, const char *signal,
                   const QObject *receiver, const char *method,
                   Qt::ConnectionType type)
{
  if (sender == 0 || receiver == 0 || signal == 0 || method == 0) {
    qWarning("QObject::connect: Cannot connect %s::%s to %s::%s",
         sender ? sender->metaObject()->className() : "(null)",
         (signal && *signal) ? signal+1 : "(null)",
         receiver ? receiver->metaObject()->className() : "(null)",
         (method && *method) ? method+1 : "(null)");
    return QMetaObject::Connection(0);
  }
  QByteArray tmp_signal_name;

  if (!check_signal_macro(sender, signal, "connect", "bind"))
    return QMetaObject::Connection(0);
  const QMetaObject *smeta = sender->metaObject();//获取发送者的元数据对象
  const char *signal_arg = signal;//信号
  ++signal; //skip code
  QArgumentTypeArray signalTypes;//信号参数类型数组
  Q_ASSERT(QMetaObjectPrivate::get(smeta)->revision >= 7);
  //信号转换为签名,并得到信号参数类型数组
  QByteArray signalName = QMetaObjectPrivate::decodeMethodSignature(signal, signalTypes);
  //找到信号索引
  int signal_index = QMetaObjectPrivate::indexOfSignalRelative(
      &smeta, signalName, signalTypes.size(), signalTypes.constData());
  //小于0表示,表示信号索引有问题
  if (signal_index < 0) {
    // check for normalized signatures
    //将信号重新规范化,再进行上面的签名转换,并重新得到索引
    tmp_signal_name = QMetaObject::normalizedSignature(signal - 1);
    signal = tmp_signal_name.constData() + 1;

    //重新进行签名转换,并得到参数类型列表
    signalTypes.clear();
    signalName = QMetaObjectPrivate::decodeMethodSignature(signal, signalTypes);
    smeta = sender->metaObject();
    signal_index = QMetaObjectPrivate::indexOfSignalRelative(
        &smeta, signalName, signalTypes.size(), signalTypes.constData());
  }
  //重新获取的信号索引还是无效,则是头文件中信号的定义出错,找不到信号,报错信号不存在
  if (signal_index < 0) {
    err_method_notfound(sender, signal_arg, "connect");
    err_info_about_objects("connect", sender, receiver);
    return QMetaObject::Connection(0);
  }
  //根据当前信号的索引找到最原始的信号的索引,因为信号是可以被继承,这里找的祖先信号
  signal_index = QMetaObjectPrivate::originalClone(smeta, signal_index);
  signal_index += QMetaObjectPrivate::signalOffset(smeta);//信号的索引再加上信号的偏移量

  QByteArray tmp_method_name;
  //提取槽的编码,应该是QSLOT_CODE或者QSIGNAL_CODE,用于判断槽是信号还是方法
  int membcode = extract_code(method);

  //检查槽编码,槽可以是槽函数或者信号,初次以为,都无效
  if (!check_method_code(membcode, receiver, method, "connect"))
    return QMetaObject::Connection(0);
  const char *method_arg = method;
  ++method; // skip code

  QArgumentTypeArray methodTypes;
  //转换槽签名,并获取槽的参数类型列表
  QByteArray methodName = QMetaObjectPrivate::decodeMethodSignature(method, methodTypes);
  const QMetaObject *rmeta = receiver->metaObject();//获取接受者的元数据对象
  int method_index_relative = -1;
  Q_ASSERT(QMetaObjectPrivate::get(rmeta)->revision >= 7);
  switch (membcode) {
  case QSLOT_CODE://接受者是槽函数
    method_index_relative = QMetaObjectPrivate::indexOfSlotRelative(
        &rmeta, methodName, methodTypes.size(), methodTypes.constData());
    break;
  case QSIGNAL_CODE://接受者是信号
    method_index_relative = QMetaObjectPrivate::indexOfSignalRelative(
        &rmeta, methodName, methodTypes.size(), methodTypes.constData());
    break;
  }
  //槽的索引为-1,表示无效
  if (method_index_relative < 0) {
    // check for normalized methods
    //将槽进行规范化处理,并重新转换槽签名
    tmp_method_name = QMetaObject::normalizedSignature(method);
    method = tmp_method_name.constData();

    methodTypes.clear();
    methodName = QMetaObjectPrivate::decodeMethodSignature(method, methodTypes);
    // rmeta may have been modified above
    //接受者元数据对象前面可能被修改过,这里重新获取
    rmeta = receiver->metaObject();
    //重新获取槽的索引
    switch (membcode) {
    case QSLOT_CODE:
      method_index_relative = QMetaObjectPrivate::indexOfSlotRelative(
          &rmeta, methodName, methodTypes.size(), methodTypes.constData());
      break;
    case QSIGNAL_CODE:
      method_index_relative = QMetaObjectPrivate::indexOfSignalRelative(
          &rmeta, methodName, methodTypes.size(), methodTypes.constData());
      break;
    }
  }

  //如果还找不到,则说明槽定义有误,报错
  if (method_index_relative < 0) {
    err_method_notfound(receiver, method_arg, "connect");
    err_info_about_objects("connect", sender, receiver);
    return QMetaObject::Connection(0);
  }

  //检查信号和槽的参数
  if (!QMetaObjectPrivate::checkConnectArgs(signalTypes.size(), signalTypes.constData(),
                       methodTypes.size(), methodTypes.constData())) {
    qWarning("QObject::connect: Incompatible sender/receiver arguments"
         "\n    %s::%s --> %s::%s",
         sender->metaObject()->className(), signal,
         receiver->metaObject()->className(), method);
    return QMetaObject::Connection(0);
  }

  int *types = 0;
  //队列连接检查,参数要是基本类型,或者使用元数据注册
  if ((type == Qt::QueuedConnection)
      && !(types = queuedConnectionTypes(signalTypes.constData(), signalTypes.size()))) {
    return QMetaObject::Connection(0);
  }

#ifndef QT_NO_DEBUG
  //打印调试信息
  QMetaMethod smethod = QMetaObjectPrivate::signal(smeta, signal_index);
  QMetaMethod rmethod = rmeta->method(method_index_relative + rmeta->methodOffset());
  check_and_warn_compat(smeta, smethod, rmeta, rmethod);
#endif
  QMetaObject::Connection handle = QMetaObject::Connection(QMetaObjectPrivate::connect(
    sender, signal_index, smeta, receiver, method_index_relative, rmeta ,type, types));
  return handle;
}
方法代码很多很杂,但无非就是检查信号槽的格式,获取参数列表, 最后保存起来

QScopedPointer<QObjectPrivate::Connection> c(new QObjectPrivate::Connection);
c->sender = s; //发送者对象
c->signal_index = signal_index; //信号索引
c->receiver = r;  //接受者对象
c->method_relative = method_index; //槽索引
c->method_offset = method_offset;  //槽偏移
c->connectionType = type;      //连接方式
c->isSlotObject = false;
c->argumentTypes.store(types);
c->nextConnectionList = 0;
c->callFunction = callFunction;//静态回调函数

//在发送者元数据内加上连接信息
//信号发送者的对象内存中保存了连接的信息,包括槽的对象,槽地址,连接方式等
QObjectPrivate::get(s)->addConnection(signal_index, c.data());

3.7、触发信号

这时候再回过头来看3.4中的信号触发,我们知道,emit信号就是调用moc文件中的方法,方法的核心就是 QMetaObject::activate()

直接看该方法中调用槽函数的一段

//因为一个信号可能连接多个槽,这里循环遍历链表进行调用
do {
	QObjectPrivate::Connection *c = list->first;
	if (!c) continue;
	// We need to check against last here to ensure that signals added
	// during the signal emission are not emitted in this emission.
	QObjectPrivate::Connection *last = list->last;

	do {
		if (!c->receiver)
			continue;

		QObject * const receiver = c->receiver;
		const bool receiverInSameThread = currentThreadId == receiver->d_func()->threadData->threadId;

		// determine if this connection should be sent immediately or
		// put into the event queue
		//直接连接并且发送和接受不再一个线程中,或者队列连接,则放入事件队列中
		//可知,直接连接并且发送和接受不在同一个线程,则效果和队列连接相同
		if ((c->connectionType == Qt::AutoConnection && !receiverInSameThread)
			|| (c->connectionType == Qt::QueuedConnection)) {
			queued_activate(sender, signal_index, c, argv ? argv : empty_argv, locker);
			continue;
#ifndef QT_NO_THREAD
			//阻塞式队列连接
		} else if (c->connectionType == Qt::BlockingQueuedConnection) {
			locker.unlock();
			//在同一个线程,则报错
			if (receiverInSameThread) {
				qWarning("Qt: Dead lock detected while activating a BlockingQueuedConnection: "
				"Sender is %s(%p), receiver is %s(%p)",
				sender->metaObject()->className(), sender,
				receiver->metaObject()->className(), receiver);
			}
			QSemaphore semaphore;//资源计数器,avail为0
			QMetaCallEvent *ev = c->isSlotObject ?
				new QMetaCallEvent(c->slotObj, sender, signal_index, 0, 0, argv ? argv : empty_argv, &semaphore) :
				new QMetaCallEvent(c->method_offset, c->method_relative, c->callFunction, sender, signal_index, 0, 0, argv ? argv : empty_argv, &semaphore);
			//根据连接信息构造一个事件,并添加到接受者的 事件队列中
			QCoreApplication::postEvent(receiver, ev);
			//信号发送者的线程阻塞,acquire资源数为1,>avail(0),这里阻塞
			//当槽执行玩之后释放,这里的avail才会增加,阻塞结束
			semaphore.acquire();
			locker.relock();
			continue;
#endif
		}

		QConnectionSenderSwitcher sw;

		if (receiverInSameThread) {
			sw.switchSender(receiver, sender, signal_index);
		}
		const QObjectPrivate::StaticMetaCallFunction callFunction = c->callFunction;
		const int method_relative = c->method_relative;
		if (c->isSlotObject) {
			c->slotObj->ref();
			QScopedPointer<QtPrivate::QSlotObjectBase, QSlotObjectBaseDeleter> obj(c->slotObj);
			locker.unlock();
			obj->call(receiver, argv ? argv : empty_argv);

			// Make sure the slot object gets destroyed before the mutex is locked again, as the
			// destructor of the slot object might also lock a mutex from the signalSlotLock() mutex pool,
			// and that would deadlock if the pool happens to return the same mutex.
			obj.reset();

			locker.relock();
		} else if (callFunction && c->method_offset <= receiver->metaObject()->methodOffset()) {
			//we compare the vtable to make sure we are not in the destructor of the object.
			locker.unlock();
			const int methodIndex = c->method();
			if (qt_signal_spy_callback_set.slot_begin_callback != 0)
				qt_signal_spy_callback_set.slot_begin_callback(receiver, methodIndex, argv ? argv : empty_argv);

			callFunction(receiver, QMetaObject::InvokeMetaMethod, method_relative, argv ? argv : empty_argv);

			if (qt_signal_spy_callback_set.slot_end_callback != 0)
				qt_signal_spy_callback_set.slot_end_callback(receiver, methodIndex);
			locker.relock();
		} else {
			const int method = method_relative + c->method_offset;
			locker.unlock();

			if (qt_signal_spy_callback_set.slot_begin_callback != 0) {
				qt_signal_spy_callback_set.slot_begin_callback(receiver,
															method,
															argv ? argv : empty_argv);
			}

			metacall(receiver, QMetaObject::InvokeMetaMethod, method, argv ? argv : empty_argv);

			if (qt_signal_spy_callback_set.slot_end_callback != 0)
				qt_signal_spy_callback_set.slot_end_callback(receiver, method);

			locker.relock();
		}

		if (connectionLists->orphaned)
			break;
	} while (c != last && (c = c->nextConnectionList) != 0);

	if (connectionLists->orphaned)
		break;
} while (list != &connectionLists->allsignals &&
	//start over for all signals;
	((list = &connectionLists->allsignals), true));

由上面代码,我们大概可以理解信号槽的几种连接方式:

  1. 默认连接并且信号槽的对象不在同一个线程中,则效果和队列连接类似;
  2. 阻塞时队列连接,信号和槽对象不同处于同一个线程中;
  3. Qt使用QSemaphore来实现阻塞式的槽函数调用;

4、小结

本次的源码因为种种原因,看的不是很详细,但是理解Qt的信号槽机制绰绰有余了

  1. Qt自带的元数据系统利用C++的宏等特性实现反射机制;
  2. 利用元数据系统,在连接信号槽是将槽的信息(接收对象、槽方法、参数列表、连接方式等)保存在信号的元数据中;
  3. 信号也是方法,方法体有moc工具生成,方法内获取该信号连接的所有槽信息,并依序执行;

直到这里,信号槽的逻辑已经显而易见了,它就是一个变种的观察者模式,槽的信息保存在信号对象中也就是设置回调函数,触发信号也就是执行回调函数,只是Qt库将其中的各种操作细节封装起来了,所以,使用起来,不去关注设计模式的细节,也就容易很多了。不得不说,无论是从设计思路,还是开发技巧上看,Qt的开发者真的很牛叉。

5、第三方信号槽库

信号槽机制是Qt首创,但不是其独有,其他各类C++流行框架也都是互相借鉴,C++标准库的预备役的boost中也有信号槽机制的实现。如果平时开发中需要用到信号槽机制,但是又不想引入这些庞大的类库,可以使用轻量级别的信号槽库:http://sigslot.sourceforge.net,该库不详细介绍,有兴趣的小伙伴自己学习把。

以上就是c++ Qt信号槽原理的详细内容,更多关于c++ Qt信号槽的资料请关注我们其它相关文章!

(0)

相关推荐

  • C++结合QT实现带有优先级的计算器功能

    代码 MyCalculator.h #pragma once #include <QtWidgets/QMainWindow> #include <QStack> #include <QString> #include "ui_MyCalculator.h" class MyCalculator : public QMainWindow { Q_OBJECT public: MyCalculator(QWidget *parent = Q_NULLP

  • C++ Qt属性系统详细介绍

    C++ Qt属性系统详细介绍 Qt提供了一个绝妙的属性系统.跟那些由编译器提供的属性差不多.然而,作为一个独立于编译器和平台的库,Qt不依赖于非标准的编译特性,比如__property 或[property].Qt可以在任何平台上的标准编译器下编译.Qt属性系统基于元数据对象系统--就是那个提供了对象内置信号和槽通讯机制的家伙. 声明属性需要什么 要声明一个属性,需在继承自QObject的类中使用Q_PROPERTY()宏. Q_PROPERTY(type name READ getFuncti

  • Android 和 windows C/C++/QT通讯时字节存储

    ava:采用大端字节序存储数据[低地址存放数据的高位,高地址存放数据的低位,数据高位存放在数组的前面] windows(intel平台):采用小端字节序存储数据[低地址存放数据的低位,高地址存放数据的高位,数据的高位存放在数组的后面](windows接收java发送过来的short,int需要调用ntohs和ntohl来转换到小数端) [数据高位]:0x1234的高位为 0x12 [数据低位]:0x1234的低位为 0x34 如: int ihex = 0x12345678; short she

  • Qt(C++)调用工业相机Basler的SDK使用示例

    简介 由于公司采购的AVT相机不足,需要用Basler相机来弥补,所以我也了解了一下Basler这款相机的SDK.由于Basler这边的相机提供的没有提供Qt的示例,所以我做一个分享出来. 本篇的Demo采用的是工业黑白相机.工业应用中,如果我们要处理的是与图像颜色有关,那么我们最好采用彩色工业相机:如果不是,那么我们最好选用黑白工业相机,因为在同样分辨率下的工业相机,黑白工业教学精度比彩色工业相机高,尤其是在看图像边缘的时候,黑白工业相机的效果更好. 开发环境 Qt:  5.6.2vc2013

  • C++实现幸运大抽奖(QT版)

    本文实例为大家分享了C++实现幸运大抽奖的具体代码,供大家参考,具体内容如下 程序效果: #ifndef DIALOG_H #define DIALOG_H #include <QDialog> #include <QLabel> #include <QPushButton> #include <QTimer> #include <QStringList> class Dialog : public QDialog { Q_OBJECT publ

  • c++ Qt信号槽原理

    1.说明 使用Qt已经好几年了,一直以为自己懂Qt,熟悉Qt,使用起来很是熟练,无论什么项目,都喜欢用Qt编写.但真正去看Qt的源码,去理解Qt的思想也就近两年的事. 本次就着重介绍一下Qt的核心功能--信号槽机制,相信接触过Qt的人都能很熟悉地使用,甚至,大部分人还能轻松地说出信息槽的几种用法.但是信号槽的核心可不是简单说说就能说清楚的. 那么,本次,就从Qt的源码中讲解一下信号槽的机制. 其实,直到写这篇文章,我也没有完全看明白相关的源码,只是明白了其中的大部分以及使用机制,其中还有很多细节

  • 深入理解Qt信号槽机制

    目录 1. 信号和槽概述 1.1 信号的本质 1.2 槽的本质 1.3 信号和槽的关系 1. 信号和槽概述 信号槽是 Qt 框架引以为豪的机制之一.所谓信号槽,实际就是观察者模式(发布-订阅模式).当某个`事件`发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal).这种发出是没有目的的,类似广播.如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,意思是,将想要处理的信号和自己的一个函数(称为槽(slot))绑定来处理这个信号.也就是说,当信号发出时,被连

  • 浅谈Qt信号与槽的各种连接方式

    目录 简介 连接信号槽 connect 函数的第五个参数 信号与槽的连接方式 简介 信号槽是 Qt 框架引以为豪的机制之一.当用户触发某个事件时,就会发出一个信号(signal),这种发出是没有目的的,类似广播.如果有对象对这个信号感兴趣,它就会连接(connect)绑定一个函数(称为槽slot)来处理这个信号.也就是说当信号发出时,被连接的槽函数会自动被回调.这有点类似与开发模式中的观察者模式,即当发生了感兴趣的事件,某一个操作就会被自动触发 信号和槽是Qt特有的信息传输机制,是Qt设计程序的

  • Qt 信号自定义槽函数的实现

    目录 使用无参数信号与槽 使用有参信号传递 点击按钮触发信号 匿名函数与槽 Qt中实现自定义信号与槽函数,信号用于发送并触发槽函数,槽函数则是具体的功能实现,如下我们以老师学生为例子简单学习一下信号与槽函数的使用方法. 使用无参数信号与槽 首先定义一个teacher类,该类中用于发送一个信号,其次student类,定义用于接收该信号的槽函数,最后在widget中使用emit触发信号,当老师说下课时,学生请客吃饭. teacher.h 中只需要定义信号.定义一个 void hungry(); 信号

  • Qt开发实现跨窗口信号槽通信

    多窗口通信,如果是窗口类对象之间互相包含,则可以直接开放public接口调用,不过,很多情况下主窗口和子窗口之间要做到异步消息通信,就必须依赖到跨窗口的信号槽,以下是一个简单的示例. 母窗口 mainwindow.h #ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QLabel> #include <QString> class MainWindow : public

  • Qt信号与槽知识点总结归纳

    目录 一.含义与使用 (一).含义 (二).connect使用 (三).信号函数 (四).槽函数 二.示例 总结 一.含义与使用 (一).含义 信号与槽是qt中一种常用的通信手段.发送方发送信号,接收方通过槽进行接收并完成具体任务. 简单来讲,就是通过发送一个函数(信号),接收方通过另一个函数(槽)完成任务. (二).connect使用 用法上,通过connect函数进行连接. connect(发送方地址,信号函数,接收方地址,槽函数); 比如我们举如下例子: 通过点击按钮实现关闭窗口. myW

  • 一文带你了解Qt中槽的使用

    目录 一.建立槽和按钮之间的连接 二.槽函数的定义 一.建立槽和按钮之间的连接 connect(信号发送者,发送的信号,信号接收者,信号接收者的槽函数) 1.例子 connect(ui->pushButton,SIGNAL(clicked(bool)),this,SLOT(showinfo())); 解释: 信号反发送者:pushButton(这是一个按钮),发送信号:clicked(点击按钮),信号接收者:this(本类),信号接收者的槽函数:showinfo(点击按钮后响应的函数) 二.槽函

  • 详解c++实现信号槽

    目录 用c++实现信号槽机制(signal-slot) 总结 用c++实现信号槽机制(signal-slot) 信号槽机制的个人理解:信号槽是在两个c++类对象之间建立联系的通道,其中一个对象可称之为信号发送者(sender),另一个对象可称之为信号接收者(recver),sender通过信号槽发出信号后,recver就可以执行函数进行某些操作.也就是说应用程序通过信号槽可以在两个互不相关的对象之间建立起逻辑关系,使程序开发变得简洁.方便. 信号槽本质是由c++定义的类组成,分为两个部分:槽类和

  • Django框架 信号调度原理解析

    Django中提供了"信号调度",用于在框架执行操作时解耦.通俗来讲,就是一些动作发生的时候,信号允许特定的发送者去提醒一些接受者. Django内置信号 Model signals pre_init # django的modal执行其构造方法前,自动触发 post_init # django的modal执行其构造方法后,自动触发 pre_save # django的modal对象保存前,自动触发 post_save # django的modal对象保存后,自动触发 pre_delet

  • Qt实现闹钟小程序

    本文实例为大家分享了Qt之闹钟小程序的具体代码,供大家参考,具体内容如下 -首先 首先我们利用Qt的designer 设计好我们需要的闹钟界面,设计界面如下图: 其次我们来分别利用信号分别完成他们各自的槽函数 在mainwindow.h中,我们定义了下面这些私有成员变量,如下: / mainwindow.h文件/** #ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QTimeEdit&g

随机推荐