• 11745阅读
  • 2回复

[原创]“Why Not”使用QML实现一个MVVM框架 [复制链接]

上一主题 下一主题
离线cycloveu
 

只看楼主 倒序阅读 楼主  发表于: 2017-04-12
— 本帖被 XChinux 执行加亮操作(2017-04-19) —

一.前言      

      最近几年最热门的技术之一就是前端技术了,各种前端框架,前端标准和前端设计风格层出不穷,而在众多前端框架中具有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.我们有一个登陆界面,用户输入账号和密码然后点击登陆按钮,并在界面上显示登陆信息。

QML页面代码

  1. import QtQuick 2.7
  2. import QtQuick.Controls 2.0
  3. import QtQuick.Layouts 1.0
  4. ApplicationWindow {
  5.     visible: true
  6.     width: 320
  7.     height: 480
  8.     title: qsTr("MVVM")
  9.     header: ToolBar {
  10.         Text {
  11.             anchors.centerIn: parent
  12.             font.family: "微软雅黑"
  13.             font.pointSize: 16
  14.             color: "lightgreen"
  15.             text: qsTr("QML MVVM")
  16.         }
  17.     }
  18.     Column {
  19.         anchors.centerIn: parent
  20.         spacing: 5
  21.         TextArea {
  22.             id: nameTextArea
  23.             anchors.horizontalCenter: parent.horizontalCenter
  24.             placeholderText: qsTr("username")
  25.             text: mainViewModel.name
  26.             onTextChanged: mainViewModel.name = text
  27.         }
  28.         TextArea {
  29.             id: passwordTextArea
  30.             anchors.horizontalCenter: parent.horizontalCenter
  31.             placeholderText: qsTr("password")
  32.             text: mainViewModel.password
  33.             onTextChanged: mainViewModel.password = text
  34.         }
  35.         Button {
  36.             text: "Login"
  37.             anchors.right: parent.right
  38.             anchors.rightMargin: 5
  39.             onClicked: mainViewModel.loginButtonClicked()
  40.         }
  41.         Text {
  42.             id:stateText
  43.             anchors.horizontalCenter: parent.horizontalCenter
  44.             text: mainViewModel.state
  45.         }
  46.     }
  47. }

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

           text: mainViewModel.name      onTextChanged: mainViewModel.name = text

a.根据页面中的元素去设计对应的ViewModel层需要的那些属性:

name : QString

password : QString

state : QString

loginButtonClicked : signal

  1. class MainViewModel : public QObject
  2. {
  3.     Q_OBJECT
  4.     Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
  5.     Q_PROPERTY(QString password READ password WRITE setPassword NOTIFY passwordChanged)
  6.     Q_PROPERTY(QString state READ state WRITE setState NOTIFY stateChanged)
  7. public:
  8.     explicit MainViewModel(QObject *parent = 0);
  9.     MainModel* model();
  10.     void setModel(MainModel *model);
  11.     QString name();
  12.     void setName(const QString &name);
  13.     QString password();
  14.     void setPassword(const QString &password);
  15.     QString state();
  16.     void setState(const QString &state);
  17.     Q_INVOKABLE void loginButtonClicked();
  18. signals:
  19.     void nameChanged(const QString &);
  20.     void passwordChanged(const QString &);
  21.     void stateChanged(const QString &);
  22. private:
  23.     QString m_name;
  24.     QString m_password;
  25.     QString m_state;
  26.     MainModel *m_model;
  27. };
  1. MainViewModel::MainViewModel(QObject *parent) : QObject(parent),
  2.     m_model(NULL)
  3. {
  4. }
  5. MainModel *MainViewModel::model()
  6. {
  7.     return m_model;
  8. }
  9. void MainViewModel::setModel(MainModel *model)
  10. {
  11.     m_model = model;
  12.     if (m_model)
  13.     {
  14.         connect(this, &MainViewModel::nameChanged, m_model, &MainModel::setName);
  15.         connect(this, &MainViewModel::passwordChanged, m_model, &MainModel::setPassword);
  16.     }
  17. }
  18. QString MainViewModel::name()
  19. {
  20.     return m_name;
  21. }
  22. void MainViewModel::setName(const QString &name)
  23. {
  24.     m_name = name;
  25.     emit nameChanged(m_name);
  26. }
  27. QString MainViewModel::password()
  28. {
  29.     return m_password;
  30. }
  31. void MainViewModel::setPassword(const QString &password)
  32. {
  33.     m_password = password;
  34.     emit passwordChanged(m_password);
  35. }
  36. QString MainViewModel::state()
  37. {
  38.     return m_state;
  39. }
  40. void MainViewModel::setState(const QString &state)
  41. {
  42.     m_state = state;
  43.     emit stateChanged(state);
  44. }
  45. void MainViewModel::loginButtonClicked()
  46. {
  47.     if (m_model)
  48.     {
  49.         QString errorCode;
  50.         if (!m_model->login(&errorCode))
  51.         {
  52.             setState("Failed:" + errorCode);
  53.         }
  54.         else
  55.         {
  56.             setState("Successed");
  57.         }
  58.     }
  59. }

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

b.在ViewModel层的loginButtonClicked函数中调用Model层的Login函数进行逻辑处理。Model层看起来是这样的:

name : QString

password : QString

bool login();

  1. class MainModel : public QObject
  2. {
  3.     Q_OBJECT
  4. public:
  5.     explicit MainModel(QObject *parent = 0);
  6.     bool login(QString *error);
  7.     QString name();
  8.     void setName(const QString &name);
  9.     QString password();
  10.     void setPassword(const QString &password);
  11. private:
  12.     QString m_name;
  13.     QString m_password;
  14. };

最终登陆效果:

感谢你的阅读。谢谢。

by汏家的诚

2017.4.12

大道至简 悟在天成
离线hominlinx

只看该作者 1楼 发表于: 2017-06-23
你好, 能跟你交流下吗, 我的qq : 295870332
离线cycloveu

只看该作者 2楼 发表于: 2017-08-21
在QML中实现双向绑定可以用一下方法
Binding {
                    target: nameText
                    property: "text"
                    value: viewModel.text
                }
                Binding {
                    target: viewModel
                    property: "text"
                    value: nameText.text
                }
大道至简 悟在天成
快速回复
限100 字节
 
上一个 下一个