当前位置: 首页 > news >正文

《使用Qt Quick从零构建AI螺丝瑕疵检测系统》——4. 前后端联动:打通QML与C++的任督二脉

目录

  • 一、概述
    • 1.1 背景介绍:UI与逻辑的“隔阂”
    • 1.2 学习目标
    • 1.3 MVVM架构简介
  • 二、C++后端 (ViewModel) 的创建
  • 三、建立连接:从QML调用C++
  • 四、反向通信:从C++更新QML
  • 五、总结与展望

一、概述

1.1 背景介绍:UI与逻辑的“隔阂”

在前面的文章中,我们已经分别构建了C++后端的逻辑基础(第2篇)和QML前端的UI骨架(第3篇)。目前,它们就像一座大桥的两端,虽然各自都很坚固,但中间却是断开的——QML界面上的按钮还无法触发C++中的任何操作,C++中的数据也无法呈现在界面上。

本篇文章的核心任务,就是架设这座桥梁,打通QML与C++之间的“任督二脉”。我们将学习如何将一个C++对象“注入”到QML环境中,从而实现双向通信:既能从QML调用C++的函数,也能让C++在后台任务完成后,通过信号主动更新QML界面。

1.2 学习目标

通过本篇的学习,读者将能够:

  1. 理解并实践前后端分离的MVVM(Model-View-ViewModel)架构思想。
  2. 创建一个C++后端类(Backend),作为连接前端与业务逻辑的桥梁。
  3. 掌握在QML中调用C++方法的关键技术(Q_INVOKABLE)。
  4. 掌握C++通过信号(signals)更新QML界面的核心机制。

1.3 MVVM架构简介

在开始编码前,有必要了解我们即将采用的软件架构——MVVM

  • Model(模型): 负责存储和管理应用程序的数据。在我们的项目中,可以是一个代表螺丝信息的C++类。
  • View(视图): 用户看到的界面。在我们的项目中,就是Main.qml以及其他QML文件。
  • ViewModel(视图模型): 作为一个“中间人”或“桥梁”,它连接着Model和View。它负责处理View的交互请求(如按钮点击),调用Model执行业务逻辑,并将Model中的数据显示到View上。

想象一下你在餐厅吃饭:

  • 你 (View / 视图)

    • 你就是顾客
    • 你只关心菜单(UI)长什么样,以及怎么点菜(操作)
    • 不需要知道后厨是怎么运作的。
  • 服务员 (ViewModel / 视图模型)

    • 他是连接你和后厨的中间人
    • 他把你点的菜(“宫保鸡丁”)传递给后厨。
    • 他把后厨做好的菜(一盘宫保鸡丁)端回给你。
  • 后厨 (Model / 模型)

    • 后厨拥有食材(数据)厨艺(业务逻辑)
    • 他们只负责根据订单做菜
    • 他们不需要知道你是谁,坐在哪。

一句话总结:

服务员(ViewModel)让你(View)和后厨(Model)可以各干各的,互不干扰,这就是MVVM架构的核心思想——解耦

在本章中,我们将创建的Backend类,正是扮演着ViewModel这一至关重要的角色。

二、C++后端 (ViewModel) 的创建

我们将创建一个Backend类,它将成为所有业务逻辑的入口。

【例4-1】 创建Backend类。

1. 创建项目与类文件

  • 延续上一篇修改后的ScrewDetector项目。
  • 在Qt Creator中,右键点击项目名称,选择添加新文件... -> C++ -> C++ Class
    • 类名: Backend
    • 基类: 选择 QObject

2. 编写代码 (backend.h)

#ifndef BACKEND_H
#define BACKEND_H#include <QObject>
#include <QString>class Backend : public QObject
{Q_OBJECT // 必须添加,以支持信号槽和QML交互
public:explicit Backend(QObject *parent = nullptr);// 使用 Q_INVOKABLE 宏,使这个普通的C++成员函数可以被QML调用Q_INVOKABLE void startScan();signals:// 定义一个信号,用于从C++向QML传递状态更新信息void statusMessageChanged(const QString &message);
};#endif // BACKEND_H

3. 编写代码 (backend.cpp)

#include "backend.h"
#include <QDebug>
#include <QTimer> // 用于模拟耗时操作Backend::Backend(QObject *parent) : QObject(parent)
{
}void Backend::startScan()
{qDebug() << "C++: startScan() method called from QML.";emit statusMessageChanged("正在准备扫描设备...");// 使用QTimer::singleShot模拟一个2秒后的异步操作QTimer::singleShot(2000, this, [this]() {qDebug() << "C++: Simulated scan finished.";// 任务完成后,再次发射信号更新状态emit statusMessageChanged("扫描完成!");});
}

关键代码分析:
(1) Backend: 它继承自QObject并包含Q_OBJECT宏,这是它能与QML进行深度交互的基础。
(2) Q_INVOKABLE: 这是一个Qt宏,是打通“从QML到C++”方向通信的最简单方式。任何被标记为Q_INVOKABLE的公有成员函数,都可以像JavaScript函数一样在QML代码中被直接调用。
(3) signals: statusMessageChanged信号是打通“从C++到QML”方向通信的关键。当后端发生某个事件(如此处的扫描状态改变),就发射这个信号,QML可以监听并做出响应。

三、建立连接:从QML调用C++

现在,我们需要将创建的Backend对象实例“告知”QML引擎,让QML能够找到并调用它。

【核心概念:上下文属性(Context Property)】

QML引擎维护着一个根上下文(Root Context),可以把它理解为QML世界的“全局作用域”。通过将一个C++对象设置为根上下文的属性,这个对象就成了一个在所有QML文件中都可以直接访问的“全局变量”。

【例4-2】 注册Backend对象并从QML调用。

1. 编写代码 (main.cpp)
这是连接C++和QML世界最关键的一步。

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QIcon>
#include <QQmlContext>   // 1. 包含上下文头文件
#include "backend.h"    // 2. 包含我们自己的Backend头文件int main(int argc, char *argv[])
{QGuiApplication app(argc, argv);app.setWindowIcon(QIcon(":/icons/appicon.png"));QQmlApplicationEngine engine;// 3. 创建Backend的实例Backend backend;// 4. 将C++对象注册为QML的上下文属性//    第一个参数是QML中使用的名字,我们将其命名为"backend"//    第二个参数是C++对象的地址engine.rootContext()->setContextProperty("backend", &backend);// ... (后续代码保持不变) ...return app.exec();
}

2. 编写代码 (Main.qml)
现在,在Main.qml中,可以直接通过名字backend来访问C++对象了。

import QtQuick
import QtQuick.Controls
import QtQuick.LayoutsWindow {// ... (属性保持不变) ...ColumnLayout {// ... (布局保持不变) ...// --- 1. 结果展示区 (修改) ---// 我们用一个Label来显示状态信息Frame {id: resultFrameLayout.fillWidth: trueLayout.preferredHeight: 150background: Rectangle { color: "#2c3e50" }Label { // 使用Label代替Text,样式更统一id: statusLabeltext: "准备就绪"color: "white"font.pixelSize: 18anchors.centerIn: parent}}// --- 2. 控制区 (修改) ---RowLayout {// ... (布局保持不变) ...Button {id: startButtontext: "开始检测"Layout.preferredWidth: 120Layout.preferredHeight: 40// 关键:按钮点击时,调用C++ backend对象的startScan方法onClicked: {backend.startScan();}}// ... (stopButton保持不变) ...}}
}

3. 运行结果
运行程序,点击“开始检测”按钮。会看到应用程序输出窗口依次输出如下:

C++: startScan() method called from QML.
C++: Simulated scan finished.

关键代码分析:
(1) setContextProperty("backend", &backend): 这行代码是整座“桥梁”的基石。它告诉QML引擎:“现在有一个全局对象,它的名字叫backend,它对应的是C++中的这个backend实例。”
(2) backend.startScan(): 在QML中,调用一个C++的Q_INVOKABLE方法,语法与调用JavaScript函数完全相同。

四、反向通信:从C++更新QML

上面的例子已经展示了QML操作C++,本节讲解如何在QML中监听C++发来的信号——Connections组件。

【核心概念:结构化的信号监听】

Connections是一个QML组件,专门用于监听指定目标(target)的所有信号。

