QML 与 C++ 混合编程技术

QML 与 C++ 混合编程是 Qt 开发中的重要技术,它允许开发者结合 QML 的声明式 UI 设计能力和 C++ 的高性能计算能力,构建功能强大、性能优异的应用程序。本文将深入解析 QML 与 C++ 混合编程的核心技术和最佳实践。

一、C++ 与 QML 交互基础

1. 将 C++ 对象暴露给 QML
// person.h
#ifndef PERSON_H
#define PERSON_H

#include <QObject>
#include <QString>

class Person : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
    Q_PROPERTY(int age READ age WRITE setAge NOTIFY ageChanged)
    
public:
    explicit Person(QObject *parent = nullptr);
    
    QString name() const;
    void setName(const QString &name);
    
    int age() const;
    void setAge(int age);
    
signals:
    void nameChanged();
    void ageChanged();
    
private:
    QString m_name;
    int m_age;
};

#endif // PERSON_H

// person.cpp
#include "person.h"

Person::Person(QObject *parent) : QObject(parent), m_age(0)
{
}

QString Person::name() const
{
    return m_name;
}

void Person::setName(const QString &name)
{
    if (m_name != name) {
        m_name = name;
        emit nameChanged();
    }
}

int Person::age() const
{
    return m_age;
}

void Person::setAge(int age)
{
    if (m_age != age) {
        m_age = age;
        emit ageChanged();
    }
}
2. 在 QML 中使用 C++ 对象
// main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "person.h"

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);
    
    // 创建 C++ 对象
    Person person;
    person.setName("张三");
    person.setAge(30);
    
    // 将对象暴露给 QML
    QQmlApplicationEngine engine;
    engine.rootContext()->setContextProperty("myPerson", &person);
    
    // 加载 QML 文件
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    
    return app.exec();
}
// main.qml
import QtQuick 2.15
import QtQuick.Window 2.15

Window {
    id: mainWindow
    visible: true
    width: 400
    height: 300
    title: "QML 与 C++ 交互示例"
    
    Column {
        anchors.centerIn: parent
        spacing: 20
        
        Text {
            text: "姓名: " + myPerson.name
            font.pointSize: 14
        }
        
        Text {
            text: "年龄: " + myPerson.age
            font.pointSize: 14
        }
        
        Button {
            text: "增加年龄"
            onClicked: myPerson.age = myPerson.age + 1
        }
    }
}

二、从 C++ 加载 QML

1. 使用 QQmlComponent 动态加载
#include <QGuiApplication>
#include <QQmlComponent>
#include <QQmlEngine>
#include <QQuickWindow>
#include <QDebug>

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);
    
    QQmlEngine engine;
    
    // 创建 QML 组件
    QQmlComponent component(&engine, QUrl("qrc:/MyComponent.qml"));
    
    // 检查是否有错误
    if (component.isError()) {
        qDebug() << "组件错误:" << component.errors();
        return -1;
    }
    
    // 创建对象实例
    QObject *object = component.create();
    if (object) {
        // 如果是窗口类型,显示窗口
        QQuickWindow *window = qobject_cast<QQuickWindow*>(object);
        if (window) {
            window->show();
        }
    }
    
    return app.exec();
}
2. 从 C++ 调用 QML 函数
// MyComponent.qml
import QtQuick 2.15

Item {
    id: myComponent
    width: 200
    height: 100
    
    function sayHello(name) {
        console.log("你好, " + name + "!");
        return "欢迎来到 QML";
    }
}
// 从 C++ 调用 QML 函数
QObject *object = component.create();
if (object) {
    // 调用 QML 函数
    QVariant returnedValue;
    QVariant msg = "C++";
    QMetaObject::invokeMethod(object, "sayHello",
                              Q_RETURN_ARG(QVariant, returnedValue),
                              Q_ARG(QVariant, msg));
    
    qDebug() << "函数返回值:" << returnedValue.toString();
}

