我们使用QT的UI控件时,常用到触发控件的操作。
比如点击一个按钮,就进行一个什么操作。ui文件可以右击控件–转到槽来生成一个槽函数,很方便。
选择槽函数后,头文件里会多一个槽函数
private slots:
void on_pushButton_clicked();
这是QT的 QObject函数的信号与槽功能,使得控件的点击为一个信号,点击后触发槽函数进行操作。
我们自己也可以在QObject类里写上Q_OBJECT,signals和slots来实现信号槽。
QObject::connect(u,SIGNAL(started()),uObj,SLOT(dosthing()));
元对象编译器
MOC, the Meta Object Compiler。
Qt程序在交由标准编译器(例如MSVC)编译之前,先使用moc分析cpp头文件;如果它发现在一个头文件中包含了Q_OBJECT宏,则会生成另外一个cpp源文件(moc_文件名.cpp),该cpp源文件中包含了Q_OBJECT宏的实现、运行时信息(反射)等。因此Qt程序的完整编译过程为moc->预处理->编译->链接
它怎么实现的呢?
QT的QObject类型有signals
slots
Q_OBJECT
emit
SIGNAL
SLOT等宏定义。
定义相关信号槽的实现。
CTRL键+鼠标点击这些宏,进入qobjecdefs.h文件,这里定义了这些宏。
信号
当按钮(对象)改变状态时,信号就emit,对象只负责发送,不负责接收方的处理。
槽
接收到信号,不关心信号。
这个是观察者模式。
信号与槽如何连接:
(connect函数)
ctrl按鼠标进入qobject.h文件
看到 在QObject里定义connect()函数
static QMetaObject::Connection connect(const QObject *sender, const char *signal,
const QObject *receiver, const char *member, Qt::ConnectionType = Qt::AutoConnection);
static QMetaObject::Connection connect(const QObject *sender, const QMetaMethod &signal,
const QObject *receiver, const QMetaMethod &method,
Qt::ConnectionType type = Qt::AutoConnection);
inline QMetaObject::Connection connect(const QObject *sender, const char *signal,
const char *member, Qt::ConnectionType type = Qt::AutoConnection) const;
原来QT是通过QObject::connect静态函数建立连接;其中sender与receiver是指向对象的指针,分别代表了被观察者和 观察者。
signal与method分别通过SIGNAL()与SLOT()宏来进行转换 (类型是const char*)
思路:
- 定义两个类 sender(被观察者)和recver(观察者)
- recver(观察者),定义对某个处理函数比如按钮触发后处理函数。槽函数
- sender(被观察者),定义一个数据结构,保持观察者对哪个事件id感兴趣,使用map建立对应关系。
- sender(被观察者) 有两个方法
1 添加观察者和感兴趣的事件id到容器map 中
2 通知事件函数执行逻辑:首先遍历map容器,有没有感兴趣的id
若有,则代表一系列观察者,对这个事件感兴趣,再次遍历观察者列表,让其执行相应的槽函数。
程序运行时,connect借助两个字符串,即可将信号与槽的关联建立起来,那么,它是如果做到的呢?C++的经验可以告诉我们:
1.类中应该保存有信号和槽的字符串信息
2.字符串和信号槽函数要关联
moc_xxx.cpp文件中看到
// SIGNAL 0
void Worker::resultReady(const QString & _t1)
{
void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
QMetaObject::activate(this, &staticMetaObject, 0, _a);
}
qobjectdefs.h文件里
// 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);
所以
class Object;
struct MetaObject
{
const char * sig_names;
const char * slts_names;
static void active(Object * sender, int idx);
};
这个函数该怎么写呢:思路很简单
●从前面的保存连接的map中,找出与该信号关联的对象和槽
●调用该对象这个槽
typedef std::multimap<int, Connection> ConnectionMap;
typedef std::multimap<int, Connection>::iterator ConnectionMapIt;
void MetaObject::active(Object* sender, int idx)
{
ConnectionMapIt it;
std::pair<ConnectionMapIt, ConnectionMapIt> ret;
ret = sender->connections.equal_range(idx);
for (it=ret.first; it!=ret.second; ++it) {
Connection c = (*it).second;
**//c.receiver->metacall(c.method);**
}
}
槽如何调用呢
void Worker::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
if (_c == QMetaObject::InvokeMetaMethod) {
auto *_t = static_cast<Worker *>(_o);
Q_UNUSED(_t)
switch (_id) {
case 0: _t->resultReady((*reinterpret_cast< const QString(*)>(_a[1]))); break;
case 1: _t->doWork((*reinterpret_cast< const QString(*)>(_a[1]))); break;
default: ;
}
} else if (_c == QMetaObject::IndexOfMethod) {
int *result = reinterpret_cast<int *>(_a[0]);
{
using _t = void (Worker::*)(const QString & );
if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&Worker::resultReady)) {
*result = 0;
return;
}
}
}
}
void myclass::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
if (_c == QMetaObject::InvokeMetaMethod) {
auto *_t = static_cast<myclass *>(_o);
Q_UNUSED(_t)
switch (_id) {
case 0: _t->operate((*reinterpret_cast< const QString(*)>(_a[1]))); break;
case 1: _t->handleResults((*reinterpret_cast< const QString(*)>(_a[1]))); break;
default: ;
}
} else if (_c == QMetaObject::IndexOfMethod) {
int *result = reinterpret_cast<int *>(_a[0]);
{
using _t = void (myclass::*)(const QString & );
if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&myclass::operate)) {
*result = 0;
return;
}
}
}
}
●直接调用槽函数我们都知道了,就一个普通函数
●可现在通过索引调用了,那么我们必须定义一个接口函数
class Object
{
void metacall(int idx);
...