Qt 与 GTK 图形框架教程 / 02 - Qt 基础 / Qt Basics
Qt 基础 / Qt Basics
掌握 Qt 的核心机制:信号与槽、对象模型、属性系统和元对象编译器。 Master Qt’s core mechanisms: signals & slots, object model, property system, and MOC.
2.1 Qt 对象模型 / Qt Object Model
Qt 的对象模型是 C++ 的超集,所有 Qt 对象都继承自 QObject。
Qt’s object model extends C++; all Qt objects inherit from QObject.
QObject 核心功能
| 功能 / Feature | 说明 / Description |
|---|---|
| 对象树 (Object Tree) | 父对象自动销毁子对象 / Parent auto-deletes children |
| 信号与槽 | 类型安全的通信机制 / Type-safe communication |
| 属性系统 | 运行时动态属性 / Runtime dynamic properties |
| 事件系统 | 事件循环与事件分发 / Event loop & dispatch |
| 国际化 | tr() 翻译支持 / Translation support |
| 内省 | 运行时类型信息 / Runtime type information |
C++ 示例 / C++ Example
// main.cpp - Qt 对象模型基础
// Qt Object Model Basics
#include <QCoreApplication>
#include <QDebug>
#include <QObject>
#include <QTimer>
class Animal : public QObject {
Q_OBJECT
public:
explicit Animal(const QString &name, QObject *parent = nullptr)
: QObject(parent), m_name(name) {
qDebug() << "Animal created:" << m_name;
}
~Animal() override {
qDebug() << "Animal destroyed:" << m_name;
}
QString name() const { return m_name; }
private:
QString m_name;
};
int main(int argc, char *argv[]) {
QCoreApplication app(argc, argv);
// 对象树演示:parent 拥有 child 的所有权
// Object tree: parent owns child
QObject root;
auto *cat = new Animal("Cat", &root); // root 拥有 cat
auto *dog = new Animal("Dog", &root); // root 拥有 dog
auto *bird = new Animal("Bird", cat); // cat 拥有 bird
qDebug() << "Children of root:" << root.children().size();
qDebug() << "Children of cat:" << cat->children().size();
// 当 root 被销毁时,cat、dog、bird 都会被自动销毁
// When root is destroyed, cat, dog, bird are auto-destroyed
// 遍历对象树
// Traverse object tree
for (QObject *child : root.children()) {
auto *animal = qobject_cast<Animal *>(child);
if (animal) {
qDebug() << "Found animal:" << animal->name();
}
}
// 使用 QTimer 在 0ms 后退出(演示事件循环)
QTimer::singleShot(0, &app, &QCoreApplication::quit);
return app.exec();
}
#include "main.moc"
Python 示例 / Python Example (PySide6)
#!/usr/bin/env python3
"""Qt 对象模型基础 - PySide6"""
"""Qt Object Model Basics - PySide6"""
import sys
from PySide6.QtCore import QObject, QTimer
class Animal(QObject):
"""动物类,演示对象树"""
"""Animal class demonstrating object tree"""
def __init__(self, name: str, parent: QObject | None = None):
super().__init__(parent)
self._name = name
print(f"Animal created: {name}")
def __del__(self):
print(f"Animal destroyed: {self._name}")
@property
def name(self) -> str:
return self._name
def main():
root = QObject()
cat = Animal("Cat", root) # root 拥有 cat
dog = Animal("Dog", root) # root 拥有 dog
bird = Animal("Bird", cat) # cat 拥有 bird
print(f"Children of root: {len(root.children())}")
print(f"Children of cat: {len(cat.children())}")
# 遍历对象树
for child in root.children():
if isinstance(child, Animal):
print(f"Found animal: {child.name}")
# root 销毁时,所有子对象自动销毁
print("--- Destroying root ---")
root.deleteLater()
if __name__ == "__main__":
from PySide6.QtCore import QCoreApplication
app = QCoreApplication(sys.argv)
QTimer.singleShot(0, main)
QTimer.singleShot(100, app.quit)
app.exec()
对象树图示 / Object Tree Diagram
root (QObject)
├── cat (Animal)
│ └── bird (Animal) ← cat 拥有 bird
└── dog (Animal)
销毁顺序:root → cat → dog → bird (先子后父)
Destruction order: root → cat → dog → bird (children first)
2.2 信号与槽 / Signals and Slots
信号与槽是 Qt 的核心通信机制,替代了传统的回调函数。 Signals & slots are Qt’s core communication mechanism, replacing callbacks.
基本语法 / Basic Syntax
| 关键字 / Keyword | 作用 / Role |
|---|---|
signals: | 声明信号(自动生成) / Declare signals (auto-generated) |
slots: | 声明槽函数 / Declare slot functions |
emit | 发射信号 / Emit a signal |
connect() | 连接信号与槽 / Connect signal to slot |
disconnect() | 断开连接 / Disconnect |
C++ 示例 / C++ Example
// signals_slots.cpp - 信号与槽完整示例
// Signals & Slots Complete Example
#include <QCoreApplication>
#include <QDebug>
#include <QTimer>
class Thermometer : public QObject {
Q_OBJECT
public:
explicit Thermometer(QObject *parent = nullptr) : QObject(parent) {}
void setTemperature(double temp) {
if (!qFuzzyCompare(m_temperature, temp)) {
m_temperature = temp;
emit temperatureChanged(temp);
if (temp > 100.0) {
emit overheating(temp);
}
}
}
signals:
void temperatureChanged(double newTemp);
void overheating(double temp);
private:
double m_temperature = 0.0;
};
class Alarm : public QObject {
Q_OBJECT
public slots:
void onOverheating(double temp) {
qDebug() << "⚠️ ALARM: Temperature too high:" << temp << "°C!";
}
};
class Display : public QObject {
Q_OBJECT
public slots:
void updateDisplay(double temp) {
qDebug() << "🌡️ Display:" << temp << "°C";
}
};
int main(int argc, char *argv[]) {
QCoreApplication app(argc, argv);
auto *thermo = new Thermometer;
auto *display = new Display;
auto *alarm = new Alarm;
// 1:1 连接 / 1:1 connection
QObject::connect(thermo, &Thermometer::temperatureChanged,
display, &Display::updateDisplay);
// 1:N 连接:同一个信号连接多个槽 / 1:N: one signal, multiple slots
QObject::connect(thermo, &Thermometer::overheating,
display, &Display::updateDisplay);
QObject::connect(thermo, &Thermometer::overheating,
alarm, &Alarm::onOverheating);
// Lambda 连接 / Lambda connection
QObject::connect(thermo, &Thermometer::temperatureChanged,
[](double temp) {
if (temp < 0) {
qDebug() << "❄️ Freezing!";
}
});
// 模拟温度变化
QTimer::singleShot(0, [thermo]() {
thermo->setTemperature(25.0); // 正常
thermo->setTemperature(-5.0); // 低温
thermo->setTemperature(105.0); // 过热
});
QTimer::singleShot(100, &app, &QCoreApplication::quit);
return app.exec();
}
#include "main.moc"
Python 示例 / Python Example (PySide6)
#!/usr/bin/env python3
"""信号与槽完整示例 - PySide6"""
"""Signals & Slots Complete Example - PySide6"""
import sys
from PySide6.QtCore import QObject, Signal, Slot, QCoreApplication, QTimer
class Thermometer(QObject):
"""温度计:发射信号"""
"""Thermometer: emits signals"""
temperature_changed = Signal(float)
overheating = Signal(float)
def __init__(self, parent: QObject | None = None):
super().__init__(parent)
self._temperature = 0.0
@property
def temperature(self) -> float:
return self._temperature
@temperature.setter
def temperature(self, value: float):
if self._temperature != value:
self._temperature = value
self.temperature_changed.emit(value)
if value > 100.0:
self.overheating.emit(value)
class Alarm(QObject):
"""警报器:接收信号"""
"""Alarm: receives signals"""
@Slot(float)
def on_overheating(self, temp: float):
print(f"⚠️ ALARM: Temperature too high: {temp}°C!")
class Display(QObject):
"""显示器"""
"""Display"""
@Slot(float)
def update_display(self, temp: float):
print(f"🌡️ Display: {temp}°C")
def main():
thermo = Thermometer()
display = Display()
alarm = Alarm()
# 1:1 连接
thermo.temperature_changed.connect(display.update_display)
# 1:N 连接
thermo.overheating.connect(display.update_display)
thermo.overheating.connect(alarm.on_overheating)
# Lambda 连接
thermo.temperature_changed.connect(
lambda temp: print("❄️ Freezing!") if temp < 0 else None
)
# 触发信号
thermo.temperature = 25.0
thermo.temperature = -5.0
thermo.temperature = 105.0
if __name__ == "__main__":
app = QCoreApplication(sys.argv)
QTimer.singleShot(0, main)
QTimer.singleShot(100, app.quit)
app.exec()
连接类型 / Connection Types
| 类型 / Type | 枚举值 / Enum | 说明 / Description |
|---|---|---|
| 自动 | Qt::AutoConnection | 默认,同线程直接调用,跨线程队列 / Default |
| 直接 | Qt::DirectConnection | 槽在发射线程执行 / Slot runs in emitter’s thread |
| 队列 | Qt::QueuedConnection | 槽在接收者线程执行 / Slot runs in receiver’s thread |
| 阻塞队列 | Qt::BlockingQueuedConnection | 阻塞直到槽执行完 / Blocks until slot finishes |
| 唯一 | Qt::UniqueConnection | 防止重复连接 / Prevents duplicate connections |
// 跨线程连接示例
// Cross-thread connection
QObject::connect(worker, &Worker::resultReady,
gui, &GUI::updateResult,
Qt::QueuedConnection);
// 防止重复连接
// Prevent duplicate connections
QObject::connect(button, &QPushButton::clicked,
this, &MyWidget::onClicked,
Qt::UniqueConnection);
新旧语法对比 / Old vs New Syntax
// ❌ 旧语法(字符串-based,编译期不检查类型)
// Old syntax (string-based, no compile-time check)
connect(button, SIGNAL(clicked()), this, SLOT(onClicked()));
// ✅ 新语法(类型安全,编译期检查)
// New syntax (type-safe, compile-time check)
connect(button, &QPushButton::clicked, this, &MyWidget::onClicked);
// ✅ 新语法 + Lambda
connect(button, &QPushButton::clicked, [this]() {
qDebug() << "Button clicked!";
});
⚠️ 始终使用新语法。旧语法在运行时匹配签名,拼写错误不会编译报错。 Always use new syntax. Old syntax matches at runtime; typos won’t cause compile errors.
2.3 属性系统 / Property System
Qt 属性系统在 C++ 基础上添加了运行时反射能力。 Qt’s property system adds runtime reflection to C++.
Q_PROPERTY 宏 / Q_PROPERTY Macro
| 参数 / Parameter | 说明 / Description |
|---|---|
READ | getter 函数 / getter function |
WRITE | setter 函数 / setter function |
NOTIFY | 值变化信号 / change signal |
RESET | 重置为默认值 / reset to default |
DESIGNABLE | Qt Designer 是否可见 / visible in designer |
SCRIPTABLE | 脚本是否可访问 / accessible from scripts |
STORED | 是否存储到文件 / stored in save files |
USER | 是否是用户可见属性 / user-facing property |
C++ 示例 / C++ Example
#include <QObject>
#include <QColor>
#include <QDebug>
class Person : public QObject {
Q_OBJECT
// 声明属性 / Declare properties
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
Q_PROPERTY(int age READ age WRITE setAge NOTIFY ageChanged)
Q_PROPERTY(QColor favoriteColor READ favoriteColor
WRITE setFavoriteColor NOTIFY favoriteColorChanged)
public:
explicit Person(QObject *parent = nullptr) : QObject(parent) {}
// getter 函数 / Getters
QString name() const { return m_name; }
int age() const { return m_age; }
QColor favoriteColor() const { return m_color; }
// setter 函数 / Setters
void setName(const QString &name) {
if (m_name != name) {
m_name = name;
emit nameChanged(name);
}
}
void setAge(int age) {
if (m_age != age) {
m_age = age;
emit ageChanged(age);
}
}
void setFavoriteColor(const QColor &color) {
if (m_color != color) {
m_color = color;
emit favoriteColorChanged(color);
}
}
signals:
void nameChanged(const QString &name);
void ageChanged(int age);
void favoriteColorChanged(const QColor &color);
private:
QString m_name;
int m_age = 0;
QColor m_color = Qt::blue;
};
// 动态属性操作 / Dynamic property operations
void propertyDemo() {
Person person;
person.setName("Alice");
person.setAge(30);
person.setFavoriteColor(Qt::red);
// 通过属性系统读写 / Read/write via property system
qDebug() << "Name:" << person.property("name").toString();
person.setProperty("age", 31);
// 动态添加属性 / Add dynamic property
person.setProperty("nickname", "Ali");
qDebug() << "Nickname:" << person.property("nickname").toString();
// 遍历所有属性 / Iterate all properties
const QMetaObject *meta = person.metaObject();
for (int i = 0; i < meta->propertyCount(); ++i) {
QMetaProperty prop = meta->property(i);
qDebug() << prop.name() << "=" << prop.read(&person);
}
}
Python 示例 / Python Example (PySide6)
"""属性系统示例 - PySide6"""
"""Property System Example - PySide6"""
from PySide6.QtCore import QObject, Property, Signal
class Person(QObject):
name_changed = Signal(str)
age_changed = Signal(int)
def __init__(self, parent=None):
super().__init__(parent)
self._name = ""
self._age = 0
def _get_name(self) -> str:
return self._name
def _set_name(self, name: str):
if self._name != name:
self._name = name
self.name_changed.emit(name)
# PySide6 属性声明
name = Property(str, _get_name, _set_name, notify=name_changed)
def _get_age(self) -> int:
return self._age
def _set_age(self, age: int):
if self._age != age:
self._age = age
self.age_changed.emit(age)
age = Property(int, _get_age, _set_age, notify=age_changed)
# 使用示例
person = Person()
person.name_changed.connect(lambda n: print(f"Name changed to: {n}"))
person.name = "Alice" # 触发信号
2.4 元对象编译器 (MOC) / Meta-Object Compiler
MOC 是 Qt 的预处理器,为使用 Q_OBJECT 宏的类生成额外的 C++ 代码。
MOC is Qt’s preprocessor that generates additional C++ code for classes using Q_OBJECT.
MOC 生成什么 / What MOC Generates
| 生成内容 / Generated | 用途 / Purpose |
|---|---|
| 信号实现 / Signal implementations | emit 的底层调用 / Behind emit calls |
| 槽函数调用 / Slot invocations | connect() 的回调机制 / Callback mechanism |
qt_metacall() | 运行时方法调用 / Runtime method invocation |
| 类型信息 / Type info | className(), inherits() 等 / Runtime type info |
| 属性访问 / Property access | property() / setProperty() 实现 |
CMake 构建配置 / CMake Build Configuration
# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(MyQtApp LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_AUTOMOC ON) # 自动运行 MOC / Auto-run MOC
set(CMAKE_AUTORCC ON) # 自动处理 .qrc / Auto-handle .qrc
set(CMAKE_AUTOUIC ON) # 自动处理 .ui / Auto-handle .ui
find_package(Qt6 REQUIRED COMPONENTS Core Widgets)
add_executable(myapp
main.cpp
mainwindow.h # 含 Q_OBJECT 的头文件
mainwindow.cpp
)
target_link_libraries(myapp PRIVATE Qt6::Core Qt6::Widgets)
qmake 构建配置(旧项目)/ qmake Build (Legacy)
# myapp.pro
QT += core gui widgets
TARGET = myapp
TEMPLATE = app
SOURCES += main.cpp mainwindow.cpp
HEADERS += mainwindow.h
MOC 工作流程 / MOC Workflow
源文件 (.h + .cpp)
│
▼
MOC 预处理器 ──→ moc_*.cpp(自动生成)
│ │
▼ ▼
C++ 编译器 ←────────────┘
│
▼
链接器 ──→ 可执行文件
必须使用 Q_OBJECT 的场景 / When to Use Q_OBJECT
class MyClass : public QObject {
Q_OBJECT // ← 必须!如果类中需要以下功能:
// Required! If class needs:
public:
// ✅ 自定义信号
signals:
void mySignal();
// ✅ 自定义槽函数
public slots:
void mySlot();
// ✅ 使用 tr() 进行国际化
QString text = tr("Hello");
// ✅ 使用 qobject_cast<>()
// ✅ 使用 Q_PROPERTY
// ✅ 使用 dynamic properties
};
2.5 事件系统 / Event System
Qt 的事件系统是信号与槽的底层基础。 Qt’s event system is the underlying foundation of signals & slots.
事件类型层次 / Event Type Hierarchy
| 类型 / Type | 示例 / Examples |
|---|---|
| 输入事件 | QKeyEvent, QMouseEvent, QWheelEvent |
| 绘制事件 | QPaintEvent, QResizeEvent |
| 定时器事件 | QTimerEvent |
| 拖放事件 | QDragEnterEvent, QDropEvent |
| 焦点事件 | QFocusEvent |
| 自定义事件 | QEvent 子类 / QEvent subclass |
C++ 示例 / C++ Example
#include <QWidget>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QDebug>
class MyWidget : public QWidget {
Q_OBJECT
public:
explicit MyWidget(QWidget *parent = nullptr) : QWidget(parent) {
setFocusPolicy(Qt::StrongFocus);
setMouseTracking(true);
}
protected:
// 重写事件处理器 / Override event handlers
void keyPressEvent(QKeyEvent *event) override {
qDebug() << "Key pressed:" << event->text()
<< "code:" << event->key();
if (event->key() == Qt::Key_Escape) {
close();
}
QWidget::keyPressEvent(event);
}
void mousePressEvent(QMouseEvent *event) override {
if (event->button() == Qt::LeftButton) {
qDebug() << "Left click at:" << event->pos();
}
QWidget::mousePressEvent(event);
}
void mouseMoveEvent(QMouseEvent *event) override {
qDebug() << "Mouse at:" << event->pos();
QWidget::mouseMoveEvent(event);
}
// 通用事件过滤 / Generic event filter
bool event(QEvent *event) override {
if (event->type() == QEvent::KeyPress) {
qDebug() << "Event filter: KeyPress";
}
return QWidget::event(event);
}
};
注意事项 / Important Notes
⚠️ Q_OBJECT 必须在头文件中 / Q_OBJECT Must Be in Header
Q_OBJECT宏必须出现在头文件(.h)的类声明中。 如果放在.cpp文件,需要在文件末尾添加#include "filename.moc"。
Q_OBJECTmust be in the header (.h) class declaration. If in a.cppfile, add#include "filename.moc"at the end.
⚠️ 信号参数类型必须匹配 / Signal Parameters Must Match
新语法的
connect()在编译期检查类型,参数不匹配会报错。 Lambda 连接时注意捕获变量的生命周期。New-syntax
connect()checks types at compile time. Parameter mismatches cause errors. Watch variable lifetimes in lambda connections.
⚠️ 循环连接 / Circular Connections
信号 A 连接信号 B,信号 B 连接信号 A,会导致无限循环。 Qt 会自动检测并阻止,但设计时应避免。
Signal A → B → A creates infinite loops. Qt detects this but avoid it in design.
扩展阅读 / Further Reading
| 资源 / Resource | 链接 / Link |
|---|---|
| 信号与槽官方文档 | https://doc.qt.io/qt-6/signalsandslots.html |
| 对象模型 | https://doc.qt.io/qt-6/object.html |
| 属性系统 | https://doc.qt.io/qt-6/properties.html |
| MOC 文档 | https://doc.qt.io/qt-6/moc.html |
| 事件系统 | https://doc.qt.io/qt-6/eventsandfilters.html |