qml界面和c++后端程序交互

发布于:2025-02-19 ⋅ 阅读:(19) ⋅ 点赞:(0)

qml界面和c++后端程序交互

1.C++ 数据暴露给qml进行交互

注意事项:

要想c++类传递到qml,必须是QObject类型的;如果是QWidget或者其他界面类型的c++类则不生效

(1)setContextProperty方式(在c++端实例化对象)

serialportmanager.h

#ifndef SERIALPORTMANAGER_H
#define SERIALPORTMANAGER_H

#include <QObject>
#include <QStringList>
#include <QSerialPortInfo>
#include <QTimer>
#include <QDebug>

class SerialPortManager : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QStringList portList READ portList NOTIFY portListChanged)

public:
    explicit SerialPortManager(QObject *parent = nullptr);
    QStringList portList() const;

signals:
    void portListChanged();  // 当串口列表更新时发送信号

public slots:
    void updatePortList();   // 更新串口列表

private:
    QStringList m_portList;  // 存储当前串口列表
    QTimer *m_timer;         // 用于定时更新串口列表
    bool m_isFirstUpdate = true;    // 标志是否是第一次获取串口列表
};

#endif // SERIALPORTMANAGER_H

重点:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

serialportmanager.cpp

#include "serialportmanager.h"
#include <QSerialPortInfo>
#include <QTimer>

SerialPortManager::SerialPortManager(QObject *parent)
    : QObject(parent), m_timer(new QTimer(this)), m_isFirstUpdate(true)
{
    // 初始时立即获取可用串口列表,并发送信号
    updatePortList();

    // 设置定时器每秒更新一次串口列表
    connect(m_timer, &QTimer::timeout, this, &SerialPortManager::updatePortList);
    m_timer->start(1000);  // 每1000毫秒更新一次串口列表
}

QStringList SerialPortManager::portList() const {
    return m_portList;
}

void SerialPortManager::updatePortList() {
    QStringList ports;

    // 获取所有可用的串口并更新到 ports 列表中
    foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) {
        ports.append(info.portName());
    }

    // 对串口列表进行排序,确保是从小到大的顺序
    ports.sort();

    // 第一次获取串口列表时,直接发出信号
    if (m_isFirstUpdate) {
        m_portList = ports;
        emit portListChanged();  // 发出串口列表已更新的信号
        qDebug() << "First update, signal emitted.";
        m_isFirstUpdate = false; // 标记为非第一次更新
    }
    else {
        // 如果串口列表发生变化,则更新并发出信号
        if (ports != m_portList) {
            m_portList = ports;
            emit portListChanged();  // 发出串口列表已更新的信号
            qDebug() << "List updated, signal emitted.";
        }
    }
}

在这里插入图片描述

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "SerialPortManager.h"
int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    const QUrl url(QStringLiteral("qrc:/main.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                     &app, [url](QObject *obj, const QUrl &objUrl) {
        if (!obj && url == objUrl)
            QCoreApplication::exit(-1);
    }, Qt::QueuedConnection);



    // 创建 SerialPortManager 实例
    SerialPortManager serialPortManager;
    // 将 SerialPortManager 实例暴露给 QML
    engine.rootContext()->setContextProperty("serialPortManager", &serialPortManager);

    // 加载 QML 文件
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));


    return app.exec();
}

在这里插入图片描述

