“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页面代码 - 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
- 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; };
- 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();- 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
|