查看完整版本: [-- “Why Not”使用QML实现一个MVVM框架 --]

QTCN开发网 -> Qt基础编程 -> “Why Not”使用QML实现一个MVVM框架 [打印本页] 登录 -> 注册 -> 回复主题 -> 发表主题

cycloveu 2017-04-12 17:57

“Why Not”使用QML实现一个MVVM框架

一.前言      
      最近几年最热门的技术之一就是前端技术了,各种前端框架,前端标准和前端设计风格层出不穷,而在众多前端框架中具有MVC,MVVM功能的框架成为耀眼新星,比如GitHub关注度很高的Vue.js ,由于是国人作品,其设计风格和文档友好度对国人而言更胜一筹。

二.认识MVVM
1.MVVM的结构
      MVVM是Model-View-ViewModel的简写。其结构如下图:

      View绑定到ViewModel,然后执行一些命令在向它请求一个动作。而反过来,ViewModel跟Model通讯,告诉它更新来响应UI。这样便使得为应用构建UI非常的容易。往一个应用程序上贴一个界面越容易,外观设计师就越容易使用Blend来创建一个漂亮的界面。同时,当UI和功能越来越松耦合的时候,功能的可测试性就越来越强。
2.MVVM的优点
      MVVM模式和MVC模式一样,主要目的是分离视图(View)和模型(Model),有几大优点
      1. 低耦合。视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的"View"上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
      2. 可重用性。你可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑。
      3. 独立开发。开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计,使用Expression Blend可以很容易设计界面并生成xml代码。
      4. 可测试。界面素来是比较难于测试的,而现在测试可以针对ViewModel来写。



3.模块职责
      Model: 负责数据存储和业务逻辑。
      View: 负责展示数据。并将自身的变化同步到ViewModel中
      ViewModel: 对Model的封装,通过一系列属性暴露Model的状态,提供给View进行显示



三.怎么把QML和MVVM结合起来。
      我们知道VIew和ViewModel的双向绑定是MVVM架构的关键。WPF中使用data template、commands、data binding这些微软提供的技术进行数据绑定。那么我们的QT又有什么样的武器呢?没错就是Single/slots.



下面用一个简单的实例来说明怎么实现QML版的MVVM。
1.我们有一个登陆界面,用户输入账号和密码然后点击登陆按钮,并在界面上显示登陆信息。

[attachment=16815]
QML页面代码

  1. import QtQuick 2.7
    import QtQuick.Controls 2.0
    import QtQuick.Layouts 1.0

    ApplicationWindow {
        visible: true
        width: 320
        height: 480
        title: qsTr("MVVM")

        header: ToolBar {
            Text {
                anchors.centerIn: parent
                font.family: "微软雅黑"
                font.pointSize: 16
                color: "lightgreen"
                text: qsTr("QML MVVM")
            }
        }

        Column {
            anchors.centerIn: parent
            spacing: 5

            TextArea {
                id: nameTextArea
                anchors.horizontalCenter: parent.horizontalCenter
                placeholderText: qsTr("username")
                text: mainViewModel.name
                onTextChanged: mainViewModel.name = text
            }

            TextArea {
                id: passwordTextArea
                anchors.horizontalCenter: parent.horizontalCenter
                placeholderText: qsTr("password")
                text: mainViewModel.password
                onTextChanged: mainViewModel.password = text
            }

            Button {
                text: "Login"
                anchors.right: parent.right
                anchors.rightMargin: 5
                onClicked: mainViewModel.loginButtonClicked()
            }

            Text {
                id:stateText
                anchors.horizontalCenter: parent.horizontalCenter
                text: mainViewModel.state
            }
        }
    }

从代码可以看出:QML中控件的值和ViewModel中的属性主要通过这两句实现双向绑定的:
           text: mainViewModel.name      onTextChanged: mainViewModel.name = text


a.根据页面中的元素去设计对应的ViewModel层需要的那些属性:
name : QString
password : QString
state : QString
loginButtonClicked : signal
  1. class MainViewModel : public QObject
    {
        Q_OBJECT

        Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
        Q_PROPERTY(QString password READ password WRITE setPassword NOTIFY passwordChanged)
        Q_PROPERTY(QString state READ state WRITE setState NOTIFY stateChanged)

    public:
        explicit MainViewModel(QObject *parent = 0);

        MainModel* model();
        void setModel(MainModel *model);

        QString name();
        void setName(const QString &name);

        QString password();
        void setPassword(const QString &password);

        QString state();
        void setState(const QString &state);

        Q_INVOKABLE void loginButtonClicked();

    signals:
        void nameChanged(const QString &);
        void passwordChanged(const QString &);
        void stateChanged(const QString &);

    private:
        QString m_name;
        QString m_password;
        QString m_state;

        MainModel *m_model;
    };
  1. MainViewModel::MainViewModel(QObject *parent) : QObject(parent),
        m_model(NULL)
    {

    }

    MainModel *MainViewModel::model()
    {
        return m_model;
    }

    void MainViewModel::setModel(MainModel *model)
    {
        m_model = model;

        if (m_model)
        {
            connect(this, &MainViewModel::nameChanged, m_model, &MainModel::setName);
            connect(this, &MainViewModel::passwordChanged, m_model, &MainModel::setPassword);
        }
    }

    QString MainViewModel::name()
    {
        return m_name;
    }

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

    QString MainViewModel::password()
    {
        return m_password;
    }

    void MainViewModel::setPassword(const QString &password)
    {
        m_password = password;
        emit passwordChanged(m_password);
    }

    QString MainViewModel::state()
    {
        return m_state;
    }

    void MainViewModel::setState(const QString &state)
    {
        m_state = state;
        emit stateChanged(state);
    }

    void MainViewModel::loginButtonClicked()
    {
        if (m_model)
        {
            QString errorCode;
            if (!m_model->login(&errorCode))
            {
                setState("Failed:" + errorCode);
            }
            else
            {
                setState("Successed");
            }
        }
    }

      我们通过在setModel函数中调用connect将ViewModel中属性的改变同步到Model中

b.在ViewModel层的loginButtonClicked函数中调用Model层的Login函数进行逻辑处理。Model层看起来是这样的:
name : QString
password : QString
bool login();
  1. class MainModel : public QObject
    {
        Q_OBJECT
    public:
        explicit MainModel(QObject *parent = 0);

        bool login(QString *error);

        QString name();
        void setName(const QString &name);

        QString password();
        void setPassword(const QString &password);

    private:
        QString m_name;
        QString m_password;
    };


最终登陆效果:
[attachment=16816]

感谢你的阅读。谢谢。
by汏家的诚
2017.4.12




hominlinx 2017-06-23 14:17
你好, 能跟你交流下吗, 我的qq : 295870332

cycloveu 2017-08-21 21:29
在QML中实现双向绑定可以用一下方法
Binding {
                    target: nameText
                    property: "text"
                    value: viewModel.text
                }
                Binding {
                    target: viewModel
                    property: "text"
                    value: nameText.text
                }


查看完整版本: [-- “Why Not”使用QML实现一个MVVM框架 --] [-- top --]



Powered by phpwind v8.7 Code ©2003-2011 phpwind
Gzip disabled