main.qml

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
//import QtQuick.Dialogs 1.12
ApplicationWindow {
    visible: true
    width: 640
    height: 480
    title: "Serial Port List"

    ListView {
        anchors.fill: parent
        model: ListModel {
            // 绑定到 SerialPortManager 的 portList
            ListElement { portName: "COM1" }
            ListElement { portName: "COM2" }
        }

        delegate: Item {
            width: parent.width
            height: 50

            Text {
                anchors.centerIn: parent
                text: portName
            }
        }

        // 定义一个更新串口列表并刷新模型的函数
        function updateSerialPortList() {
//            console.log("portListChanged 信号已触发");

            // 清空现有模型
            model.clear();
//            console.log("模型已清空");

            // 打印获取到的可用串口列表
            console.log("获取到的串口列表: " + serialPortManager.portList.join(", "));

            // 更新模型,填充新的串口列表
            for (var i = 0; i < serialPortManager.portList.length; i++) {
                console.log("串口名称: " + serialPortManager.portList[i]);
                model.append({ portName: serialPortManager.portList[i] });
            }
//            console.log("模型更新完毕");
        }
        Component.onCompleted: {
//            console.log("Component.onCompleted: 组件已完成加载");

            // 检查 serialPortManager 是否已初始化
            if (serialPortManager && serialPortManager.portList) {
//                console.log("serialPortManager 已初始化,开始处理 portList");

                // 连接 portListChanged 信号到独立的更新函数
                serialPortManager.portListChanged.connect(updateSerialPortList);

                // 主动调用 C++ 端获取串口列表
//                console.log("调用 C++ 端 updatePortList 获取串口列表");
                updateSerialPortList();
            } else {
                // 如果 C++ 端尚未初始化,打印提示
                console.log("后端程序尚未初始化");
            }
        }


    }
}

在这里插入图片描述

现象:

初始状态:

在这里插入图片描述

串口变化时:

在这里插入图片描述

总结:

c++注册并实例化对象;  发送信号

qml界面connect信号并实现槽函数响应;


注册后的c++对象,qml可以进行访问

(2)使用qmlRegisterType方式

1. qmlRegisterType 概述

qmlRegisterType 是 Qt 的一项功能,允许我们将 C++ 类注册到 QML 中。通过这种方式,QML 可以实例化 C++ 类,并与其交互。此机制非常适用于将 C++ 的复杂逻辑暴露给 QML,以便在 UI 层中使用。

qmlRegisterType 中,我们可以指定 C++ 类的名称、QML 类型、版本以及需要注册的元类型。这使得 C++ 类能够成为 QML 环境中的一个普通对象。

  1. 基本步骤

为了实现 C++ 和 QML 的数据通信,基本的步骤如下:

  1. 定义 C++ 类:首先,我们定义一个包含数据成员、信号、槽和 Q_PROPERTY 的 C++ 类。

  2. 注册 C++ 类到 QML:使用 qmlRegisterType 将 C++ 类注册到 QML。

  3. 在 QML 中使用 C++ 类:在 QML 中创建和使用 C++ 类的实例,并通过信号与槽机制进行数据通信。

  4. 示例实现

下面我们通过一个简单的计数器示例,演示如何使用 qmlRegisterType 实现 C++ 和 QML 的数据通信。

3.1 C++ 类定义

首先,我们定义一个 C++ 类,包含一个整数属性和一个信号,用来更新计数器的值。

cppCopy Code// counter.h
#ifndef COUNTER_H
#define COUNTER_H

#include <QObject>

class Counter : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int count READ count WRITE setCount NOTIFY countChanged)

public:
    explicit Counter(QObject *parent = nullptr);

    int count() const;
    void setCount(int newCount);

signals:
    void countChanged();

private:
    int m_count;
};

#endif // COUNTER_H
cppCopy Code// counter.cpp
#include "counter.h"

Counter::Counter(QObject *parent) : QObject(parent), m_count(0)
{
}

int Counter::count() const
{
    return m_count;
}

void Counter::setCount(int newCount)
{
    if (m_count != newCount) {
        m_count = newCount;
        emit countChanged();
    }
}

3.2 注册 C++ 类到 QML

在应用程序的入口文件中(通常是 main.cpp),我们通过 qmlRegisterType 注册 C++ 类到 QML 环境中。

cppCopy Code#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "counter.h"

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    // 注册 C++ 类到 QML
    qmlRegisterType<Counter>("com.example", 1, 0, "Counter");

    QQmlApplicationEngine engine;

    // 加载 QML 文件
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    
    return app.exec();
}

在这里,qmlRegisterType<Counter>("com.example", 1, 0, "Counter");Counter 类注册到 QML 中,并指定了模块名为 com.example,版本为 1.0,QML 中对应的类型名为 Counter

