QObject

上一节中我们讲了QObject是Qt中使用Meta-Object元对象模型或者说使用信号与槽机制,必须继承的根基类,一般面向对象语言都会有这么一个根基类,提供了语言的基础,那么Qt作为C++的扩展库,QObject作为Qt的根类,为我们提供了哪些功能呢?

对象树

在Qt的构造函数中,我们可以发现都带有一个QObject* parent=0的默认参数,这个parent就是用来指定父对象

QObject::QObject(QObject *parent): d_ptr(new QObjectPrivate)
{Q_D(QObject);d_ptr->q_ptr = this;d->threadData = (parent && !parent->thread()) ? parent->d_func()->threadData : QThreadData::current();d->threadData->ref();if (parent) {QT_TRY {if (!check_parent_thread(parent, parent ? parent->d_func()->threadData : 0, d->threadData))parent = 0;setParent(parent);} QT_CATCH(...) {d->threadData->deref();QT_RETHROW;}}qt_addObject(this);if (Q_UNLIKELY(qtHookData[QHooks::AddQObject]))reinterpret_cast<QHooks::AddQObjectCallback>(qtHookData[QHooks::AddQObject])(this);
}

先看看d_ptr的定义

QScopedPointer<QObjectData> d_ptr;

它是一个QObjectData的指针,在Qt源码中一般用d_ptr表示类的数据指针。

class Q_CORE_EXPORT QObjectData {
public:virtual ~QObjectData() = 0;QObject *q_ptr;QObject *parent;QObjectList children;uint isWidget : 1;uint blockSig : 1;uint wasDeleted : 1;uint isDeletingChildren : 1;uint sendChildEvents : 1;uint receiveChildEvents : 1;uint isWindow : 1; //for QWindowuint unused : 25;int postedEvents;QDynamicMetaObjectData *metaObject;QMetaObject *dynamicMetaObject() const;
};

QObjectData 定义了一个q_ptr指针指回QObject,parent 指向父对象,children保存子对象,还定义了一些标志位,当然还有我们上节的QMetaObject 类元信息。

在构造函数有这么一句

d_ptr(new QObjectPrivate)

那么QObjectPrivate是什么,既然它能赋值给d_ptr,那应该是QObjectData的派生类,转到定义看看。

class Q_CORE_EXPORT QObjectPrivate : public QObjectData
{Q_DECLARE_PUBLIC(QObject)public:struct ExtraData{ExtraData() {}#ifndef QT_NO_USERDATAQVector<QObjectUserData *> userData;#endifQList<QByteArray> propertyNames;QVector<QVariant> propertyValues;QVector<int> runningTimers;QList<QPointer<QObject> > eventFilters;QString objectName;};//...
}

果然,它就是继承自QObjectData,然后带了些额外的数据

#define Q_DECLARE_PUBLIC(Class)                                    \inline Class* q_func() { return static_cast<Class *>(q_ptr); } \inline const Class* q_func() const { return static_cast<const Class *>(q_ptr); } \friend class Class;

Q_DECLARE_PUBLIC宏声明了友元类,并通过q_func()方法返回q_ptr,即QObject类指针

在QObject的声明中也有如下一句:

Q_DECLARE_PRIVATE(QObject)

看看Q_DECLARE_PRIVATE的定义

#define Q_DECLARE_PRIVATE(Class) \
inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(qGetPtrHelper(d_ptr)); } \
inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(qGetPtrHelper(d_ptr)); } \
friend class Class##Private;

在Qt的源码中到处可以见到这样的好基友,通过声明一个名字为ClassPrivate的友元类,用来保存私有数据,保证数据的封装性和隐秘性

继续看QObject的构造函数,又有这么一个宏Q_D,Qt还真是喜欢用宏定义,这样可以使代码简洁些

#define Q_D(Class) Class##Private * const d = d_func()
#define Q_Q(Class) Class * const q = q_func()

就是定义一个d指向私有类,定义一个q指向本身。继续

d_ptr->q_ptr = this;

d_ptr通过new QObjectPrivate,指向QObjectPrivate,那么d_ptr->q_ptr = this就是将QObjectPrivate中的q_ptr指回QObject

不要被绕晕了,其实很简单,一句话总结
QObject和QObjectPrivate互为友元类,QObject中new了一个QObjectPrivate,通过d_ptr保存QObjectPrivate的指针,而QObjectPrivate中通过q_ptr指回QObejct,真是一对好基友

