ui_xxx.h
QMetaObject
是 Qt 元对象系统的核心类之一,提供了对 Qt 对象类型(通常是继承自 QObject
的类)的元信息访问能力,包括:
- 类名
- 父类信息
- 信号与槽
- 属性(
Q_PROPERTY
) - 枚举、方法、构造函数等
这使得 Qt 支持反射功能,如运行时动态调用函数、获取属性信息、连接信号与槽等。
基本概念
在 Qt 中,元对象系统是通过 Q_OBJECT
宏启用的。任何类只要继承 QObject
并声明 Q_OBJECT
宏,就会自动生成一个 static QMetaObject
成员(编译器会用 moc
工具生成)。
例如:
1
2
3
4
5
6
7
8
9
10
11
| class MyObject : public QObject {
Q_OBJECT
public:
MyObject(QObject *parent = nullptr);
signals:
void mySignal();
public slots:
void mySlot();
};
|
编译后,MyObject
会自动生成一个 static const QMetaObject staticMetaObject
和一个虚函数 metaObject()
来访问它。
常用接口
1
| const QMetaObject *meta = obj->metaObject();
|
类信息
1
2
| meta->className(); // 类名
meta->superClass(); // 父类的 QMetaObject*
|
方法信息(信号/槽/普通方法)
1
2
3
4
5
| int methodCount = meta->methodCount();
for (int i = 0; i < methodCount; ++i) {
QMetaMethod method = meta->method(i);
qDebug() << method.methodSignature(); // 方法签名
}
|
可以根据签名获取某方法并调用:
1
| meta->invokeMethod(obj, "mySlot");
|
属性信息
1
2
3
4
5
| int propertyCount = meta->propertyCount();
for (int i = 0; i < propertyCount; ++i) {
QMetaProperty prop = meta->property(i);
qDebug() << prop.name() << prop.read(obj);
}
|
信号与槽连接(高级方式)
1
2
3
| QMetaMethod signal = meta->method(meta->indexOfSignal("mySignal()"));
QMetaMethod slot = target->metaObject()->method(target->metaObject()->indexOfSlot("mySlot()"));
QObject::connect(obj, signal, target, slot);
|
常用场景举例
- 动态方法调用
1
| QMetaObject::invokeMethod(obj, "mySlot", Qt::DirectConnection);
|
- 动态属性读写
1
2
| QVariant value = obj->property("name"); // 读取
obj->setProperty("name", QVariant("Qt")); // 设置
|
- 自定义事件派发(静态辅助函数)
1
| QMetaObject::invokeMethod(obj, "doSomething", Qt::QueuedConnection);
|
功能类别 | 方法 / 接口 | 示例 / 用法说明 |
---|
类信息 | className() | 获取类名,如 "MyObject" |
| superClass() | 获取父类的 QMetaObject* |
方法(函数) | methodCount() | 返回类中方法的总数(含信号、槽、普通方法) |
| method(int index) | 获取指定索引的 QMetaMethod |
| indexOfMethod("foo()") | 获取方法索引(也可用 indexOfSignal() / indexOfSlot() ) |
| QMetaObject::invokeMethod(obj, "foo") | 动态调用方法 |
属性 | propertyCount() | 获取属性总数 |
| property(int index) | 获取第 i 个 QMetaProperty |
| QObject::property("name") | 读取对象属性值 |
| QObject::setProperty("name", value) | 设置对象属性值 |
信号与槽 | indexOfSignal("sig()") | 获取信号的索引 |
| indexOfSlot("slot()") | 获取槽函数的索引 |
| method(index) -> QMetaMethod | 获取信号/槽方法元信息 |
| QObject::connect(obj, signalMethod, target, slotMethod) | 使用 QMetaMethod 高级连接方式 |
动态调用 | QMetaObject::invokeMethod() | 支持 DirectConnection , QueuedConnection , 传参等 |
其他功能 | enumeratorCount() / enumerator(index) | 访问 Q_ENUM 枚举信息 |
| constructorCount() / constructor(index) | 是通过 Q_INVOKABLE 声明的构造函数,获取构造函数信息(较少使用) |
Q_INVOKABLE 宏
Q_INVOKABLE
是 Qt 提供的一个宏,用于标记一个成员函数,使其能通过 QMetaObject
系统在运行时被动态调用,也就是支持反射(类似 Java 的 public
+ @Reflectable
)。它常与 QMetaObject::invokeMethod()
、QML、Qt 的脚本系统一起使用。
Q_INVOKABLE
的作用:
将类中的普通成员函数注册到 Qt 的元对象系统中,使其可以被动态查找、调用,或被 QML / 脚本访问。
如果有一个 public
函数,但没有使用 Q_INVOKABLE
或将其声明为 public slots:
,它不会被元对象系统识别,不能用 invokeMethod()
动态调用。
使用示例
基本用法
1
2
3
4
5
6
7
8
9
10
| class MyObject : public QObject {
Q_OBJECT
public:
MyObject(QObject *parent = nullptr) : QObject(parent) {}
Q_INVOKABLE void sayHello() {
qDebug() << "Hello from Q_INVOKABLE function!";
}
};
|
动态调用:
1
2
| MyObject obj;
QMetaObject::invokeMethod(&obj, "sayHello");
|
没有 Q_INVOKABLE
的话,这里会调用失败。
与参数一起使用
1
2
3
| Q_INVOKABLE QString greet(const QString &name) {
return "Hello, " + name;
}
|
调用(支持传参):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| #include "widget.h"
#include <QApplication>
#include <QMetaObject>
#include <QVariant>
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
Widget w;
// 构造参数
QString name = "ChatGPT";
QString returnValue;
// 正确调用 greet(QString)
bool success = QMetaObject::invokeMethod(
&w, // 对象指针
"greet", // 方法名
Q_RETURN_ARG(QString , returnValue), // 返回值
Q_ARG(QString, name) // 参数类型必须精确匹配
);
if (success) {
qDebug() << "Return value:" << returnValue;
} else {
qDebug() << "invokeMethod failed!";
}
w.show();
return a.exec();
}
|
与 slots 的区别
特性 | Q_INVOKABLE | slots |
---|
可动态调用 | ✅ 是 | ✅ 是 |
可连接为槽函数 | ❌ 否(不能自动 connect) | ✅ 是 |
可被 QML 调用 | ✅ 是 | ✅ 是(仅 public slots ) |
是否影响运行时性能 | 否,完全静态编译时注册 | 否 |
函数原型(最常用的重载)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| bool QMetaObject::invokeMethod(
QObject *obj, // 【1】目标对象,必须继承 QObject
const char *member, // 【2】要调用的成员函数名称(函数名字符串,不含参数类型)
Qt::ConnectionType type = Qt::AutoConnection, // 【3】连接类型(控制是同步调用还是异步调用等)
QGenericReturnArgument ret = QGenericReturnArgument(), // 【4】返回值参数(使用 Q_RETURN_ARG 封装)
QGenericArgument val0 = QGenericArgument(), // 【5~14】函数参数(最多支持 10 个参数)
QGenericArgument val1 = QGenericArgument(),
QGenericArgument val2 = QGenericArgument(),
QGenericArgument val3 = QGenericArgument(),
QGenericArgument val4 = QGenericArgument(),
QGenericArgument val5 = QGenericArgument(),
QGenericArgument val6 = QGenericArgument(),
QGenericArgument val7 = QGenericArgument(),
QGenericArgument val8 = QGenericArgument(),
QGenericArgument val9 = QGenericArgument()
);
|
常用简洁版本(Qt 5/6 常用)
1
2
3
4
5
6
| QMetaObject::invokeMethod(
obj,
"methodName",
Q_RETURN_ARG(Type, returnValue),
Q_ARG(ParamType, param)
);
|
Q_ENUMS 宏
Q_ENUMS
是 Qt 早期(Qt 5 及以前)用于将枚举类型注册到元对象系统中的宏,使得枚举值可以通过反射访问、打印、与 QML 或调试工具交互。
不过,它已经被 Q_ENUM
(注意少了一个 S)取代,并且后者功能更强、类型安全更高,是 Qt 5.5 起推荐的用法。
使用方式(老版)
1
2
3
4
5
6
7
8
9
10
11
| class MyClass : public QObject {
Q_OBJECT
Q_ENUMS(Status) // 注册枚举类型到元对象系统
public:
enum Status {
Idle,
Running,
Finished
};
};
|
使用 QMetaEnum
获取枚举信息:
1
2
3
4
5
6
7
8
9
| // 通过枚举名字 "Status" 获取该枚举在 MyClass 元对象中的索引(编号)
int index = MyClass::staticMetaObject.indexOfEnumerator("Status");
// 用枚举索引获取对应的 QMetaEnum 对象,QMetaEnum 提供枚举反射功能
QMetaEnum metaEnum = MyClass::staticMetaObject.enumerator(index);
// 使用 QMetaEnum 的 valueToKey 方法,将枚举值转换为对应的字符串名称
// 这里传入的是枚举值 MyClass::Running,返回字符串 "Running"
qDebug() << metaEnum.valueToKey(MyClass::Running); // 输出: "Running"
|
推荐:使用 Q_ENUM
(Qt 5.5+)
1
2
3
4
5
6
7
8
9
10
11
| class MyClass : public QObject {
Q_OBJECT
public:
enum Status {
Idle,
Running,
Finished
};
Q_ENUM(Status) // 推荐写法(类型安全,更现代)
};
|
项目 | Q_ENUMS | ✅ Q_ENUM |
---|
自动注册 | 否 | 是 |
支持 QMetaEnum 反射 | 是 | 是 |
类型安全(编译期检查) | 否 | ✅ 是 |
QML 自动识别 | 否 | ✅ 是(搭配 Q_GADGET / QObject ) |
Qt 6 是否推荐 | ❌ 否 | ✅ 是 |
Q_FLAGS 宏
Q_FLAGS
是 Qt 用来将 枚举组合类型(flags) 注册到元对象系统的宏。它可以把基于位掩码的枚举类型(通常用于表示多选状态的组合)注册进 Qt 元对象系统,支持反射、调试输出、QML 使用等。
什么是 Flags
Flags 是一种通过位运算(按位或 |
)组合多个枚举值的技术。常见用法:
1
2
3
4
5
6
| enum Option {
OptionA = 0x01,
OptionB = 0x02,
OptionC = 0x04,
};
Q_DECLARE_FLAGS(Options, Option) // 定义 Options 为 Option 的组合类型
|
这样就可以写:
1
| Options opts = OptionA | OptionC;
|
示例
myclass.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
| #ifndef MYCLASS_H
#define MYCLASS_H
#include <QObject>
// MyClass 继承自 QObject,支持 Qt 元对象系统
class MyClass : public QObject {
Q_OBJECT
public:
// 定义枚举 Option,表示不同选项,使用位掩码方便组合
enum Option {
OptionA = 0x01,
OptionB = 0x02,
OptionC = 0x04,
};
// 定义 Flags 类型,允许多个 Option 位组合在一起
Q_DECLARE_FLAGS(Options, Option)
// 将 Flags 类型注册到元对象系统,支持反射和信号槽等特性
Q_FLAGS(Options)
explicit MyClass(QObject* parent = nullptr);
// 设置选项,接收多个枚举值的组合
void setOptions(Options opts);
// 获取当前选项组合
Options options() const;
private:
Options m_options; // 保存当前选项组合
};
// 启用按位操作符(|、&、^ 等)支持 Options 类型
Q_DECLARE_OPERATORS_FOR_FLAGS(MyClass::Options)
#endif // MYCLASS_H
|
在头文件中这行代码定义了 Options
这个名字:
1
| Q_DECLARE_FLAGS(Options, Option)
|
这行宏展开后等价于:
1
| typedef QFlags<Option> Options;
|
Options
是 QFlags<Option>
类型,用来优雅地处理 Option
枚举的位组合,并能被 Qt 的元对象系统识别和操作。
myclass.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| #include "myclass.h"
// 构造函数,初始化 m_options 为0(无选项)
MyClass::MyClass(QObject* parent) : QObject(parent), m_options(0) {
}
// 设置选项组合
void MyClass::setOptions(Options opts) {
m_options = opts;
}
// 获取当前选项组合
MyClass::Options MyClass::options() const {
return m_options;
}
|
main.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| #include "myclass.h"
#include <QCoreApplication>
#include <QDebug>
#include <QMetaEnum>
int main(int argc, char* argv[]) {
QCoreApplication app(argc, argv);
MyClass obj;
// 使用按位或组合多个选项,设置 OptionA 和 OptionC
obj.setOptions(MyClass::OptionA | MyClass::OptionC);
// 直接打印枚举组合,会显示字符串形式(依赖 Q_FLAGS 宏)
qDebug() << "Options set:" << obj.options();
// 通过元对象系统获取枚举信息
int index = MyClass::staticMetaObject.indexOfEnumerator("Option");
QMetaEnum metaEnum = MyClass::staticMetaObject.enumerator(index);
// 把枚举值(包含组合位)转换成字符串,多个选项用 '|' 连接
QString flagsString = metaEnum.valueToKeys(obj.options());
qDebug() << "Flags as string via QMetaEnum:" << flagsString;
return 0;
}
|
元对象系统综合示例
打开 widget.ui 文件,拖入一个标签和三个编辑栏。进入信号和槽的编辑模式,鼠标左键从一个编辑框拖到标签,松开后会弹出配置连接的对话框,选择信号 textEdited(QString) 和槽 setText(QString)。进入伙伴编辑模式,鼠标左键从标签拖到编辑框。进入 Tab 顺序编辑模式,依次点击控件的顺序。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
| #ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE
class Widget : public QWidget {
Q_OBJECT
public:
Widget(QWidget* parent = nullptr);
~Widget();
Q_PROPERTY(QString nickName READ nickName WRITE setNickName NOTIFY nickNameChanged)
Q_PROPERTY(int count MEMBER m_count READ count WRITE setCount NOTIFY countChanged)
Q_PROPERTY(double value MEMBER m_value NOTIFY valueChanged)
const QString& nickName() const;
int count() const;
signals:
void nickNameChanged(const QString& strNewName);
void countChanged(int nNewCount);
void valueChanged(double dblNewValue);
public slots:
void setNickName(const QString& strNewName);
void setCount(int nNewCount);
private:
Ui::Widget* ui;
QString m_nickName;
int m_count;
double m_value;
};
#endif // WIDGET_H
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| #include "widget.h"
#include "./ui_widget.h"
Widget::Widget(QWidget* parent) : QWidget(parent), ui(new Ui::Widget) {
ui->setupUi(this);
}
Widget::~Widget() {
delete ui;
}
const QString& Widget::nickName() const {
return m_nickName;
}
int Widget::count() const {
return m_count;
}
void Widget::setNickName(const QString& strNewName) {
if (strNewName == m_nickName) return;
m_nickName = strNewName;
emit nickNameChanged(strNewName);
}
void Widget::setCount(int nNewCount) {
if (nNewCount == m_count) return;
m_count = nNewCount;
emit countChanged(nNewCount);
}
|
showchanges.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| #ifndef SHOWCHANGES_H
#define SHOWCHANGES_H
#include <QObject>
class ShowChanges : public QObject {
Q_OBJECT
public:
explicit ShowChanges(QObject* parent = nullptr);
public slots:
void recvValue(double v); // 用于接收 valueChanged 信号的槽函数
void recvNickName(const QString& strNewName);
void recvCount(int nNewCount);
};
#endif // SHOWCHANGES_H
|
showchanges.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| #include "showchanges.h"
#include <QDebug>
ShowChanges::ShowChanges(QObject* parent) : QObject{parent} {
}
void ShowChanges::recvValue(double v) {
qDebug() << "recvValue: " << v;
}
void ShowChanges::recvNickName(const QString& strNewName) {
qDebug() << "recvNickName: " << strNewName;
}
void ShowChanges::recvCount(int nNewCount) {
qDebug() << "recvCount: " << nNewCount;
}
|
main.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| #include "showchanges.h"
#include "widget.h"
#include <QApplication>
#include <QDebug>
int main(int argc, char* argv[]) {
QApplication a(argc, argv);
Widget w;
ShowChanges s;
QObject::connect(&w, &Widget::valueChanged, &s, &ShowChanges::recvValue);
QObject::connect(&w, &Widget::nickNameChanged, &s, &ShowChanges::recvNickName);
QObject::connect(&w, &Widget::countChanged, &s, &ShowChanges::recvCount);
w.setNickName("Wid");
qDebug() << w.nickName();
w.setCount(7);
qDebug() << w.count();
w.setProperty("value", 3.14);
qDebug() << w.property("value").toDouble();
w.show();
return a.exec();
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
| /********************************************************************************
** Form generated from reading UI file 'widget.ui'
**
** Created by: Qt User Interface Compiler version 6.9.0
**
** WARNING! All changes made in this file will be lost when recompiling UI file!
********************************************************************************/
#ifndef UI_WIDGET_H
#define UI_WIDGET_H
// 包含必要的 Qt 模块头文件,用于界面元素
#include <QtCore/QVariant>
#include <QtWidgets/QApplication>
#include <QtWidgets/QLabel>
#include <QtWidgets/QLineEdit>
#include <QtWidgets/QVBoxLayout>
#include <QtWidgets/QWidget>
QT_BEGIN_NAMESPACE
// 自动生成的 UI 类,用于描述界面结构和初始化控件
class Ui_Widget {
public:
// 控件声明
QWidget* verticalLayoutWidget;
QVBoxLayout* verticalLayout;
QLineEdit* lineEdit;
QLabel* label;
QLineEdit* lineEdit_2;
QLineEdit* lineEdit_3;
// 设置 UI 元素(由 Qt 自动生成)
void setupUi(QWidget* Widget) {
// 设置窗口对象名和大小
if (Widget->objectName().isEmpty())
Widget->setObjectName("Widget");
Widget->resize(479, 290);
// 创建一个布局容器部件(内部 QWidget)
verticalLayoutWidget = new QWidget(Widget);
verticalLayoutWidget->setObjectName("verticalLayoutWidget");
verticalLayoutWidget->setGeometry(QRect(110, 20, 231, 181)); // 设置位置和大小
// 创建垂直布局器,挂在到 verticalLayoutWidget 上
verticalLayout = new QVBoxLayout(verticalLayoutWidget);
verticalLayout->setObjectName("verticalLayout");
verticalLayout->setContentsMargins(0, 0, 0, 0); // 去除边距
// 创建第一个文本框
lineEdit = new QLineEdit(verticalLayoutWidget);
lineEdit->setObjectName("lineEdit");
verticalLayout->addWidget(lineEdit); // 加入布局
// 创建标签
label = new QLabel(verticalLayoutWidget);
label->setObjectName("label");
verticalLayout->addWidget(label); // 加入布局
// 第二个文本框
lineEdit_2 = new QLineEdit(verticalLayoutWidget);
lineEdit_2->setObjectName("lineEdit_2");
verticalLayout->addWidget(lineEdit_2);
// 第三个文本框
lineEdit_3 = new QLineEdit(verticalLayoutWidget);
lineEdit_3->setObjectName("lineEdit_3");
verticalLayout->addWidget(lineEdit_3);
#if QT_CONFIG(shortcut)
// 设置 label 的快捷键对应控件(此处是 lineEdit)
label->setBuddy(lineEdit);
#endif // QT_CONFIG(shortcut)
// 设置 Tab 键的切换顺序
QWidget::setTabOrder(lineEdit, lineEdit_2);
QWidget::setTabOrder(lineEdit_2, lineEdit_3);
// 设置翻译文本(界面文字)
retranslateUi(Widget);
// 设置当 lineEdit 编辑文本时,将文本设置到 label 上
QObject::connect(lineEdit, &QLineEdit::textEdited, label, &QLabel::setText);
// 自动连接 Qt Designer 中设置的信号与槽
QMetaObject::connectSlotsByName(Widget);
} // setupUi
// 设置界面文本内容(可用于多语言翻译)
void retranslateUi(QWidget* Widget) {
Widget->setWindowTitle(QCoreApplication::translate("Widget", "Widget", nullptr));
label->setText(QCoreApplication::translate("Widget", "TextLabel", nullptr));
} // retranslateUi
};
// Ui 命名空间中定义了一个 Widget 类,继承自动生成的 Ui_Widget
// 这样我们可以在主程序中使用 Ui::Widget 来访问所有 UI 元素
namespace Ui {
class Widget : public Ui_Widget {};
} // namespace Ui
QT_END_NAMESPACE
#endif // UI_WIDGET_H
|
ui_widget.h
是 Qt 自动生成的文件,由 uic
(User Interface Compiler) 工具根据 Qt Designer 设计的 .ui
文件生成。
这个文件中定义了一个 Ui::Widget
类,包含所有在 Designer 里拖拽的控件,以及 setupUi()
函数。
QT_CONFIG(shortcut)
是 Qt 的配置宏,判断当前构建是否启用了 快捷键(shortcut)功能模块。
- 如果启用了(大多数桌面 Qt 默认开启),这段代码会编译。
- 如果未启用(如一些嵌入式 Qt 精简配置中),这段代码被忽略。
小结
文件名 | 说明 |
---|
widget.ui | 在 Qt Designer 中创建的界面文件 |
ui_widget.h | Qt 自动生成的头文件,包含界面控件和初始化函数 |
Widget 类 | 手写的逻辑类,通过 setupUi() 使用界面控件 |