三、从 QML 调用 C++ 函数

1. 使用 Q_INVOKABLE 宏
// calculator.h
#ifndef CALCULATOR_H
#define CALCULATOR_H

#include <QObject>

class Calculator : public QObject
{
    Q_OBJECT
public:
    explicit Calculator(QObject *parent = nullptr);
    
    // 使用 Q_INVOKABLE 声明可从 QML 调用的函数
    Q_INVOKABLE int add(int a, int b);
    Q_INVOKABLE QString formatNumber(int number);
};

#endif // CALCULATOR_H

// calculator.cpp
#include "calculator.h"
#include <QString>

Calculator::Calculator(QObject *parent) : QObject(parent)
{
}

int Calculator::add(int a, int b)
{
    return a + b;
}

QString Calculator::formatNumber(int number)
{
    return QString::number(number);
}
// main.qml
import QtQuick 2.15
import QtQuick.Window 2.15

Window {
    id: mainWindow
    visible: true
    width: 400
    height: 200
    title: "调用 C++ 函数示例"
    
    // 访问 C++ 对象
    property var calculator: Calculator {}
    
    Column {
        anchors.centerIn: parent
        spacing: 20
        
        Text {
            text: "1 + 2 = " + calculator.add(1, 2)
            font.pointSize: 14
        }
        
        Text {
            text: "格式化数字: " + calculator.formatNumber(1234567)
            font.pointSize: 14
        }
    }
}
2. 信号与槽机制
// notifier.h
#ifndef NOTIFIER_H
#define NOTIFIER_H

#include <QObject>

class Notifier : public QObject
{
    Q_OBJECT
public:
    explicit Notifier(QObject *parent = nullptr);
    
signals:
    // 定义信号
    void messageSent(const QString &message);
    
public slots:
    // 定义槽函数
    void sendNotification(const QString &title, const QString &content);
};

#endif // NOTIFIER_H

// notifier.cpp
#include "notifier.h"
#include <QDebug>

Notifier::Notifier(QObject *parent) : QObject(parent)
{
}

void Notifier::sendNotification(const QString &title, const QString &content)
{
    qDebug() << "发送通知:" << title << content;
    emit messageSent("[" + title + "] " + content);
}
// main.qml
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

Window {
    id: mainWindow
    visible: true
    width: 400
    height: 250
    title: "信号与槽示例"
    
    // 创建 C++ 对象实例
    property var notifier: Notifier {}
    
    Column {
        anchors.centerIn: parent
        spacing: 20
        width: parent.width * 0.8
        
        TextField {
            id: titleField
            placeholderText: "通知标题"
        }
        
        TextField {
            id: contentField
            placeholderText: "通知内容"
        }
        
        Button {
            text: "发送通知"
            onClicked: notifier.sendNotification(titleField.text, contentField.text)
        }
        
        Text {
            id: messageText
            text: "等待通知..."
            color: "blue"
            font.pointSize: 14
        }
    }
    
    // 连接信号和槽
    Connections {
        target: notifier
        onMessageSent: {
            messageText.text = message
        }
    }
}

四、QML 与 C++ 数据模型交互

1. C++ 实现数据模型
// personmodel.h
#ifndef PERSONMODEL_H
#define PERSONMODEL_H

#include <QAbstractListModel>
#include "person.h"

class PersonModel : public QAbstractListModel
{
    Q_OBJECT
public:
    enum PersonRoles {
        NameRole = Qt::UserRole + 1,
        AgeRole
    };
    
    explicit PersonModel(QObject *parent = nullptr);
    
    // 实现抽象方法
    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
    QHash<int, QByteArray> roleNames() const override;
    
    // 添加人员
    Q_INVOKABLE void addPerson(const QString &name, int age);
    
private:
    QList<Person*> m_persons;
};

#endif // PERSONMODEL_H

// personmodel.cpp
#include "personmodel.h"

PersonModel::PersonModel(QObject *parent) : QAbstractListModel(parent)
{
}

