"In fact, the socialization (connection) gives us the tools to fill our evolutionary roles. They are our building blocks."
Warren Farrell
Yes, socialization is so important to us humans who are all characterized as gregarious animals million years ago. Something created by humans could not be an exception as well (... well, there are still plenty of exceptions though having said that). In fact, the socialization system of Qt among classes under QObject
, canonically known as the signal-slot(s) connection, is worth explaining it using two or three five-thousand-word pieces of prose.
Why? Because it's the heart of various Qt objects - it is the fact the most efficient, standardized, well-known and (probably) the only way to communicate between objects (including widgets and a whole bunch more that will be introduced later).
Let us dive - hopefully as deep as possible - into the well-known signal-slot system, and the controller behind: the meta-object system.
(In fact, I have already mentioned two or three times in my previous articles. They're too important that even a hello-world example for fresh novices requires so.)
The Heart's Heart of Heart
If you peek at the official repositories of (open-source) Qt, you will soon discovered that the one with the most traffic, stars and popularity by looking at the number of forks (provided that you are using GitHub), and the most important one by inferring the name, is Qt Base. Of the ten or twenty sub-components of the Qt Base repository, by just inferring their names, you will figure out that Qt Core is the most important one. In Qt Core, the signal-slot system is the most important one.
That is why it is called the heart's heart of heart.
Qt could somehow be considered as a library based on the signal-slot system, or more accurately, the meta-object system. The meta-object system, in plain English, is a mechanism used to build independent software components (you will soon get to know what I mean). These components need not to recognize (identify) each other at runtime, even in order to run and collaborate with one another seamlessly. After this article and probably two or three more, you will soon discover that the meta-object system extends the C++ language per se and beautifies it, given that you follow my steps and learn Qt patiently for a while.
Why? Because the meta-object system provides 3 key features:
- Signal-slot system (it's way too important that it sometimes be the pronoun of the meta-object system per se though it's just a part of it; we'll be using the term "meta-object system" consistently from now on in this series. Sorry for the previous incongruousness),
- Dynamic runtime-data-type detection (do you believe it, It's C-plus-plus plus Qt, not Python nor Ruby! How mighty is Qt), and
- Dynamic property system.
Everything in Qt is possible if and only if these 3 key features is possible and got implemented.
In this article, we'll talk about the first one first. In the next or two, I'll be cover the rest of them.
The meta-object system is possible when
- The class inherits
QObject
or any of its subclass, i.e. the class that would like to enjoy the meta-object system must be, either direct or indirect, descendant ofQObject
. This is because the meta-object system is implemented byQObject
andQMetaObject
(you'll get to know it later in this article). - As the meta-object system requires some private class members, so every
QObject
descendant needs to get aQ_OBJECT
macro declared in theprivate:
section of the class (owing the fact that C++ defaults members of undeclared accessibility as private members, so you could also put the macro as the first declaration of the class, which is consistent with the Qt programming style, as well as mine). Putting it in thepublic:
section is an undefined behavior (at least I have never tried it). If you do not do so, errors regarding undefined members will be raised if you try to define a signal, emit a signal (see later) in or with this class (internally and externally respectively); similarly, if you do so in a non-QObject
-descendant class, you'll still get a whole bunch of errors as the declaration calls someQObject
methods. - You must have a meta-object compiler made by Qt. It's somehow like the preprocessor compiler mandated by the C++ Standard and provided by almost all C/C++ compilers. However, instead of dealing with the hash signs
#
, it deals with the Q_OBJECT declarations, implementing the dynamic properties, etc. Unless you accidentally delete it (I won't believe there is such a careless person in the world), this condition is the easiest to fulfill by the time you finished the Qt installation.
The above content looks so dry that I think we need a comprehensive and self-explanatory example to make it practical.
Flowers
This time, we only build a console project. To build a console project (i.e. the project that you do not use a window or something, just use the core features of Qt), you will follow the following steps:
- Launch Qt Creator (of course!)
- Click
File -> New Project
as always - Then, here's the different part: after the dialog is being launched, do manually click the "Qt Console Application" instead of the default one, "Qt Widget Application".
- Follow the steps in the "Hello, Qt!" article. Name your project a reasonable title, and for this time, I opt for "Flowers" instead of any other strange things.
If you're curious, you could glimpse at the newly created
CMakeLists.txt
file, and it differs from the previous files at: allWidget
entries are replaced by the wordCore
. That indicates that using widgets is invalid and may results in a preprocessing error or a linkage error (for C++ intermediate learners, just think of some errors irrelevant to your code). But since we're using the meta-object system today, using the cores is already enough.
Using the <QtCore>
module is indeed already enough, but only having the main.cpp
file is indeed not enough. To set things up, we need to create four additional files, namely flower.h
, flower.cpp
, picker.h
and picker.cpp
. We could just create them by pressing the hotkey (Ctrl+N
) or navigating to the action File -> New File
(I've already taught it before in somewhere, haven't I), then choose the option "C++ Class".
Enter the name of the class. After entering the name, you could soon realize that the "Header file:" and "Source file:" fields are already filled of their required relative directories automatically by Qt Creator. How helpful it is!
As the two classes, namely Flower
and Picker
need to inherit QObject
, select the option "QObject" in the combo-box labeled as "Base class:". Qt Creator is so helpful that it automatically helps you in selecting the often-omitted options: #include <QObject>
and add the macro directive Q_OBJECT
. Without these two, our code cannot function. But it's the two cute (?) creatures that we often forget.
Anyways, let's press "Next" and your final dialogs should look like these ones:
Okay, let's do some coding (finally!)
The code is pretty standardized, so I will just pick some important spots to explain. The other parts are often self-explanatory. (I am trying my best to reduce your reading stress in this article as this one should be the longest one throughout the whole series; but forgive me, because the meta-object system is literally the behemoth in Qt and explaining it takes two or three longest articles like this one.)
Full code
Let me show you my code first. I'll then explain it.
-
flower.h
:
#ifndef FLOWER_H
#define FLOWER_H
#include <QObject>
#include <QTimer>
class Flower : public QObject
{
Q_OBJECT
public:
explicit Flower(int secToBloom, int bloomToWither, QObject *parent = nullptr);
signals:
void bloomSignal();
void witherSignal();
protected slots:
void onFlowerBloom();
private:
QTimer *m_timer;
int m_secToBloom;
int m_bloomToWither;
};
#endif // FLOWER_H
-
flower.cpp
:
#include "flower.h"
Flower::Flower(int secToBloom, int bloomToWither, QObject *parent)
: QObject(parent),
m_timer(new QTimer(this)),
m_secToBloom(secToBloom * 1'000),
m_bloomToWither(bloomToWither * 1'000)
{
m_timer->setSingleShot(true);
connect(m_timer,
&QTimer::timeout,
this,
&Flower::onFlowerBloom);
m_timer->start(m_secToBloom);
}
void Flower::onFlowerBloom()
{
emit bloomSignal();
disconnect(m_timer,
&QTimer::timeout,
this,
&Flower::onFlowerBloom);
connect(m_timer,
&QTimer::timeout,
this,
&Flower::witherSignal);
m_timer->start(m_bloomToWither);
}
-
picker.h
:
#ifndef PICKER_H
#define PICKER_H
#include <QObject>
class Picker : public QObject
{
Q_OBJECT
public:
explicit Picker(QString name, QObject *parent = nullptr);
public slots:
void onFlowerBloom();
void onFlowerWither();
private:
QString m_name;
};
#endif // PICKER_H
-
picker.cpp
:
#include "picker.h"
#include <QDebug>
#include <QCoreApplication>
Picker::Picker(QString name, QObject *parent)
: QObject(parent),
m_name(name)
{}
void Picker::onFlowerBloom()
{
qDebug() << "I," << m_name << ", picked a flower when it bloomed!";
}
void Picker::onFlowerWither()
{
qDebug() << "[Sobbing] I," << m_name << ", witnessed the death of a beautiful flower. How sad!";
QCoreApplication::exit(0);
}
From the above example, we could conclude that the basic syntax for connecting the "sender" (signal) to the "receiver" (slot) is
QObject::connect(objectSender, signalPointer, objectReceiver, slotPointer);
If you're connecting inside a QObject
subclass, you may omit the member/ scope indicator QObject::
.
From the above example, we also explored the three different keywords introduced by Qt: signal
, slot
and emit
. The signal
keyword introduces a list of signals in the class member declaration lists until the next access specifier meets, so does the slot:
keyword. The emit
keyword, followed by a signal, emits that signal.
We could see that we could connect a same signal to multiple slots (as in the Flower::bloomSignal
) too.
We also explored the usage of a pretty handy class QTimer
. We'll look into it later, but you just need to remember that QTimer::start
starts a timer by milliseconds (that's why the private properties Flower::m_secToBloom
and Flower::m_bloomToWither
needs to be multiplied by 1000 programmatically in the fields-initialization list), while a timer emits the QTimer::timeout
signal after the time is out. The QTimer::singleShot
method specifies that the timeout
signal will be emitted once only, instead of an interval of the number specified in the parameter of QTimer::start
.
Also, as a good practice, unless the QObject
subclass will not be referred by any other functions (i.e. passed in as a parameter), you should really use the pointer forms of the QObject
's instead of the variable forms.
Additionally, you may be using some libraries such as boost
, which have classes or identifiers called signals
, slots
or emit
, i.e. the Qt identifiers clash with them. In this case, fire your CMakeLists.txt
up and add the following definition:
add_definitions(-DQT_NO_KEYWORDS)
After that, you will discover that you are not able to use signals
, slots
and emit
keywords any more. Instead, you must use Q_SIGNALS
, Q_SLOTS
and Q_EMIT
respectively.
If these macro names still clash, well, then, the 3-rd party library designer, you're a son-of-a-b***h, go refactor your code...
Why?
As mentioned, these 6 names are all macros (7 if you count Q_OBJECT
; in fact, there are hundred or more macros defined by Qt).
If you go on qobjectdefs.h
in the Qt Base repository, you'll find the following code:
#define signals public __attribute__((annotate("qt_signal")))
#define slots __attribute__((annotate("qt_slot")))
Q_SIGNAL
andQ_SLOTS
are actually the same.
A signal
is basically a waiting-to-be-handled public
object, and the annotation will probably be ignored by the compiler (because it has been tagged __attribute__
, see here), so does a slot
.
As signals
is already been defined as public
while slots
is not, that is the reason why the syntax public signals:
is illegal while public slots:
is legal. This also explains that in, say, private:signals: a();
, a
could still be accessed outside of the class.
Same as emit
(or Q_EMIT
), if you go to qmetamacros.h
, you will soon discovered that
#define emit
So, emit signal();
is just signal();
. Actually, when you defined the signal, the Meta-object compiler (moc
or moc.exe
if you're on Windows) compiles the signal into a function. That's why you could also connect a signal to another signal (connect QTimer::timeout
to Flower::witherSignal
above, for example).
Actually, there are also two macros you haven't been introduced yet. They are
SIGNALS
andSLOTS
, where you'll find them on decade-old code. They're basically converting the name to a string of a name that is recognized by themoc
. Precisely, inmoc
,"1"
means a slot and"2"
means a signal. If you, say, do the codeSIGNAL(clicked())
andSLOT(onClicked())
, they output"2clicked()"
and"1onClicked()"
respectively. This is why Qt Creator warns you if you have a space between the arguments as these macros require strict formatting and strict syntax.
We seldom use these directives now, and we better use the function pointer to pass to theconnect
function. They're outdated, obsolete and will not be used unless we need to deal with an antique, like code written fifteen years ago where Qt 5 was still in beta-testing. Until the challenges and big projects in this series later on, we'll not be using any of them.Do notice that mix-and-match is not allowed, i.e. a function pointer connecting to a
SLOT
macro or vice versa.
Meta-object Compiler is a True Magician
The Q_OBJECT
macro defines some key functions of performing the meta-object mechanism:
#define Q_OBJECT \
public: \
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 \
QT_DEFINE_TAG_STRUCT(QPrivateSignal); \
QT_ANNOTATE_CLASS(qt_qobject, "")
It basically:
- Defines a static meta-object
QMetaObject staticMetaObject
, - Overrides the virtual method
QObject::metaObject
, - Overrides the virtual method
QObject::qt_metacast
, - Does some internationalization support (we'll be discussing that later),
- Overrides the virtual method
QObject::qt_metacall
, and - Defines the static function
qt_static_metacall
.
For your level, you need not care what are and how do these methods work. You just need to know them they're important and indispensable so as to do some signaling works.
In C++, every declared functions that are called at least once must have a definition somewhere, otherwise you'll have the linkage error by the linker, saying the Undefined reference at
somewhere (I'm pretty sure that you all have been tortured by it, haven't you). The moc
compiler, in simple English, is generating the definitions of the above functions (as well as the initialization of the objects).
The files are usually named identical as the host source file with a prefix moc_
over it, for example the Flower
class in flower.cpp
have its meta-object definitions in moc_flower.cpp
.
Let's take a closer look on the moc_flower.cpp
(for moc_picker.cpp
, the overall structure is about the same):
/****************************************************************************
** Meta object code from reading C++ file 'flower.h'
**
** Created by: The Qt Meta Object Compiler version 68 (Qt 6.8.1)
**
** WARNING! All changes made in this file will be lost!
*****************************************************************************/
#include "../../../../flower.h"
#include <QtCore/qmetatype.h>
#include <QtCore/qtmochelpers.h>
#include <memory>
#include <QtCore/qxptype_traits.h>
#if !defined(Q_MOC_OUTPUT_REVISION)
#error "The header file 'flower.h' doesn't include <QObject>."
#elif Q_MOC_OUTPUT_REVISION != 68
#error "This file was generated using the moc from 6.8.1. It"
#error "cannot be used with the include files from this version of Qt."
#error "(The moc has changed too much.)"
#endif
#ifndef Q_CONSTINIT
#define Q_CONSTINIT
#endif
QT_WARNING_PUSH
QT_WARNING_DISABLE_DEPRECATED
QT_WARNING_DISABLE_GCC("-Wuseless-cast")
namespace {
struct qt_meta_tag_ZN6FlowerE_t {};
} // unnamed namespace
#ifdef QT_MOC_HAS_STRINGDATA
static constexpr auto qt_meta_stringdata_ZN6FlowerE = QtMocHelpers::stringData(
"Flower",
"bloomSignal",
"",
"witherSignal",
"onFlowerBloom"
);
#else // !QT_MOC_HAS_STRINGDATA
#error "qtmochelpers.h not found or too old."
#endif // !QT_MOC_HAS_STRINGDATA
Q_CONSTINIT static const uint qt_meta_data_ZN6FlowerE[] = {
// content:
12, // revision
0, // classname
0, 0, // classinfo
3, 14, // methods
0, 0, // properties
0, 0, // enums/sets
0, 0, // constructors
0, // flags
2, // signalCount
// signals: name, argc, parameters, tag, flags, initial metatype offsets
1, 0, 32, 2, 0x06, 1 /* Public */,
3, 0, 33, 2, 0x06, 2 /* Public */,
// slots: name, argc, parameters, tag, flags, initial metatype offsets
4, 0, 34, 2, 0x09, 3 /* Protected */,
// signals: parameters
QMetaType::Void,
QMetaType::Void,
// slots: parameters
QMetaType::Void,
0 // eod
};
Q_CONSTINIT const QMetaObject Flower::staticMetaObject = { {
QMetaObject::SuperData::link<QObject::staticMetaObject>(),
qt_meta_stringdata_ZN6FlowerE.offsetsAndSizes,
qt_meta_data_ZN6FlowerE,
qt_static_metacall,
nullptr,
qt_incomplete_metaTypeArray<qt_meta_tag_ZN6FlowerE_t,
// Q_OBJECT / Q_GADGET
QtPrivate::TypeAndForceComplete<Flower, std::true_type>,
// method 'bloomSignal'
QtPrivate::TypeAndForceComplete<void, std::false_type>,
// method 'witherSignal'
QtPrivate::TypeAndForceComplete<void, std::false_type>,
// method 'onFlowerBloom'
QtPrivate::TypeAndForceComplete<void, std::false_type>
>,
nullptr
} };
void Flower::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
auto *_t = static_cast<Flower *>(_o);
if (_c == QMetaObject::InvokeMetaMethod) {
switch (_id) {
case 0: _t->bloomSignal(); break;
case 1: _t->witherSignal(); break;
case 2: _t->onFlowerBloom(); break;
default: ;
}
}
if (_c == QMetaObject::IndexOfMethod) {
int *result = reinterpret_cast<int *>(_a[0]);
{
using _q_method_type = void (Flower::*)();
if (_q_method_type _q_method = &Flower::bloomSignal; *reinterpret_cast<_q_method_type *>(_a[1]) == _q_method) {
*result = 0;
return;
}
}
{
using _q_method_type = void (Flower::*)();
if (_q_method_type _q_method = &Flower::witherSignal; *reinterpret_cast<_q_method_type *>(_a[1]) == _q_method) {
*result = 1;
return;
}
}
}
}
const QMetaObject *Flower::metaObject() const
{
return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
}
void *Flower::qt_metacast(const char *_clname)
{
if (!_clname) return nullptr;
if (!strcmp(_clname, qt_meta_stringdata_ZN6FlowerE.stringdata0))
return static_cast<void*>(this);
return QObject::qt_metacast(_clname);
}
int Flower::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 < 3)
qt_static_metacall(this, _c, _id, _a);
_id -= 3;
}
if (_c == QMetaObject::RegisterMethodArgumentMetaType) {
if (_id < 3)
*reinterpret_cast<QMetaType *>(_a[0]) = QMetaType();
_id -= 3;
}
return _id;
}
// SIGNAL 0
void Flower::bloomSignal()
{
QMetaObject::activate(this, &staticMetaObject, 0, nullptr);
}
// SIGNAL 1
void Flower::witherSignal()
{
QMetaObject::activate(this, &staticMetaObject, 1, nullptr);
}
QT_WARNING_POP
After analyzing the source code, the moc
compiler actually:
- Scans the headers, and analyzes the names and the corresponding information (like slots) according to the
signals:
andslots:
specifiers (actually by looking at the attributes"qt_signals"
and"qt_slots"
, etc.) and stores them into a global variableqt_meta_stringdata_
succeeding the class name. The strange numbers are actually internal references used by C++ storing the data of overrides, overloads and templates (and that's why we need to haveextern "C"
when communicating with C code, as the nomenclature is different in nature; anyway, back to the topic), - Annotates the methods, signals, slots, properties, enumerators, etc. and stores them into the global array
qt_meta_data_
succeeding by an internal class name, - Implements the declarations in
Q_OBJECT
. It calls someQObject
functions, and that's why I have mentioned that non-QObject
-descendants will have some errors in using this macro. It then initializes and puts all sort of behaviors inside thestaticMetaObject
instance, and links all implemented methods to thestaticMetaObject
instance. - Makes templates and implementations for the signals
Flower::bloomSignal
andFlower::witherSignal()
.
The signals, as in step 4, are actually regular functions:
void Flower::bloomSignal()
{
QMetaObject::activate(this, &staticMetaObject, 0, nullptr);
}
What does the QMetaObject::activate
do?
Connecting
QMetaObject::activate
is an internal method in QMetaObject
. In QMetaObject
in the file qobjectdefs.h
, it defines that
// internal index-based signal activation
static void activate(QObject *sender, int signal_index, void **argv);
static void activate(QObject *sender, const QMetaObject *, int local_signal_index, void **argv);
static void activate(QObject *sender, int signal_offset, int local_signal_index, void **argv);
Looking deeper into the source of QMetaObject
, you will soon discover that the QMetaObject
is just a struct with many anonymous attributes, like:
struct Q_CORE_EXPORT QMetaObject
{
// E L L I P S E S
struct Data { // private data
SuperData superdata;
const uint *stringdata;
const uint *data;
typedef void (*StaticMetacallFunction)(QObject *, QMetaObject::Call, int, void **);
StaticMetacallFunction static_metacall;
const SuperData *relatedMetaObjects;
const QtPrivate::QMetaTypeInterface *const *metaTypes;
void *extradata; //reserved for future use
} d;
// E L L I P S E S
};
What you need to care about is that in moc_flower.cpp
, the variables and instances with the prefix qt_meta_data_
members are actually transferred to the QMetaObject::d
members. For example, qt_meta_stringdata_ZN6FlowerE
is actually given to QMetaObject::d::stringdata
; qt_meta_data_ZN6FlowerE
is actually given to QMetaObject::d::data
; qt_static_metacall
is actually given to QMetaObject::d::static_metacall
, etc.
The QMetaObject::activate
, as well as the qt_static_metacall
functions, are all functions calling a signal/ slot/ meta-method by an index. For example, in QMetaObject::activate
, after analyzing the source code, there are actually several steps in invoking the signals:
- Testing if the signals are connected, the method returns if not,
- Testing the declarative data,
- Testing any signal interference,
- Getting the connectors,
- Calling all the slots involved
- Calling the additional signal interference.
These might seem a bit abstract, and for your level, you just need to know that it calls QMetaObject::metacall
. The latter one tests the instance of the receiver, and if it is valid, it calls its metaCall()
method; otherwise, it calls the qt_metacall()
method defined in the instance of the receiver. As long as the receiver also inherits QObject
, qt_metacall
is guaranteed to exist.
How do I know the indices, in order to call the activate()
, metaCall()
or qt_metacall()
methods?
Simple. First, the moc
will just simply scan the number of signals the heir has, and it hands over to the internal class Connection
, which represents a signal connection.
There are two important members in Connection
, namely Connection::method_relative
and Connection::method_offset
. After calling the QObject::connect
connection method, it hands over the signaling data to the QMetaObject
class.
You do not need to know how they work; they're to be hidden and remain internal. But you just need to know that they in QMetaObject
, it matches the relative index of a method (and assigns some index to a newly met method). When invoking the signals, another method methodMatch
is encountered and it matches the corresponding method, like the overloading part, overriding part and any other parts.
The required data include data
, static_metacall
in QMetaObject::d
, which are all defined in moc_flower.cpp
.
This leads us to today's final topic:
QMetaObject
I know you're scratching your head till your brain could consume all the information we have discussed. Fortunately, today's key points could be reviewed unlimited times as long as dev.to
is free. So, please allow me to introduce a hidden struct we've mentioned numerous times yet not discussed: QMetaObject
.
Actually, it's simpler than the magics of moc
. It's way simpler. To understand QMetaObject
, think of staticMetaObject
. It stores all the meta information (i.e. information related to the class per se, in plain English, like the class name, signals names and indices, slots names and indices, parent classes, properties, etc.), and it is these pieces of required information make signal-slot connection possible.
Here's some key methods in QMetaObject
:
-
className
returns the name of a class, -
superClass
returns theQMetaObject
of parent class of the host class. - We have other name-explanatory methods like
method
,methodCount
,enumerator
,enumeratorCount
,constructor
,constructorCount
etc. They all return some meta classes likeQMetaMethod
orQMetaEnum
, etc., which have similar functions as aQMetaObject
. Note that accessing the methods, constructors, etc. requires the index, which is described above.
For Python lovers, here's a close analogy:
>>> import inspect
>>> # It's just literally the same
>>> import pprint; pprint.pprint(dir(inspect))
['AGEN_CLOSED',
'AGEN_CREATED',
'AGEN_RUNNING',
'AGEN_SUSPENDED',
'ArgInfo',
'Arguments',
'Attribute',
'BlockFinder',
'BoundArguments',
'BufferFlags',
'CORO_CLOSED',
'CORO_CREATED',
'CORO_RUNNING',
'CORO_SUSPENDED',
'CO_ASYNC_GENERATOR',
'CO_COROUTINE',
'CO_GENERATOR',
'CO_ITERABLE_COROUTINE',
'CO_NESTED',
'CO_NEWLOCALS',
'CO_NOFREE',
'CO_OPTIMIZED',
'CO_VARARGS',
'CO_VARKEYWORDS',
'ClassFoundException',
'ClosureVars',
'EndOfBlock',
'FrameInfo',
'FullArgSpec',
'GEN_CLOSED',
'GEN_CREATED',
'GEN_RUNNING',
'GEN_SUSPENDED',
'OrderedDict',
'Parameter',
'Signature',
'TPFLAGS_IS_ABSTRACT',
'Traceback',
'_ClassFinder',
'_FrameInfo',
'_KEYWORD_ONLY',
'_NonUserDefinedCallables',
'_POSITIONAL_ONLY',
'_POSITIONAL_OR_KEYWORD',
'_ParameterKind',
'_Traceback',
'_VAR_KEYWORD',
'_VAR_POSITIONAL',
'__all__',
'__author__',
'__builtins__',
'__cached__',
'__doc__',
'__file__',
'__loader__',
'__name__',
'__package__',
'__spec__',
'_check_class',
'_check_instance',
'_descriptor_get',
'_empty',
'_filesbymodname',
'_findclass',
'_finddoc',
'_get_code_position',
'_get_code_position_from_tb',
'_get_dunder_dict_of_class',
'_getmembers',
'_has_code_flag',
'_has_coroutine_mark',
'_is_coroutine_mark',
'_main',
'_missing_arguments',
'_sentinel',
'_shadowed_dict',
'_shadowed_dict_from_weakref_mro_tuple',
'_signature_bound_method',
'_signature_from_builtin',
'_signature_from_callable',
'_signature_from_function',
'_signature_fromstr',
'_signature_get_partial',
'_signature_get_user_defined_method',
'_signature_is_builtin',
'_signature_is_functionlike',
'_signature_strip_non_python_syntax',
'_static_getmro',
'_too_many',
'_void',
'abc',
'ast',
'attrgetter',
'builtins',
'classify_class_attrs',
'cleandoc',
'collections',
'currentframe',
'dis',
'enum',
'findsource',
'formatannotation',
'formatannotationrelativeto',
'formatargvalues',
'functools',
'get_annotations',
'getabsfile',
'getargs',
'getargvalues',
'getasyncgenlocals',
'getasyncgenstate',
'getattr_static',
'getblock',
'getcallargs',
'getclasstree',
'getclosurevars',
'getcomments',
'getcoroutinelocals',
'getcoroutinestate',
'getdoc',
'getfile',
'getframeinfo',
'getfullargspec',
'getgeneratorlocals',
'getgeneratorstate',
'getinnerframes',
'getlineno',
'getmembers',
'getmembers_static',
'getmodule',
'getmodulename',
'getmro',
'getouterframes',
'getsource',
'getsourcefile',
'getsourcelines',
'importlib',
'indentsize',
'isabstract',
'isasyncgen',
'isasyncgenfunction',
'isawaitable',
'isbuiltin',
'isclass',
'iscode',
'iscoroutine',
'iscoroutinefunction',
'isdatadescriptor',
'isframe',
'isfunction',
'isgenerator',
'isgeneratorfunction',
'isgetsetdescriptor',
'iskeyword',
'ismemberdescriptor',
'ismethod',
'ismethoddescriptor',
'ismethodwrapper',
'ismodule',
'isroutine',
'istraceback',
'itertools',
'linecache',
'make_weakref',
'markcoroutinefunction',
'modulesbyfile',
'namedtuple',
'os',
're',
'signature',
'stack',
'sys',
'token',
'tokenize',
'trace',
'types',
'unwrap',
'walktree']
... Stayed tuned for the next article. We'll still be discussing meta-objects, signals-and-slots, ...
(I know you're not quite anticipating compared to the previous article regarding how you expect the signal-slot system looks like, particularly after seeing these complex and perplexed mechanisms... But, my friend, please believe me that this is the most important feature in Qt in my opinion, and I've devoted time to elucidating it.)
Top comments (0)