3.3 在 QML 中使用 C++ 类

接下来,在 QML 中创建一个 Counter 对象,并与其进行交互。我们可以使用 Text 元素来显示 count 属性,并使用按钮来更新计数器的值。

qmlCopy Code// main.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
import com.example 1.0

ApplicationWindow {
    visible: true
    width: 640
    height: 480

    // 创建 Counter 对象
    Counter {
        id: counter
    }

    Column {
        anchors.centerIn: parent

        Text {
            text: "Count: " + counter.count
            font.pixelSize: 32
        }

        Button {
            text: "Increment"
            onClicked: counter.setCount(counter.count + 1)
        }
    }
}

在这里,Counter { id: counter } 创建了一个 Counter 对象,并使用 counter.count 显示当前计数值。点击按钮时,onClicked 触发 counter.setCount(counter.count + 1),更新 count 属性的值。由于我们在 C++ 类中使用了 Q_PROPERTY 和信号机制,当 count 属性变化时,QML 自动更新显示。

3.4 运行效果

当你运行这个应用时,QML 界面会显示一个初始计数值 0,并有一个按钮“Increment”。每点击一次按钮,计数值会增加并显示在界面上。C++ 和 QML 之间通过信号与属性的绑定实现了实时的数据通信。

2.qml数据暴露给c++

2.1. C++ 获取 QML 中的对象

C++ 可以通过 QQmlApplicationEngineQQmlComponent 来加载 QML 文件,并通过 rootObject()findChild() 方法获取 QML 对象。

示例代码:

假设我们有一个 QML 文件 Main.qml,其中有一个 Text 对象,我们希望从 C++ 代码中访问并修改它。

// Main.qml
import QtQuick 2.15
import QtQuick.Controls 2.15

ApplicationWindow {
    visible: true
    width: 640
    height: 480
    title: "QML to C++ Example"

    Text {
        id: label
        text: "Hello from QML!"
        anchors.centerIn: parent
    }
}

在 C++ 中,我们可以通过以下代码获取并修改这个 Text 对象的 text 属性。

// main.cpp
#include <QCoreApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QQuickItem>

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);

    QQmlApplicationEngine engine;

    // 加载 QML 文件
    engine.load(QUrl(QStringLiteral("qrc:/Main.qml")));

    // 获取根对象
    QObject *rootObject = engine.rootObjects().first();

    // 查找 QML 中的 Text 对象
    QObject *labelObject = rootObject->findChild<QObject*>("label");

    if (labelObject) {
        // 修改 QML 中的 Text 对象的 text 属性
        labelObject->setProperty("text", "Modified by C++!");
    }

    return app.exec();
}

2.2. 步骤分析

  • 加载 QML 文件:通过 engine.load() 加载 QML 文件。
  • 获取根对象rootObjects().first() 获取 QML 文件中定义的根对象(ApplicationWindow)。
  • 查找 QML 对象:通过 findChild<QObject*>("label") 查找 idlabel 的 QML 对象。
  • 修改属性:通过 setProperty 修改 QML 对象的属性,在这里我们修改了 Text 对象的 text 属性。

2.3. 通过信号与槽交互

C++ 也可以连接 QML 中的信号与槽。QML 中的信号可以通过 QObject::connect 方法连接到 C++ 的槽函数。

示例:

假设 QML 中有一个按钮,当按钮被点击时,我们希望 C++ 能够接收到这个信号并进行处理:

// Main.qml
import QtQuick 2.15
import QtQuick.Controls 2.15

ApplicationWindow {
    visible: true
    width: 640
    height: 480
    title: "Signal from QML to C++"

    Button {
        text: "Click Me"
        onClicked: {
            console.log("Button clicked!")
        }
    }
}

在 C++ 中,我们可以连接 QML 中的 onClicked 信号:

// main.cpp
#include <QCoreApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QQuickItem>

class MyObject : public QObject {
    Q_OBJECT
public:
    MyObject() {}

public slots:
    void onButtonClicked() {
        qDebug() << "Button clicked in QML!";
    }
};

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);

    QQmlApplicationEngine engine;

    // 创建 C++ 对象
    MyObject myObject;

    // 加载 QML 文件
    engine.load(QUrl(QStringLiteral("qrc:/Main.qml")));

    // 获取根对象
    QObject *rootObject = engine.rootObjects().first();

    // 查找 QML 中的 Button 对象
    QObject *buttonObject = rootObject->findChild<QObject*>("button");

    if (buttonObject) {
        // 连接 QML 信号到 C++ 槽
        QObject::connect(buttonObject, SIGNAL(clicked()), &myObject, SLOT(onButtonClicked()));
    }

    return app.exec();
}

2.4. 总结

  • 暴露 QML 对象给 C++:虽然 QML 本身不提供类似暴露 C++ 对象的机制,但 C++ 可以通过 QQmlApplicationEnginefindChild() 等方法获取 QML 中的对象并与之交互。
  • 信号和槽:C++ 可以连接 QML 中的信号与槽,从而实现 C++ 与 QML 之间的交互。
  • 属性访问:C++ 可以访问并修改 QML 对象的属性。

3.实时数据传递

  1. Q_PROPERTY 和 信号槽: 用于简单的实时数据传递,尤其是数据较少或更新频率较低的场景。

  2. QQmlContext::setContextProperty 可以将 C++ 对象暴露给 QML,QML 可以通过信号与槽获取实时数据。

  3. QTimer 用于定期更新数据,可以结合其他方式实现实时数据传递。

  4. 网络通信(QWebSocket, QNetworkAccessManager): 用于跨进程或跨机器的实时数据通信。

    基本上就是信号和槽结合定时器实现实时数据传递,
    网络通讯的特殊场景提供简单示例仅供参考
    
(1) 使用 QTimer 和信号和槽进行实时更新

参考开篇第一个串口示例即可;

(2) 使用 QWebSocketQNetworkAccessManager 实现实时数据通信
  • 通过 WebSocket 或网络通信协议可以实现跨进程、跨机器的实时数据传输。

示例(QWebSocket)

C++ 后端:

#include <QWebSocketServer>
#include <QWebSocket>

class WebSocketServer : public QObject {
    Q_OBJECT

public:
    WebSocketServer(QObject *parent = nullptr) : QObject(parent) {
        server = new QWebSocketServer("Real-Time Server", QWebSocketServer::NonSecureMode, this);
        connect(server, &QWebSocketServer::newConnection, this, &WebSocketServer::onNewConnection);
        server->listen(QHostAddress::Any, 12345);
    }

private slots:
    void onNewConnection() {
        QWebSocket *socket = server->nextPendingConnection();
        connect(socket, &QWebSocket::textMessageReceived, this, &WebSocketServer::onMessageReceived);
    }

    void onMessageReceived(const QString &message) {
        qDebug() << "Received message:" << message;
    }

private:
    QWebSocketServer *server;
};

QML 前端(WebSocket)

import QtQuick 2.15
import QtQuick.Controls 2.15
import QtWebSockets 1.2

ApplicationWindow {
    visible: true
    width: 640
    height: 480

    WebSocket {
        id: socket
        url: "ws://localhost:12345"
        onTextMessageReceived: {
            console.log("Received message: " + message);
        }

        Component.onCompleted: {
            socket.open();
        }
    }
}

总结

  1. Q_PROPERTY 和 信号槽: 用于简单的实时数据传递,尤其是数据较少或更新频率较低的场景。
  2. QQmlContext::setContextProperty 可以将 C++ 对象暴露给 QML,QML 可以通过信号与槽获取实时数据。
  3. QTimer 用于定期更新数据,可以结合其他方式实现实时数据传递。
  4. 网络通信(QWebSocket, QNetworkAccessManager): 用于跨进程或跨机器的实时数据通信。

网站公告

今日签到

点亮在社区的每一天
去签到