int PersonModel::rowCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent);
    return m_persons.size();
}

QVariant PersonModel::data(const QModelIndex &index, int role) const
{
    if (index.row() < 0 || index.row() >= m_persons.size())
        return QVariant();
        
    Person *person = m_persons.at(index.row());
    if (role == NameRole)
        return person->name();
    else if (role == AgeRole)
        return person->age();
        
    return QVariant();
}

QHash<int, QByteArray> PersonModel::roleNames() const
{
    QHash<int, QByteArray> roles;
    roles[NameRole] = "name";
    roles[AgeRole] = "age";
    return roles;
}

void PersonModel::addPerson(const QString &name, int age)
{
    beginInsertRows(QModelIndex(), rowCount(), rowCount());
    Person *person = new Person(this);
    person->setName(name);
    person->setAge(age);
    m_persons.append(person);
    endInsertRows();
}
2. 在 QML 中使用 C++ 数据模型
// main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "personmodel.h"

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);
    
    // 创建数据模型
    PersonModel personModel;
    personModel.addPerson("张三", 30);
    personModel.addPerson("李四", 25);
    personModel.addPerson("王五", 40);
    
    // 注册类型
    qmlRegisterType<Person>("com.example", 1, 0, "Person");
    
    // 将模型暴露给 QML
    QQmlApplicationEngine engine;
    engine.rootContext()->setContextProperty("personModel", &personModel);
    
    // 加载 QML
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    
    return app.exec();
}
// main.qml
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import com.example 1.0

Window {
    id: mainWindow
    visible: true
    width: 400
    height: 300
    title: "C++ 数据模型示例"
    
    Column {
        anchors.fill: parent
        spacing: 10
        padding: 10
        
        // 显示数据模型
        ListView {
            id: personList
            anchors {
                top: parent.top
                left: parent.left
                right: parent.right
                bottom: addButton.top
                bottomMargin: 10
            }
            model: personModel
            delegate: Item {
                width: parent.width
                height: 40
                
                Rectangle {
                    anchors.fill: parent
                    color: index % 2 === 0 ? "lightgray" : "white"
                    
                    Text {
                        text: "姓名: " + name + ", 年龄: " + age
                        anchors.verticalCenter: parent.verticalCenter
                        anchors.left: parent.left
                        anchors.leftMargin: 10
                    }
                }
            }
        }
        
        // 添加新人员的控件
        Row {
            id: addButton
            anchors {
                left: parent.left
                right: parent.right
                bottom: parent.bottom
            }
            spacing: 10
            
            TextField {
                id: nameField
                placeholderText: "姓名"
                width: 120
            }
            
            TextField {
                id: ageField
                placeholderText: "年龄"
                width: 60
                validator: IntValidator { bottom: 0; top: 150 }
            }
            
            Button {
                text: "添加"
                onClicked: {
                    personModel.addPerson(nameField.text, parseInt(ageField.text))
                    nameField.text = ""
                    ageField.text = ""
                }
            }
        }
    }
}

五、总结

QML 与 C++ 混合编程提供了强大的开发能力:

  1. 对象暴露:通过 QQmlContext::setContextProperty() 将 C++ 对象暴露给 QML。
  2. 类型注册:使用 qmlRegisterType() 注册 C++ 类,使其可以在 QML 中实例化。
  3. 属性系统:通过 Q_PROPERTY 宏实现 C++ 对象属性在 QML 中的双向绑定。
  4. 函数调用:使用 Q_INVOKABLE 宏或信号槽机制实现 QML 与 C++ 的互相调用。
  5. 数据模型:通过继承 QAbstractListModel 实现 C++ 数据模型,并在 QML 中使用。

这种混合编程模式让开发者能够充分发挥 QML 的 UI 设计优势和 C++ 的性能优势,同时保持代码的模块化和可维护性,是开发复杂 Qt 应用的理想选择。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程与实战

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值