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 环境中的一个普通对象。
- 基本步骤
为了实现 C++ 和 QML 的数据通信,基本的步骤如下:
定义 C++ 类:首先,我们定义一个包含数据成员、信号、槽和
Q_PROPERTY
的 C++ 类。注册 C++ 类到 QML:使用
qmlRegisterType
将 C++ 类注册到 QML。在 QML 中使用 C++ 类:在 QML 中创建和使用 C++ 类的实例,并通过信号与槽机制进行数据通信。
示例实现
下面我们通过一个简单的计数器示例,演示如何使用 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++ 可以通过 QQmlApplicationEngine
或 QQmlComponent
来加载 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")
查找id
为label
的 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++ 可以通过
QQmlApplicationEngine
或findChild()
等方法获取 QML 中的对象并与之交互。 - 信号和槽:C++ 可以连接 QML 中的信号与槽,从而实现 C++ 与 QML 之间的交互。
- 属性访问:C++ 可以访问并修改 QML 对象的属性。
3.实时数据传递
Q_PROPERTY
和 信号槽: 用于简单的实时数据传递,尤其是数据较少或更新频率较低的场景。QQmlContext::setContextProperty
: 可以将 C++ 对象暴露给 QML,QML 可以通过信号与槽获取实时数据。QTimer
: 用于定期更新数据,可以结合其他方式实现实时数据传递。网络通信(
QWebSocket
,QNetworkAccessManager
): 用于跨进程或跨机器的实时数据通信。基本上就是信号和槽结合定时器实现实时数据传递, 网络通讯的特殊场景提供简单示例仅供参考
(1) 使用 QTimer
和信号和槽进行实时更新
参考开篇第一个串口示例即可;
(2) 使用 QWebSocket
或 QNetworkAccessManager
实现实时数据通信
- 通过 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();
}
}
}
总结
Q_PROPERTY
和 信号槽: 用于简单的实时数据传递,尤其是数据较少或更新频率较低的场景。QQmlContext::setContextProperty
: 可以将 C++ 对象暴露给 QML,QML 可以通过信号与槽获取实时数据。QTimer
: 用于定期更新数据,可以结合其他方式实现实时数据传递。- 网络通信(
QWebSocket
,QNetworkAccessManager
): 用于跨进程或跨机器的实时数据通信。