模型视图设计模式
模型视图设计模式的核心思想
1、模型(数据)与视图(显示)相分离
2、模型对外提供标准接口存取数据(不关心数据如何显示)
3、视图自定义数据的显示方式(不关系数据如何组织、存储)
注:模型里面并不真正存储数据(数据少的话也可以直接存储在模型里),模型只是负责从诸如磁盘文件、数据库、网络通讯等获得源数据,并提供给视图,视图对数据进行修改,然后再通过模型更新源数据。
模型视图模式组成
Qt中的模型视图模式分为三个部分:模型、视图、和委托(处理用户对数据项的编辑与渲染数据项,目前暂时不谈委托)
模型视图模式中的类
模型:
1、 抽象类QAbstractItemModel为所有模型的基类,它实现了供视图与委托的接口
2、 QAbstractItemModel提供的接口能够满足视图以表格、列表、树的形式显示数据
3、 如果要为列表或者表格设计自定义模型,建议继承自QAbstractListModel和QAbstractTableModel,这两个类已经实现了许多通用函数
内置标准模型:
QStringListModel:存储简单的字符串列表
QStandardItemModel:可以用于树结构的存储,提供了层次数据
QFileSystemModel:本地系统的文件和目录信息
QSqlQueryModel、QSqlTableModel、QSqlRelationalTableModel:存取数据库数据
视图
抽象类QAbstractItemView是所有视图类的基类
内置标准视图
1、 QListView把Model中的数据显示为一个列表
2、 QTableView把Model中的数据以表格的形式表现
3、 QTreeView用具有层次结构的列表来显示Model中的数据
委托
1、 抽象类QAbstractItemDelegate是委托类的基类
2、 内置有两个相互独立的标准委托QStyledItemDelegate 与QItemDelegate
3、 QStyledItemDelegate 是Qt4.4版本后的标准委托
4、 QStyledItemDelegate使用当前的样式来绘制它的项
5、 在实现自定义委托或使用Qt样式表时,使用QStyledItemDelegate作为基类。
模型视图模式直观理解
//模型视图示例
//Widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QtGui/QWidget>
#include <QFileSystemModel>
#include <QTreeView>
class Widget : public QWidget
{
Q_OBJECT
QFileSystemModel m_fsModel; //声明系统文件模型
QTreeView m_treeView; //声明树状视图
public:
Widget(QWidget *parent = 0);
~Widget();
};
#endif // WIDGET_H
//widget.cpp
#include "Widget.h"
#include <QDir> /
Widget::Widget(QWidget *parent)
:QWidget(parent)
{
m_treeView.setParent(this); //设置视图的父子间关系
m_treeView.move(10, 10); //设置视图类对象位置
m_treeView.resize(500, 300); //设置视图类对象大小
m_fsModel.setRootPath(QDir::currentPath()); //设置当前路径为模型监视路径
m_treeView.setModel(&m_fsModel); //连接视图与模型
//以当前文件目录模型索引来显示模型数据,如果没有此句将会显示根目录数据
m_treeView.setRootIndex(m_fsModel.index(QDir::currentPath()));
}
Widget::~Widget()
{
}
模型视图模式的工作机制(信号-槽)
当数据发生改变时,模型发出信号通知视图
当用户与视图进行交互时,视图发出信号提供交互信息
用户编辑数据项时,委托发出信号告知模型与视图编辑器的状态
数据与视图分离的重要机制:
模型索引:
在Qt中,不管模型以什么结构组织数据,都必须为每一个数据提供独一无二的索引,
模型索引提供对数据块的临时引用,视图与委托通过索引访问模型中的具体数据
Qt中的模型索引类QModelIndex
QModelIndex中包含了具体数据的访问途径、包含了一个指向模型的指针
QModelIndex提供的是模型的临时索引
注意:由于模型会经常重新组织其内部的数据结构,模型索引很可能失效,不应该保存模型索引。如果实在需要长期引用一个数据块,使用QPersistentModelIndex类创建永久模型索引,永久模型索引保证其引用的数据及时更新
模型中的数据以层次结构表示
List Model虽然是线性的列表,有一个 RootItem(根节点),线性的一个个数据可以看作是一个只有一列的表格,但是它是有层次的,因为有一个根节点。Table Model就比较容易理解,只是也存在一个根节点。Tree Model主要面向层次数据,而每一层次都可以都很多列,因此也是一个带有层次的表格。
模型中索取数据索引的通用方式
在Qt中,为了定位模型中的数据(即获取模型索引),需要三个属性:行号、列号以及父索引。即三元组:(row, column, parent)
当父节点为虚拟Root节点时,使用空索引(直接调用QModelIndex()产生)作为父节点
QModelIndexindexA = model->index(0, 0, QModelIndex());
QModelIndex indexB = model->index(1, 1, QModelIndex());
QModelIndex indexC = model->index(2, 1, QModelIndex());
QModelIndex indexA =model->index(0, 0,QModelIndex());
QModelIndex indexC =model->index(2, 1,QModelIndex());
QModelIndexindexB = model->index(1, 0,indexA); //B为A的子项
//widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QtGui/QWidget>
#include <QPlainTextEdit>
#include <QFileSystemModel>
class Widget : public QWidget
{
Q_OBJECT
QPlainTextEdit m_edit;
QFileSystemModel m_fsm;
protected slots:
void onDirectoryLoaded(const QString& path);
public:
Widget(QWidget *parent = 0);
~Widget();
};
#endif // WIDGET_H
//widget.cpp
#include "Widget.h"
#include <QDir>
#include <QModelIndex>
#include <QByteArray>
#include <QBuffer>
#include <QTextStream>
Widget::Widget(QWidget *parent)
:QWidget(parent)
{
m_edit.setParent(this); //设置父子关系
m_edit.move(10, 10);
m_edit.resize(500, 300);
connect(&m_fsm, SIGNAL(directoryLoaded(QString)), this,SLOT(onDirectoryLoaded(QString))); //绑定信号与槽
m_fsm.setRootPath(QDir::currentPath()); //设置当前路径为文件模型监视路径
}
void Widget::onDirectoryLoaded(constQString& path) //这里的path由m_fsm.setRootPath(QDir::currentPath());控制
{
QModelIndex root = m_fsm.index(path);
QByteArray array;
QBuffer buffer(&array);
if( buffer.open(QIODevice::WriteOnly) ) //以只读方式打开缓冲区
{
QTextStream out(&buffer);
out << m_fsm.isDir(root) << endl;
out << m_fsm.data(root).toString()<< endl; //通过模型访问数据
out << root.data().toString() << endl; //通过索引访问数据
out << &m_fsm << endl; //模型地址
out << root.model() << endl; //获取索引内指向模型的指针
out << m_fsm.filePath(root) << endl;
out << m_fsm.fileName(root) << endl;
out << endl;
for(int i=0; i<m_fsm.rowCount(root); i++)
{
QModelIndex ci = m_fsm.index(i, 0, root);
out << ci.data().toString() << endl;
}
out.flush();
buffer.close(); //关闭缓冲区
}
if( buffer.open(QIODevice::ReadOnly) )
{
QTextStream in(&buffer);
m_edit.insertPlainText(in.readAll());
buffer.close();
}
}
Widget::~Widget()
{
}
模型中的数据项的角色
数据角色的概念:
1、 模型中的数据在视图中的用途(显示方式)可能不同
2、 模型必须为数据设置特定的数据角色(数据属性)
3、 数据角色用于提示视图数据的作用
4、 数据角色是不同视图以统一风格显示数据的标准
Qt中的数据角色定义:
通用角色(与类型相关):
Qt::DisplayRole | 以文本形式显示的关键数据(QString) |
Qt::DecorationRole | 以图标形式显示的数据(QColor、QIcon、QPixmap) |
Qt::EditRole | 适合在 编辑器中编辑的形式的数据(QString) |
Qt::ToolTipRole | 显示在项的ToolTip中的数据 |
Qt::StatusTipRole | 状态栏中显示的提示性数据 |
Qt::WhatsThisRole | 项的帮助信息数据 |
Qt::SizeHintRole | 提供给视图的项大小数据 |
数据角色的意义:
1、 定义了数据在特定系统下的标准用途
2、 不同视图可以通过相同标准显示数据
注意:
1、 数据角色只是一个附加属性,这个属性代表推荐的数据显示方式
2、 不同的视图完全可以自由解析或者忽略数据的角色信息
//widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QtGui/QWidget>
#include <QTableView>
#include <QListView>
#include <QTreeView>
#include <QStandardItemModel>
class Widget : public QWidget
{
Q_OBJECT
/*QStandardItemModel负责保存数据,每个数据项被表示为QStandardItem对象。类QabstractItemModel,QabstractListModel,QAbstractTableModel不保存数据,需要从这些类派生出子类,并在子类中定义某种数据结构来保存数据。*/
QStandardItemModel m_model;
QTableView m_tableView;
QListView m_listView;
QTreeViewm_treeView;
void initModel();
void initView();
public:
Widget(QWidget *parent = 0);
~Widget();
};
#endif // WIDGET_H
//widget.cpp
#include "Widget.h"
#include <QStandardItem>
Widget::Widget(QWidget *parent)
:QWidget(parent, Qt::WindowContextHelpButtonHint)
{
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类对象)(虚拟的根节点)
QStandardItem* itemA = new QStandardItem(); //构造数据项
QStandardItem* itemB = new QStandardItem();
QStandardItem* itemC = new QStandardItem();
QStandardItem* itemChild = new QStandardItem();
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);
itemChild->setData("Child", Qt::DisplayRole);
itemChild->setData("Tip Child", Qt::ToolTipRole);
itemChild->setData("Help Child", Qt::WhatsThisRole);
itemC->setChild(0, 0, itemChild); //给数据项itemC设置子数据项itemChild,将子数据项设置在row为0,column为0的位置(此时
//子数据项itemChild的索引三元组为(0,0,itemC)
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()
{
}
声明:
此文根据 狄泰学院唐老师的《QT实验分析教程》创作