界面架构 - MVVM (Qt)

MVVM

MVVM(Model-View-ViewModel)架构是一种旨在进一步分离界面和业务逻辑的设计模式,特别适合支持数据绑定的 UI 框架,如 Qt Quick(QML)。在 MVVM 架构中,主要包含以下三个部分:

  • Model(模型)

    与其他架构类似,Model 层负责数据存储和业务逻辑处理。在本例中,Model 的逻辑非常简单,就是计算字符串的长度。通常我们可以把这部分逻辑直接集成到 ViewModel 中,或者在更复杂的项目中将其独立出来。

  • View(视图)

    View 层负责界面展示和用户交互,通常只关心如何展示数据。通过数据绑定,View 可以自动响应 ViewModel 中数据的变化,而不需要手动更新界面。在 Qt Quick 中,View 通常由 QML 文件构成。

  • ViewModel(视图模型)

    ViewModel 层充当了 Model 与 View 之间的中介,它暴露 View 所需的数据和操作(通常以属性和方法的形式),并负责处理用户的交互逻辑。通过 Q_PROPERTY 与信号机制,ViewModel 可以将 Model 的数据转换为 View 可直接绑定的形式,从而实现自动同步更新。

MVVM 的主要特点

  • 数据绑定

    ViewModel 的属性与 View 通过数据绑定建立联系,当 ViewModel 中的数据发生变化时,View 会自动更新显示,减少了手动更新 UI 的代码。

  • 彻底解耦

    ViewModel 不依赖于 View 的具体实现,降低了耦合度,使得业务逻辑更容易单独测试和维护。

  • 增强的可测试性

    由于 ViewModel 是纯 C++ 类(或 QML 中的 JavaScript 对象),它可以独立于 UI 环境进行单元测试。

示例

示例功能

构建一个简单的应用,界面包含:

  • QLineEdit:供用户输入字符串;
  • QPushButton:当用户点击按钮时,计算 QLineEdit 中文本的字符数;
  • QLabel:显示计算后的字符数。

我们将使用 Qt Quick(QML)构建 View,并使用 C++ 实现 ViewModel 类,将业务逻辑(计算字符串长度)封装在 ViewModel 中。

示例代码

ViewModel 类(C++)

定义一个 ViewModel 类,包含两个属性:

  • inputText:用户输入的文本,支持读写;

  • result:计算结果,只读属性。当用户点击按钮时,调用 ViewModel 的槽函数计算输入文本长度,并更新 result 属性。

cpp 复制代码
// ViewModel.h
#ifndef VIEWMODEL_H
#define VIEWMODEL_H

#include <QObject>
#include <QString>

class ViewModel : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString inputText READ inputText WRITE setInputText NOTIFY inputTextChanged)
    Q_PROPERTY(QString result READ result NOTIFY resultChanged)

public:
    explicit ViewModel(QObject *parent = nullptr) : QObject(parent) {}

    QString inputText() const { return m_inputText; }
    void setInputText(const QString &text) {
        if (m_inputText != text) {
            m_inputText = text;
            emit inputTextChanged();
        }
    }

    QString result() const { return m_result; }

public slots:
    // 当按钮点击时调用此槽,计算输入文本长度
    void calculateLength() {
        int len = m_inputText.length();
        m_result = QString("长度:%1").arg(len);
        emit resultChanged();
    }

signals:
    void inputTextChanged();
    void resultChanged();

private:
    QString m_inputText;
    QString m_result;
};

#endif // VIEWMODEL_H

主函数入口(main.cpp)

在 main 函数中,我们创建 QGuiApplication 和 QQmlApplicationEngine,并将 ViewModel 实例通过上下文属性传递给 QML 层,供界面绑定和调用。

cpp 复制代码
// main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "ViewModel.h"

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

    QQmlApplicationEngine engine;

    // 创建 ViewModel 实例
    ViewModel viewModel;
    // 将 ViewModel 注册到 QML 上下文中,名字为 "viewModel"
    engine.rootContext()->setContextProperty("viewModel", &viewModel);

    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;

    return app.exec();
}

QML 文件(main.qml)

qml 复制代码
// main.qml
import QtQuick 2.12
import QtQuick.Controls 2.12

ApplicationWindow {
    visible: true
    width: 300
    height: 200
    title: "MVVM 示例"

    Column {
        spacing: 10
        anchors.centerIn: parent

        TextField {
            id: inputField
            placeholderText: "请输入文本"
            text: viewModel.inputText
            onTextChanged: viewModel.inputText = text
        }

        Button {
            text: "计算长度"
            onClicked: viewModel.calculateLength()
        }

        Text {
            text: viewModel.result
            font.pointSize: 16
        }
    }
}

在 QML 中,我们构建一个简单的界面,包含一个 TextField、一个 Button 和一个 Text。

TextField 的文本与 viewModel.inputText 绑定;

Button 的点击事件调用 viewModel.calculateLength();

Text 的显示内容绑定到 viewModel.result。

总结

在上述 MVVM 示例中:

  • ViewModel 封装了业务逻辑(计算字符串长度),并通过 Q_PROPERTY 暴露输入文本(inputText)和计算结果(result)。当用户操作界面时,ViewModel 中的数据会更新,并通过信号通知 QML 自动刷新界面。

  • View(QML 部分) 仅负责界面展示和用户交互,通过数据绑定与 ViewModel 建立联系。这样 View 完全不关心具体的业务逻辑,降低了耦合度。

  • Model 部分在这个简单示例中直接内嵌在 ViewModel 中,但在更复杂的项目中,可以将业务逻辑和数据存储进一步独立出来。

这种架构充分利用了 QML 的数据绑定特性,使得界面和业务逻辑解耦,提升了代码的可维护性和可测试性。

相关推荐
用户8524950718412 分钟前
Next.js + Redis:我的笔记,终于有了记忆
架构
Maxkim18 分钟前
DeepSeek Harness 源码深度分析:像 VS Code 一样插件化的 Agent 框架
前端·架构
用户8524950718425 分钟前
Next.js App Router:一篇笔记的服务端奇幻漂流
架构
晴天161 小时前
Cordis 框架代码核心解析:一个可逆插件系统的实现-Day18
人工智能·ai·架构
小新讲网安2 小时前
HTTP请求走私攻击实战:CL.TE与TE.CL绕过前端服务器全解析
服务器·前端·网络·web安全·http·架构·漏洞
艾莉丝努力练剑2 小时前
【QT:解决问题】Qt5Core.dll:无法定位程序输入点
java·开发语言·qt·学习·面试
阿图灵15 小时前
Agentic AI 架构入门(九):Agent 通信协议全景——ACP/A2A/AG-UI/MCP
人工智能·ui·架构·ai agent·智能体·mcp·agentic ai
这个DBA有点耶17 小时前
3000万个应用共享一套数据库:多租户“逻辑表”架构是如何做到的?
数据库·架构·dba
消失的旧时光-194317 小时前
第一篇:Ktor Client 到底是什么?从 Retrofit 迁移理解 Ktor 网络请求架构
网络·架构·retrofit·ktor·dsl
zlinear数据采集卡17 小时前
D223的PWM电机控制:6路独立脉冲+加减速算法深度解析
arm开发·stm32·嵌入式硬件·算法·fpga开发·架构