强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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
READgetter 函数 / getter function
WRITEsetter 函数 / setter function
NOTIFY值变化信号 / change signal
RESET重置为默认值 / reset to default
DESIGNABLEQt 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 implementationsemit 的底层调用 / Behind emit calls
槽函数调用 / Slot invocationsconnect() 的回调机制 / Callback mechanism
qt_metacall()运行时方法调用 / Runtime method invocation
类型信息 / Type infoclassName(), inherits() 等 / Runtime type info
属性访问 / Property accessproperty() / 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_OBJECT must be in the header (.h) class declaration. If in a .cpp file, 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

01 - Qt 与 GTK 概述 | 03 - Qt Widgets 控件