一、简介
1、Model/Veiw是建立在MVC基础上的。什么是MVC?这里有详细介:https://blog.csdn.net/zuiyingong6567/article/details/80150834
2、Qt中的MVC略有不同,变成了MVD。如下图:
3、model、view、delegate 之间的作用关系简单概括如下:
1)model和data相互通信,然后model为view和delegate提供接口。
2)view通过调用model的接口,从model中获取模型索引QModelIndex,通过QModelIndex可以获得data。
3)delegate为view展示data,delegate可以被编辑修改删除。而当在delegate上编辑时,它会用QModelIndex于model通信,通知model更新数据。从这里可以看出来,view和delegate都可以使用QModelIndex,但是view只能使用QModelIndex进行读取,delegate只能修改。
4、Model:所有model都基于QAbstractItemModel类。该类定义了view和delegate用于访问data的接口。data本身不必存储在model中,它可以存储在由单独的类、文件、数据库或其他数据结构中。Qt提供了一些现成的model:
1)QStringListModel:用于存储简单的QString列表。
2)QStandardItemModel:适用于各种结构,每个项都可以包含任意数据。
3)QFileSystemModel:提供有关本地归档系统中的文件和目录的信息。
4)QSqlQueryModel、QSqlTableModel、QSqlRelationalTableModel:用于访问数据库。
同时也可以子类化QAbstractItemModel、QAbstractListModel或QAbstractTableModel来创建自定义模型。
5、Views:QListView列表视图,QTableView表格视图,QTreeView树形视图。
6、Delegates:QAbstractItemDelegate是模型/视图框架中委托的抽象基类。默认委托实现由QStyledItemDelegate提供,Qt的标准视图将其用作默认委托。如果需要展示更复杂的数据,需要我们重写Delegates。
7、模型视图中的方便类:QListWidget、QTreeWidget和QTableWidget。这些类不如视图类灵活,不能与任意模型一起使用。
二、如何使用model/view
1、model详解:
1)model的三种分层结构:
2)模型索引:为了确保数据的表示与访问数据的方式保持分离,引入了模型索引的概念。通过模型获得的每一条信息都由模型索引表示。视图和委托使用这些索引请求要显示的数据项。因此,只有模型需要知道如何获取数据。模型索引是临时的,当模型数据变化后,此模型索引变失效了,除非使用了QPersistentModelIndex。要获得与数据项相对应的模型索引,必须为模型指定三个属性:行号、列号和父项的模型索引:QModelIndex QAbstractItemModel::index(int row, int column, const QModelIndex&parent = QModelIndex()) const。
3)Item Role项角色:模型中的项可以为其他组件执行各种角色,允许为不同的情况提供不同类型的数据。例如,Qt::DisplayRole用于访问可以在视图中显示为文本的字符串。通常,项包含许多不同角色的数据,标准角色由Qt::ItemDataRole定义。我们可以通过向模型传递与项目对应的模型索引,并指定一个角色来获取我们想要的数据类型,从而获得需要的数据。
4)总结上文:
a)视图和委托可以通过模型索引获得数据。
b)通过行号、列号和父项的模型索引可以获得模型索引。
c)模型索引是根据其他组件(如视图和委托)的请求由模型构建的。
d)如果在使用index()请求索引时为父项指定了有效的模型索引,则返回的索引引用模型中父项下的项,如果在使用index()请求索引时为父项指定了无效的模型索引,则返回的索引引用模型中的顶级项。
e)角色区分与项关联的不同类型的数据。
5)如何使用模型索引:
a)可以使用rowCount()和columnCount()找到模型的维度。这些函数通常需要指定父模型索引。
b)模型索引用于访问模型中的项。
c)要访问模型中的顶级项,使用QModelIndex()指定空模型索引作为父索引。
d)项包含不同角色的数据。要获取特定角色的数据,必须向模型提供模型索引和角色。
6) 使用Qt自带的model
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
QSplitter *splitter = new QSplitter;
QFileSystemModel *model = new QFileSystemModel;
model->setRootPath("F:/");//指定此路径的根路径为model的根路径
QModelIndex index = model->index("F:/");//指定索引项即展示的根
QListView *list = new QListView;
list->setModel(model);
list->setRootIndex(index);
splitter->addWidget(list);
QTableView *table = new QTableView;
table->setModel(model);
table->setRootIndex(index);
splitter->addWidget(table);
QTreeView *tree = new QTreeView;
tree->setModel(model);
tree->setRootIndex(index);
splitter->addWidget(tree);
setCentralWidget(splitter);
}
7)自定义model。
a)QAbstractItemModel类提供了一个足够灵活的接口来支持以分层结构排列信息的数据源,从而允许以某种方式插入、删除、修改或排序数据。它还支持拖放操作。
在为现有数据结构创建新模型时,重要的是考虑应该使用哪种类型的模型来提供到数据的接口。子类化QAbstractListModel或QAbstractTableModel适合列表或表,子类化QAbstractItemModel适合层次树结构。
3、view详解:
1)视图能做些什么:显示数据、处理项之间的导航、项选择、上下文菜单、拖放、与委托一起提供定制编辑器。
2)Qt中三个视图类。
a)QListView可以将模型中的项显示为简单列表,也可以以经典图标视图的形式显示。
b)QTreeView将模型中的项显示为列表的层次结构,允许以紧凑的方式表示深度嵌套的结构。
c)QTableView以表格的形式显示模型中的项,非常类似于电子表格应用程序的布局。
3)如何处理选中项:使用QItemSelectionModel类。默认情况下,可以通过selectionModel()函数获得,使用setSelectionModel()指定替换选择模型。
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
QTableWidget *table = new QTableWidget;
table->setColumnCount(3);
table->setRowCount(3);
setCentralWidget(table);
QItemSelectionModel *selection = table->selectionModel();
//让表在初始化的时候就被选中
QModelIndex topLeft;
QModelIndex bottomRight;
topLeft = table->model()->index(0,0,QModelIndex());
bottomRight = table->model()->index(1,1,QModelIndex());
QItemSelection itemSelection;
itemSelection.select(topLeft,bottomRight);
selection->select(itemSelection,QItemSelectionModel::Select);
//当当前项发生更改时,将发出此信号。前面的模型项索引将被当前索引替换,作为所选项的当前项。
connect(selection,&QItemSelectionModel::currentChanged,
[=](const QModelIndex ¤t, const QModelIndex &previous){
statusBar()->showMessage(
tr("Moved from (%1,%2) to (%3,%4)")
.arg(previous.row()).arg(previous.column())
.arg(current.row()).arg(current.column()));
});
//当选择发生更改时,将发出此信号。
connect(selection,&QItemSelectionModel::selectionChanged,
[=](const QItemSelection &selected, const QItemSelection &deselected){
QModelIndexList list = selected.indexes();
foreach (QModelIndex index, list) {
QString text = QString("(%1,%2)").arg(index.row()).arg(index.column());
table->model()->setData(index,text);
}
QModelIndexList list1 = deselected.indexes();
foreach (QModelIndex index, list1) {
table->model()->setData(index,"");
}
});
connect(selection,&QItemSelectionModel::modelChanged,
[=](QAbstractItemModel *model){
});
//如果当前项发生更改,且其列与前一个当前项的列不同,则发出此信号。
connect(selection,&QItemSelectionModel::currentColumnChanged,
[=](const QModelIndex ¤t, const QModelIndex &previous){
// qDebug() << "previous column:" << previous.column();
// qDebug() << "current column:" << current.column();
});
//如果当前项发生更改,且其行与前一个当前项的行不同,则发出此信号。
connect(selection,&QItemSelectionModel::currentRowChanged,
[=](const QModelIndex ¤t, const QModelIndex &previous){
// qDebug() << "previous row:" << previous.row();
// qDebug() << "current row:" << current.row();
});
//反转选中的状态
QAction *toggle = menuBar()->addAction("反转选中状态");
connect(toggle,&QAction::triggered,[=]{
QModelIndex topLeft = table->model()->index(0,1,QModelIndex());
QModelIndex bottomRight = table->model()->index(1,1,QModelIndex());
QItemSelection toggleSelection(topLeft,bottomRight);
selection->select(toggleSelection,QItemSelectionModel::Toggle);
});
//全选
QAction *all = menuBar()->addAction("选中全部");
connect(all,&QAction::triggered,[=]{
QModelIndex topLeft = table->model()->index(0,0,QModelIndex());
QModelIndex bottomRight = table->model()->index(table->model()->rowCount(QModelIndex())-1,
table->model()->columnCount(QModelIndex())-1,
QModelIndex());
QItemSelection itemSelection(topLeft,bottomRight);
selection->select(itemSelection,QItemSelectionModel::Select);
});
}
4)如何处理对视图中项的拖放操作
5)如何与Delegate一起提供定制编辑器,在Delegate中会演示。
1)为什么会有Delegate:通常,视图负责向用户表示模型数据,并处理用户输入。但是为了使获取该输入的方式具有一定的灵活性,便交互由Delegate处理。
2)Delegate由itemDelegate()函数返回。setItemDelegate()设置自定义Delegate。自定义Delegate的例子:
#ifndef SPINBOXDELEGATE_H
#define SPINBOXDELEGATE_H
#include <QStyledItemDelegate>
class SpinBoxDelegate : public QStyledItemDelegate
{
public:
SpinBoxDelegate(QObject *parent = 0);
protected:
//创建自定义的控件
QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const;
//设置编辑控件中的值
void setEditorData(QWidget *editor, const QModelIndex &index) const;
//将编辑控件中的值保存到model中
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const;
//设置编辑控件显示的位置和大小
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const;
};
#endif // SPINBOXDELEGATE_H
#include "spinboxdelegate.h"
#include <QSpinBox>
SpinBoxDelegate::SpinBoxDelegate(QObject *parent):QStyledItemDelegate(parent)
{
}
QWidget *SpinBoxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QSpinBox *editor = new QSpinBox(parent);
editor->setFrame(true);
editor->setMinimum(0);
editor->setMaximum(200);
return editor;
}
void SpinBoxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
int value = index.model()->data(index,Qt::EditRole).toInt();
QSpinBox *box = static_cast<QSpinBox*>(editor);
box->setValue(value);
}
void SpinBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
QSpinBox *box = static_cast<QSpinBox*>(editor);
model->setData(index,box->value(),Qt::EditRole);
}
void SpinBoxDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
editor->setGeometry(option.rect);
}
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
QAbstractItemDelegate *delegate = new SpinBoxDelegate(this);
QStringList list;
for(int i = 100; i < 120; ++i){
list << QString::number(i);
}
QStringListModel *model = new QStringListModel;
model->setStringList(list);
QListView *view = new QListView;
view->setModel(model);
view->setItemDelegate(delegate);
setCentralWidget(view);
}