继续

d->threadData = (parent && !parent->thread()) ? parent->d_func()->threadData : QThreadData::current();
d->threadData->ref();
if (parent) {
QT_TRY {if (!check_parent_thread(parent, parent ? parent->d_func()->threadData : 0, d->threadData))parent = 0;setParent(parent);
} QT_CATCH(...) {d->threadData->deref();QT_RETHROW;
}

}

线程数据我们这里先不深究,大抵意思是比较父对象和当前对象是否是同一线程中创建,如果不是是会抛出异常的。这就是说Qt要求父对象和子对象必须在同一线程中创建,这是因为父对象后面既然负责子对象的销毁工作,如果是跨线程销毁,会带来毁灭性的灾害。
setParent(parent);就是赋值QObjectData中parent指针了,转到定义看最终调用如下

void QObjectPrivate::setParent_helper(QObject *o)
{Q_Q(QObject);if (o == parent)return;if (parent) {QObjectPrivate *parentD = parent->d_func();if (parentD->isDeletingChildren && wasDeleted&& parentD->currentChildBeingDeleted == q) {// don't do anything since QObjectPrivate::deleteChildren() already// cleared our entry in parentD->children.} else {const int index = parentD->children.indexOf(q);if (parentD->isDeletingChildren) {parentD->children[index] = 0;} else {parentD->children.removeAt(index);if (sendChildEvents && parentD->receiveChildEvents) {QChildEvent e(QEvent::ChildRemoved, q);QCoreApplication::sendEvent(parent, &e);}}}}parent = o;if (parent) {// object hierarchies are constrained to a single threadif (threadData != parent->d_func()->threadData) {qWarning("QObject::setParent: Cannot set parent, new parent is in a different thread");parent = 0;return;}parent->d_func()->children.append(q);if(sendChildEvents && parent->d_func()->receiveChildEvents) {if (!isWidget) {QChildEvent e(QEvent::ChildAdded, q);QCoreApplication::sendEvent(parent, &e);}}}if (!wasDeleted && !isDeletingChildren && declarativeData && QAbstractDeclarativeData::parentChanged)QAbstractDeclarativeData::parentChanged(declarativeData, q, o);
}

一来赋值了parent,二来在parent的QObejectData中增加上该children,并且QCoreApplication::sendEvent给父对象发送了一个QChildEvent添加子类事件

继续是qt_addObject

// ### Qt >= 5.6, remove qt_add/removeObject
extern "C" Q_CORE_EXPORT void qt_addObject(QObject *)
{}extern "C" Q_CORE_EXPORT void qt_removeObject(QObject *)
{}

从定义和注释看,qt_addObject和qt_removeObject这两个函数只是一个空壳了,应该是以前版本的遗留物。

if (Q_UNLIKELY(qtHookData[QHooks::AddQObject]))reinterpret_cast<QHooks::AddQObjectCallback>(qtHookData[QHooks::AddQObject])(this);

qtHookData是Qt预留的钩子回调函数指针数组,当new一个QObject对象时,会触发QHooks::AddQObject钩子回调函数

再看看QObject的析构函数

QObject::~QObject()
{Q_D(QObject);d->wasDeleted = true;d->blockSig = 0; // unblock signals so we always emit destroyed()QtSharedPointer::ExternalRefCountData *sharedRefcount = d->sharedRefcount.load();if (sharedRefcount) {if (sharedRefcount->strongref.load() > 0) {qWarning("QObject: shared QObject was deleted directly. The program is malformed and may crash.");// but continue deleting, it's too late to stop anyway}// indicate to all QWeakPointers that this QObject has now been deletedsharedRefcount->strongref.store(0);if (!sharedRefcount->weakref.deref())delete sharedRefcount;}if (!d->isWidget && d->isSignalConnected(0)) {emit destroyed(this);}if (d->declarativeData) {if (static_cast<QAbstractDeclarativeDataImpl*>(d->declarativeData)->ownedByQml1) {if (QAbstractDeclarativeData::destroyed_qml1)QAbstractDeclarativeData::destroyed_qml1(d->declarativeData, this);} else {if (QAbstractDeclarativeData::destroyed)QAbstractDeclarativeData::destroyed(d->declarativeData, this);}}// set ref to zero to indicate that this object has been deletedif (d->currentSender != 0)d->currentSender->ref = 0;d->currentSender = 0;if (d->connectionLists || d->senders) {QMutex *signalSlotMutex = signalSlotLock(this);QMutexLocker locker(signalSlotMutex);// disconnect all receiversif (d->connectionLists) {++d->connectionLists->inUse;int connectionListsCount = d->connectionLists->count();for (int signal = -1; signal < connectionListsCount; ++signal) {QObjectPrivate::ConnectionList &connectionList =(*d->connectionLists)[signal];while (QObjectPrivate::Connection *c = connectionList.first) {if (!c->receiver) {connectionList.first = c->nextConnectionList;c->deref();continue;}QMutex *m = signalSlotLock(c->receiver);bool needToUnlock = QOrderedMutexLocker::relock(signalSlotMutex, m);if (c->receiver) {*c->prev = c->next;if (c->next) c->next->prev = c->prev;}c->receiver = 0;if (needToUnlock)m->unlock();connectionList.first = c->nextConnectionList;// The destroy operation must happen outside the lockif (c->isSlotObject) {c->isSlotObject = false;locker.unlock();c->slotObj->destroyIfLastRef();locker.relock();}c->deref();}}if (!--d->connectionLists->inUse) {delete d->connectionLists;} else {d->connectionLists->orphaned = true;}d->connectionLists = 0;}/* Disconnect all senders:* This loop basically just does*     for (node = d->senders; node; node = node->next) { ... }** We need to temporarily unlock the receiver mutex to destroy the functors or to lock the* sender's mutex. And when the mutex is released, node->next might be destroyed by another* thread. That's why we set node->prev to &node, that way, if node is destroyed, node will* be updated.*/QObjectPrivate::Connection *node = d->senders;while (node) {QObject *sender = node->sender;// Send disconnectNotify before removing the connection from sender's connection list.// This ensures any eventual destructor of sender will block on getting receiver's lock// and not finish until we release it.sender->disconnectNotify(QMetaObjectPrivate::signal(sender->metaObject(), node->signal_index));QMutex *m = signalSlotLock(sender);node->prev = &node;bool needToUnlock = QOrderedMutexLocker::relock(signalSlotMutex, m);//the node has maybe been removed while the mutex was unlocked in relock?if (!node || node->sender != sender) {// We hold the wrong mutexQ_ASSERT(needToUnlock);m->unlock();continue;}node->receiver = 0;QObjectConnectionListVector *senderLists = sender->d_func()->connectionLists;if (senderLists)senderLists->dirty = true;QtPrivate::QSlotObjectBase *slotObj = Q_NULLPTR;if (node->isSlotObject) {slotObj = node->slotObj;node->isSlotObject = false;}node = node->next;if (needToUnlock)m->unlock();if (slotObj) {if (node)node->prev = &node;locker.unlock();slotObj->destroyIfLastRef();locker.relock();}}}if (!d->children.isEmpty())d->deleteChildren();qt_removeObject(this);if (Q_UNLIKELY(qtHookData[QHooks::RemoveQObject]))reinterpret_cast<QHooks::RemoveQObjectCallback>(qtHookData[QHooks::RemoveQObject])(this);if (d->parent)        // remove it from parent objectd->setParent_helper(0);
}

比较长,关键几处有:
emit destroyed(this);发出销毁信号

  int connectionListsCount = d->connectionLists->count();for (int signal = -1; signal < connectionListsCount; ++signal) {QObjectPrivate::ConnectionList &connectionList =(*d->connectionLists)[signal];while (QObjectPrivate::Connection *c = connectionList.first) {if (!c->receiver) {connectionList.first = c->nextConnectionList;c->deref();continue;}//...}

断开所有信号与槽的连接

if (!d->children.isEmpty())d->deleteChildren();

删除子对象

if (Q_UNLIKELY(qtHookData[QHooks::RemoveQObject]))reinterpret_cast<QHooks::RemoveQObjectCallback>(qtHookData[QHooks::RemoveQObject])(this);

触发移除对象的钩子回调函数

if (d->parent)        // remove it from parent objectd->setParent_helper(0);// setParent_helper中会执行如下语句
QChildEvent e(QEvent::ChildRemoved, q);
QCoreApplication::sendEvent(parent, &e);

从父对象的子对象列表中移除该对象,此时会向父对象发出一个移除子对象的事件

总结:在Qt的构造的析构函数中,就实现了对象树。每个对象会保留父对象指针和子对象列表,构造时设置父指针,并在父对象的子对象列表中添加该对象,析构时会删除所有子对象,并从父对象的子对象列表中移除该对象。

Qt的对象树很有用,在界面构造时,我们的子控件只需要指定了父控件,那么父控件销毁时会自动销毁子控件,而不用我们操心,所以在Qt的应用程序代码中你可以发现只有new,没有delete,这就是对象树提供的方便之处,妈妈再也不用担心内存泄漏问题了

信号与槽机制

信号与槽机制的根基通过QMetaObject提供,这在上一节已经讲了,通过signals,slots等宏声明定义信号、槽。QMetaObject中会记录这些信号与槽函数,并通过索引号查找,moc会自动添加上qt_metacall的实现,通过索引调用对应的函数。

这里主要看下connect和disconnect的实现。

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 codeQArgumentTypeArray 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());if (signal_index < 0) {// check for normalized signaturestmp_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;int membcode = extract_code(method);if (!check_method_code(membcode, receiver, method, "connect"))return QMetaObject::Connection(0);const char *method_arg = method;++method; // skip codeQArgumentTypeArray 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;}if (method_index_relative < 0) {// check for normalized methodstmp_method_name = QMetaObject::normalizedSignature(method);method = tmp_method_name.constData();methodTypes.clear();methodName = QMetaObjectPrivate::decodeMethodSignature(method, methodTypes);// rmeta may have been modified abovermeta = 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_DEBUGQMetaMethod smethod = QMetaObjectPrivate::signal(smeta, signal_index);QMetaMethod rmethod = rmeta->method(method_index_relative + rmeta->methodOffset());check_and_warn_compat(smeta, smethod, rmeta, rmethod);
#endifQMetaObject::Connection handle = QMetaObject::Connection(QMetaObjectPrivate::connect(sender, signal_index, smeta, receiver, method_index_relative, rmeta ,type, types));return handle;
}

通过SIGNAL和SLOT宏传入的是函数签名字符串,所以会先调用QMetaObjectPrivate::decodeMethodSignature去解析函数签名,再调用QMetaObjectPrivate::indexOfSlotRelative去获取她们在QMetaObject中记录的索引
最终调用QMetaObjectPrivate::connect

继续跟踪

QObjectPrivate::Connection *QMetaObjectPrivate::connect(const QObject *sender,int signal_index, const QMetaObject *smeta,const QObject *receiver, int method_index,const QMetaObject *rmeta, int type, int *types)
{QObject *s = const_cast<QObject *>(sender);QObject *r = const_cast<QObject *>(receiver);int method_offset = rmeta ? rmeta->methodOffset() : 0;Q_ASSERT(!rmeta || QMetaObjectPrivate::get(rmeta)->revision >= 6);QObjectPrivate::StaticMetaCallFunction callFunction =rmeta ? rmeta->d.static_metacall : 0;QOrderedMutexLocker locker(signalSlotLock(sender),signalSlotLock(receiver));if (type & Qt::UniqueConnection) {QObjectConnectionListVector *connectionLists = QObjectPrivate::get(s)->connectionLists;if (connectionLists && connectionLists->count() > signal_index) {const QObjectPrivate::Connection *c2 =(*connectionLists)[signal_index].first;int method_index_absolute = method_index + method_offset;while (c2) {if (!c2->isSlotObject && c2->receiver == receiver && c2->method() == method_index_absolute)return 0;c2 = c2->nextConnectionList;}}type &= Qt::UniqueConnection - 1;}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());locker.unlock();QMetaMethod smethod = QMetaObjectPrivate::signal(smeta, signal_index);if (smethod.isValid())s->connectNotify(smethod);return c.take();
}

应该明白了,new 一个QObjectPrivate::Connection,里面保存了发送者,信号索引,接受者,方法索引,连接类型等信息,再终调用QObjectPrivate::get(s)->addConnection(signal_index, c.data());
当然还调用了一个连接通知函数connectNotify

void QObjectPrivate::addConnection(int signal, Connection *c)
{
Q_ASSERT(c->sender == q_ptr);
if (!connectionLists)
connectionLists = new QObjectConnectionListVector();
if (signal >= connectionLists->count())
connectionLists->resize(signal + 1);

ConnectionList &connectionList = (*connectionLists)[signal];
if (connectionList.last) {connectionList.last->nextConnectionList = c;
} else {connectionList.first = c;
}
connectionList.last = c;cleanConnectionLists();c->prev = &(QObjectPrivate::get(c->receiver)->senders);
c->next = *c->prev;
*c->prev = c;
if (c->next)c->next->prev = &c->next;if (signal < 0) {connectedSignals[0] = connectedSignals[1] = ~0;
} else if (signal < (int)sizeof(connectedSignals) * 8) {connectedSignals[signal >> 5] |= (1 << (signal & 0x1f));
}

}

真相大白了,new了一个QObjectConnectionListVector连接列表向量,ConnectionList &connectionList = (*connectionLists)[signal];通过信号索引找到连接列表,也就是说一个信号可以连接多个接收,连接列表向量中保存所有信号的连接列表。

disconnect流程和connect大抵类似:
解析函数签名->提取对应索引->调用QMetaObjectPrivate::disconnect->从连接列表向量中提取对应信号的连接列表->从连接列表中将ref连接次数-1,如果为0则移出列表->调用断连通知函数disconnectNotify

属性系统

在Meta-Object Model中我们已经讲过Qt提供了动态的添加属性的方法,在QObject中给出的接口就是:

bool setProperty(const char *name, const QVariant &value);
QVariant property(const char *name) const;
QList<QByteArray> dynamicPropertyNames() const;

事件系统

QObject中提供了如下几个事件处理接口:

void installEventFilter(QObject *filterObj);
void removeEventFilter(QObject *obj);
virtual bool event(QEvent *event);
virtual bool eventFilter(QObject *watched, QEvent *event);
bool QObject::event(QEvent *e)
{switch (e->type()) {case QEvent::Timer:timerEvent((QTimerEvent*)e);break;case QEvent::ChildAdded:case QEvent::ChildPolished:case QEvent::ChildRemoved:childEvent((QChildEvent*)e);break;case QEvent::DeferredDelete:qDeleteInEventHandler(this);break;case QEvent::MetaCall:{QMetaCallEvent *mce = static_cast<QMetaCallEvent*>(e);QConnectionSenderSwitcher sw(this, const_cast<QObject*>(mce->sender()), mce->signalId());mce->placeMetaCall(this);break;}case QEvent::ThreadChange: {Q_D(QObject);QThreadData *threadData = d->threadData;QAbstractEventDispatcher *eventDispatcher = threadData->eventDispatcher.load();if (eventDispatcher) {QList<QAbstractEventDispatcher::TimerInfo> timers = eventDispatcher->registeredTimers(this);if (!timers.isEmpty()) {// do not to release our timer ids back to the pool (since the timer ids are moving to a new thread).eventDispatcher->unregisterTimers(this);QMetaObject::invokeMethod(this, "_q_reregisterTimers", Qt::QueuedConnection,Q_ARG(void*, (new QList<QAbstractEventDispatcher::TimerInfo>(timers))));}}break;}default:if (e->type() >= QEvent::User) {customEvent(e);break;}return false;}return true;
}

event中进行了一些事件的类别判断,然后分发到各个细分函数进行处理,像timerEvent,childEvent等,当然支持自定义事件 QEvent::User+,分发到customEvent中处理。

用户自定义数据

QObject提供了添加自定义数据的接口

static uint registerUserData();
void setUserData(uint id, QObjectUserData* data);
QObjectUserData* userData(uint id) const;

QObjectUserData是一个空类,我们继承这个类,通过registerUserData注册一个用户数据,然后setUserData设置用户数据即可,通过userData就可以提取这个用户数据了

总结

QObject提供了对象树,信号与槽机制,属性系统,事件系统,支持添加自定义数据。

Qt--QObject相关推荐

  1. Qt QObject::connect: Parentheses expected错误原因

    Qt 运行提示 QObject::connect: Parentheses expected, signal QUdpSocket::readyRead in -\terminal\net103.cp ...

  2. QT:QObject 简单介绍

    QObject 是所有Qt对象的基类. QObject 是Qt模块的核心.它的最主要特征是关于对象间无缝通信的机制:信号与槽. 使用connect()建立信号到槽的连接,使用disconnect()销 ...

  3. 关于Qt QObject tr(translate)失败的问题

    编码过程中遇到的一个小问题,卡了一会,总结下: 起因:界面需要一个动态字符串,翻译时失败了. 原因:测试了十来种转换方式都失败了,从原理出发,就解决了.原理本质就是二进制数据的查表,确定ts.qm文件 ...

  4. 槽函数获取sender_Qt QObject::sender()用法

    QObject::sender()说明 在槽函数里,使用 QObject::sender()可以获取信号发射者指针,如果知道信号发射者的类型,可以将指针投射为确定的类型,然后使用这个确认类的接口函数. ...

  5. 【pyqt5】——信号与槽

    一.简单Demo 简单使用信号和槽(之前常用的使用方式): class DemoWin(QMainWindow):def __init__(self):super().__init__()self.i ...

  6. 基于PyQt的扫雷游戏实现_下篇

    接上篇文章(http://www.chenjianqu.com/show-34.html)继续. 上一篇文章已经给出了主菜单的实现,以及操作数字数组的Brick类.在介绍游戏界面的实现之前,先自定义B ...

  7. 使用QHttp与C#编写的服务端交互(编译环境mingw)

    打开qtcreator,新建一个项目,然后加一个头文件及源代码文件,如下: QtHttp.h: #ifndef QTHTTP_H #define QTHTTP_H #include <Qt/QO ...

  8. PyQt5-多窗口数据传输

    #窗口之间数据传递(通过属性方式) from PyQt5.QtWidgets import QDialogButtonBox, QDateTimeEdit,QDialog,QComboBox,QTab ...

  9. 【Qt中文手册】QObject

    Qt几乎所有的类都是从QObject直接或间接继承的,但是你真的了解QObject吗?下面先看看QObject在官方手册中的介绍. 一.QObject简介 1.信号和槽 QObject是所有Qt类的基 ...

  10. 【Qt】Qt样式表总结(三):QObject 属性

    [Qt]Qt样式表总结(一):选择器 [Qt]Qt样式表总结(二):冲突和命名空间 QObject 属性 可以使用 qproperty < 属性名称 > 语法,设置任何可以Q_PROPER ...

最新文章

  1. appium java 测试用例_如何在C#中使用Appium编写测试用例?
  2. linux 硬软链接区别
  3. Gnuplot的安装和基本使用方法
  4. [BZOJ2725/Violet 6]故乡的梦
  5. 机器学习笔记2 – sklearn之iris数据集
  6. 某集团BI决策系统建设方案分享
  7. JAVA Metrics 度量工具使用介绍1
  8. 梯度消失的有效解决方法-batch normalization
  9. php随机获取数组的值
  10. 文件下载-解决IOS自带浏览器下载乱码的问题
  11. 贪吃蛇c语言程序 简书,贪吃蛇游戏(scratch编程)
  12. 突发!ITELLYOU要改版了!
  13. 销售书籍推荐,这本书做销售的必看!
  14. OPENGL回归原点
  15. 《集成电路先进光刻技术与版图设计优化》课程分享之二:浸没式光刻工艺缺陷种类、特征及自识别方法
  16. Mysql-计算两个时间之间的差值
  17. 《一条狗的使命》观后感
  18. 大一统视角理解扩散模型
  19. 数据代码分享|Python用NLP自然语言处理LSTM神经网络Twitter推特灾难文本数据、词云可视化与SVM,KNN,多层感知器,朴素贝叶斯,随机森林,GBDT对比
  20. 使用mindspore的ResNet101使用GPU进行训练时报错

热门文章

  1. SwiftUI 中为什么应该经常用子视图替换父视图中的大段内容?
  2. 检测样本分布是不是正态分布,绘制其正态分布概率图及异常值检测-python代码实现
  3. 软件测试工程师该如何规划自己的职业发展道路?
  4. C语言连接MySQL数据库实例
  5. 微信云服务器发长视频朋友圈,今天才知道,微信朋友圈还能发5分钟长视频,超简单,一看就会...
  6. 使用Qt开发中国象棋(三):走棋着法列表
  7. 中英文标点符号的读法用法大全
  8. 无线洗地机哪款性价比高?高性价比的洗地机分享
  9. DSSD(Deconvolutional Single Shot Detector)
  10. 史上最详细唇语识别数据集综述