【例4-3】 使用Connections组件响应信号。

1. 编写代码 (Main.qml)
我们修改Main.qml,将信号处理逻辑从startScan的调用处,移到一个集中的Connections块中。这使得代码更清晰。

import QtQuick
import QtQuick.Controls
import QtQuick.LayoutsWindow {id: rootWindow// ... (属性保持不变) ...// --- 关键:添加Connections组件 ---Connections {target: backend // 监听我们在main.cpp中注册的backend对象// 当C++的backend对象发射statusMessageChanged信号时,这个函数会被自动调用// 函数名规则: on + 信号名(首字母大写)// 信号的参数会按顺序成为JS函数的参数function onStatusMessageChanged(message) {statusLabel.text = message;}}ColumnLayout {// ... (所有布局和组件与上一个例子完全相同) ...}
}

2. 运行结果
运行程序。单击“开始检测”按钮后,界面上文本框显示“正在准备扫描设备…”,等待两秒后,界面上显示“扫描完成”。
在这里插入图片描述
在这里插入图片描述
关键代码分析:
(1) Connections: 这是一个非可视化的组件,它的作用是“订阅”某个QObject对象(通过target属性指定)的所有信号。
(2) function onStatusMessageChanged(message): 这是在Connections内部定义的信号处理器。当target(即backend)发射statusMessageChanged信号时,这个JavaScript函数就会被执行。QML会自动将C++信号的参数(const QString &message)映射为JavaScript函数的参数(message)。这种写法让所有与backend的通信逻辑都集中在一个地方,极大地提高了代码的可读性和可维护性。

五、总结与展望

在本篇文章中,我们成功地架设了连接QML前端与C++后端的桥梁。我们掌握了:

  • 使用上下文属性将C++对象暴露给QML。
  • 通过**Q_INVOKABLE**宏,实现了从QML对C++方法的直接调用。
  • 通过信号与槽以及**Connections组件**,实现了从C++对QML界面的异步、解耦更新。

至此,我们的应用程序已经拥有了一个完整的、双向通信的现代化架构。前后端各司其职,并通过清晰的接口进行交互。

现在,这座桥梁已经准备好运输真正的“货物”了。在下一篇文章【《使用Qt Quick从零构建AI螺丝瑕疵检测系统》——5. 集成OpenCV:让程序拥有“视力”】中,我们将开始集成强大的OpenCV库,并通过这座桥梁,将处理后的图像数据显示在QML界面上。

http://www.xdnf.cn/news/1176895.html

相关文章:

  • 【基础】go基础学习笔记
  • 极客大挑战2019-HTTP
  • 基于Odoo的微信小程序全栈开发探索分析
  • 探索复杂列表开发:从基础到高级的全面指南
  • SSE与Websocket有什么区别?
  • 如何在 conda 中删除环境
  • rust-结构体使用示例
  • Elasticsearch 的聚合(Aggregations)操作详解
  • 使用phpstudy极简快速安装mysql
  • Java 大视界 -- Java 大数据在智能家居能源管理与节能优化中的深度应用(361)
  • API安全监测工具:数字经济的免疫哨兵
  • 五、Vue项目开发流程
  • LeetCode 2563.统计公平数对的数目
  • Effective Python 第16条:用get处理字典缺失键,避免in与KeyError的陷阱
  • 【低空经济之无人集群】
  • runc源码解读(一)——runc create
  • C++右值引用与移动语义详解
  • QML 模型
  • git更新内核补丁完整指南
  • Android LiveData 全面解析:原理、使用与最佳实践
  • 【智能协同云图库】智能协同云图库第六弹:空间模块开发
  • 飞腾D3000麒麟信安系统下配置intel I210 MAC
  • Spring AI - 函数调用演示:AI算术运算助手
  • 复盘—MySQL触发器实现监听数据表值的变化,对其他数据表做更新
  • act_hi_taskinst表历史任务记录不同步,无数据
  • 边缘智能体:轻量化部署与离线运行
  • 三维手眼标定
  • 深度分析Java内存结构
  • Hexo - 免费搭建个人博客01 - 安装软件工具
  • IAR Embedded Workbench for ARM 8.1 安装教程