模型视图设计模式的核心思想
模型 (数据) 与视图 (显示) 相分离
模型对外提供标准接口存取数据 (不关心数据如何显示)
视图自定义数据的显示方式 (不关心数据如何组织存储)
模型视图的直观理解
模型视图模式的工作机制
当数据发生改变时
- 模型发出信号通知视图
当用户与视图进行交互时
- 视图发出信号提供交互信息
Qt 中的模型类层次结构
Qt 中的视图类层次结构
关键技术问题:
模型如何为数据提供统一的访问方式?
深入理解:
在 Qt 中,无论模型以什么结构组织数据,都必须为每一个数据提供独一无二的索引;视图通过索引访问模型中的具体数据。
模型视图编程示例
模型视图结构的初探
Widget.h
class Widget : public QWidget
{
Q_OBJECT
private:
QFileSystemModel m_fsModel;
QTreeView m_treeView;
public:
Widget(QWidget* parent = nullptr);
~Widget();
};
Widget.cpp
Widget::Widget(QWidget* parent)
: QWidget(parent)
{
m_treeView.setParent(this);
m_treeView.move(10, 10);
m_treeView.resize(800, 300);
m_fsModel.setRootPath(QDir::currentPath());
m_treeView.setModel(&m_fsModel);
m_treeView.setRootIndex(m_fsModel.index(QDir::currentPath()));
}
Widget::~Widget()
{
}
程序运行结果如下所示:
模型定义标准接口 (成员函数) 对数据进行访问
视图通过标准接口获取数据并定义显示方式
模型使用信号与槽的机制通知视图数据变化
模型中的数据都是以层次结构表示的
模型中的索引
模型索引是数据与视图分离的重要机制
模型中的数据使用唯一的索引来访问
QModelIndex 是 Qt 中的模型索引类
- 包含具体数据的访问途径
- 包含一个指向模型的指针
索引的意义
索引中的行和列
线性模型可以使用 (row,column) 作为数据索引
问题
只用行和列描述数据索引是否足够通用?
不足够通用。
思考:
如何索引以树形结构组织的数据?
模型中的通用树形结构
模型中数据索引的通用方式
三元组:(row,column,parent)
数据索引深入理解
Widget.h
class Widget : public QWidget
{
Q_OBJECT
private:
QPlainTextEdit m_edit;
QFileSystemModel m_fsModel;
protected slots:
void onDirectoryLoaded(const QString& path);
public:
Widget(QWidget* parent = nullptr);
~Widget();
};
Widget.cpp
Widget::Widget(QWidget* parent)
: QWidget(parent)
{
m_edit.setParent(this);
m_edit.move(10, 10);
m_edit.resize(600, 300);
connect(&m_fsModel, &QFileSystemModel::directoryLoaded, this, &Widget::onDirectoryLoaded);
m_fsModel.setRootPath(QDir::currentPath());
}
void Widget::onDirectoryLoaded(const QString &path)
{
QModelIndex root = m_fsModel.index(path);
QByteArray array;
QBuffer buf(&array);
if(buf.open(QIODevice::WriteOnly))
{
QTextStream out(&buf);
out << m_fsModel.isDir(root) << endl;
out << m_fsModel.data(root).toString() << endl;
out << root.data().toString() << endl;
out << &m_fsModel << endl;
out << root.model() << endl;
out << m_fsModel.filePath(root) << endl;
out << m_fsModel.fileName(root) << endl;
for(int i = 0; i < m_fsModel.rowCount(root); i++)
{
QModelIndex c = m_fsModel.index(i, 0, root);
out << c.data().toString() << endl;
}
out.flush();
buf.close();
}
if(buf.open(QIODevice::ReadOnly))
{
m_edit.insertPlainText(buf.readAll());
buf.close();
}
}
Widget::~Widget()
{
}
程序运行结果如下所示:
问题
不同的视图如何显示同一个模型中的数据?
Qt 中标准模型定义
数据角色的概念
模型中的数据在视图中的用途 (显示方式可能不同)
模型必须为数据设置特定数据角色 (数据属性)
数据角色用于提示视图数据的作用
数据角色是不同视图以统一风格显示数据的标准
Qt 中的数据角色定义
数据角色的意义
定义了数据在特点系统下的标准用途
不同的视图可以通过相同标准显示数据
注意:
- 数据角色只是一个附加的属性,这个属性代表推荐的数据显示方式。不同的视图完全可以自由解析或忽略数据的角色信息。
数据模型中的角色
Widget.h
class Widget : public QWidget
{
Q_OBJECT
private:
QStandardItemModel m_model;
QTableView m_tableView;
QListView m_listView;
QTreeView m_treeView;
void initModel();
void initView();
public:
Widget(QWidget *parent = nullptr);
~Widget();
};
Widget.cpp
Widget::Widget(QWidget* parent)
: QWidget(parent, Qt::WindowContextHelpButtonHint | Qt::WindowCloseButtonHint)
{
initModel();
initView();
m_tableView.setModel(&m_model);
m_listView.setModel(&m_model);
m_treeView.setModel(&m_model);
}
void Widget::initModel()
{
QStandardItem* root = m_model.invisibleRootItem();
QStandardItem* itemA = new QStandardItem();
QStandardItem* itemB = new QStandardItem();
QStandardItem* itemC = new QStandardItem();
QStandardItem* itemChild = new QStandardItem();
itemChild->setData("child", Qt::DisplayRole);
itemChild->setData("Tip child", Qt::ToolTipRole);
itemA->setData("A", Qt::DisplayRole);
itemA->setData("Tip A", Qt::ToolTipRole);
itemA->setData("Help A", Qt::WhatsThisRole);
itemB->setData("B", Qt::DisplayRole);
itemB->setData("Tip B", Qt::ToolTipRole);
itemC->setData("C", Qt::DisplayRole);
itemC->setData("Tip C", Qt::ToolTipRole);
itemC->setData("Help C", Qt::WhatsThisRole);
itemC->setChild(0, 0, itemChild);
root->setChild(0, 0, itemA);
root->setChild(0, 1, itemB);
root->setChild(1, 0, itemC);
}
void Widget::initView()
{
m_tableView.setParent(this);
m_tableView.move(10, 10);
m_tableView.resize(300, 100);
m_listView.setParent(this);
m_listView.move(10, 120);
m_listView.resize(300, 100);
m_treeView.setParent(this);
m_treeView.move(10, 230);
m_treeView.resize(300, 100);
}
Widget::~Widget()
{
}
程序运行结果如下所示: