【Model-View-Delegate】
首先简单了解MVC。(Qt官方文档对GUI Design的书籍有相关的推荐:详细请看Books about GUI Design)
Model-View-Controller(MVC)是源自SmallTalk的一个设计模式,通常在构建用户界面时使用。在设计模式中写道:MVC由三种对象组成。模型是应用程序对象,视图时其屏幕显示,控制器定义了用户界面对用户输入的反应方式。MVC就是将这三种对象解耦以增加灵活性合复用性。
MVD是在Qt原有MVC的基础上对Controller进行了修改,为了允许灵活地处理用户输入,引入了Delegate的概念,即Model-View-Delegate。框架里有委托的好处在于,它允许自定义呈现和编辑数据项的方式。将数据的可视化模块化,以便我们在开发的过程中可以控制数据的不同方面。可以将列表视图和网格视图交换,而对数据的更改很少,类似地,将数据实例封装在委托中可以使开发人员决定如何呈现或处理数据。(后面我们会通过一个实例来体验此框架的使用)
Qt Quick的MVD框架:
Model-包含数据与结构,有几种用于创建模型的QML类型
View-显示数据的容器,view可能会展示一个list或者grid数据
Delegate-指示数据应该如何在视图中显示。委托将模型中的每个数据封装起来。可以通过委托访问数据。委托还可以将数据写回可编辑的模型中
对于以上的三部分,每个部分都有抽象类定义,这些抽象类提供了公共接口,并在某些情况下提供了功能的默认实现。我们在开发的过程中就是需要子类化这些抽象类,实现需要的功能。例如:QAbstractItemModel是所有item models的祖先,又衍生了QStringListModel,QStandardItemModel,QAbstractListModel,QAbstractTableModel等; QAbstractItemView是视图类的祖先,衍生了 QListView,QTableView等;QAbstractItemDelegate是Model-View框架中所有Delegates的抽象基类,衍生了 QStyledItemDelegate, QItemDelegate。接下来我们来通过使用QAbstractListModel-ListView-Delegate来体验这个框架的使用
【MVD实操】
1.在qml文件中声明ListView
import QtQuick 2.0
ListView {
width: 180; height: 200
model: TestListModel //model,定义要显示的数据
delegate: TestDelegate //delegate,定义数据应该如何显示
}
2.实现Model:继承Qt Model的抽象类QAbstractListModel(它不能直接使用,必须被子类化)
我们会重写三个核心方法支撑向View提供数据
struct TestElem{
int testElem1 {1};
int testElem2 {2};
int testElem3 {3};
};
class TestData
{
public:
TestData()
{
int role = Qt::UserRole;
m_roleNames.insert(role++, "testElem1");
m_roleNames.insert(role++, "testElem2");
m_roleNames.insert(role++, "testElem3");
}
~TestData() {}
QHash<int, QByteArray> m_roleNames;
QList<TestElem> m_testList;
};
class TestModel : public QAbstractListModel
{
Q_OBJECT
public:
//返回列表中的项目数
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
//从列表中查找数据
QVariant data(const QModelIndex &index = QModelIndex(),
int role = Qt::DisplayRole) const override;
//设置角色
QHash<int, QByteArray> roleNames() const override;
// 添加数据
bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
// 删除数据
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
private:
TestData m_testData;
};
int TestModel ::rowCount(const QModelIndex &parent) const
{
return m_stuData.m_stuList.size();
}
QVariant TestModel ::data(const QModelIndex &index, int role) const
{
if (index.row() < 0) {
return QVariant();
}
const TestElem &testElem = m_testData.m_testList[index.row()];
switch (role - Qt::UserRole) {
case 0:
return testElem.testElem1;
case 1:
return testElem.testElem2;
case 2:
return testElem.testElem3;
}
return QVariant();
}
QHash<int, QByteArray> TestModel ::roleNames() const
{
return m_testData.m_roleNames;
}
bool TestModel ::insertRows(int row, int count, const QModelIndex &parent)
{
beginInsertRows(parent, row, row + count - 1);
// FIXME: Implement me!
endInsertRows();
return true;
}
bool TestModel ::removeRows(int row, int count, const QModelIndex &parent)
{
beginRemoveRows(parent, row, row + count - 1);
// FIXME: Implement me!
endRemoveRows();
return true;
}
3.实现Delegate
Component{
id:TestDelegate
Rectangle{
id:delegateRec
width: 50
height: 50
Text{
id:test1
text:model.testElem1
}
Text{
id:test2
text:model.testElem2
}
Text{
id:test3
text:model.testElem3
}
}
}
会发现,C++的数据已经通过这个模型展示到了qml中,其实这也是C++与qml通信的方式之一(后续会总结C++与qml通信的几种方式);在需求开发的过程中,数据与用户界面的开发是非常独立的,只需要对应好角色名称即可
总结:熟练使用这套框架,会发现自己的代码复用性很高,需求的变动不会带来灾难的开发量,独立的处理好自己的模块即可
参考资料
【1】Qt Assistant model/view Programming
【2】Qt Assistant Models and Views in Qt Quick
【3】Qt Quick核心编程 第十三章 Model/View
【4】开源链接:https://github.com/toby20130333/QtQuickExample
【5】开源链接:https://github.com/foruok/stockMonitor