
前言:
- Qt是C++一个开发框架,具有跨平台特性。
- 这篇是作者大二学习的时候做的笔记,有可能有错误,请各位批评指正。
- 这篇记录简单的讲解QT的模型与视图。
- 欢迎大家收藏 + 关注,作者将会持续更新。
文章目录
Model/View(模型/视图)结构
简介
Model/View(模型/视图) 结构是 Qt 中用界面组件显示与编辑数据的一种结构,视图(View) 是显示和编辑数据的界面组件,模型(Model) 是视图与原始数据之间的接口。
将界面组件与所编辑的数据分离开来,又通过数据源的方式连接起来,是处理界面与数据的一种较好的方式。Qt 使用 Model/View 结构来处理这种关系,Model/View 的基本结构如下图所示。

其中各部分的功能如下:
- 数据(Data) 是实际的数据
- 视图是屏幕上的界面组件,视图从数据模型获得每个数据项的模型索引(model index),通过模型索引获取数据,然后为界面组件提供显示数据。
- 模型与实际数据通信,并为视图组件提供数据接口。它从原始数据提取需要的内容,用于视图组件进行显示和编辑。
模型、视图和代理之间使用信号和槽通信。当源数据发生变化时,数据模型发射信号通知视图组件;当用户在界面上操作数据时,视图组件发射信号表示这些操作信息;当编辑数据时,代理发射信号告知数据模型和视图组件编辑器的状态。
从数据库视角来看,模型是对模式的抽象,模式是对数据结构的描述。但我感觉简单理解就是,模型是总结出来的一些通用的数据组织形式,视图是模型+数据,给人展示出来。
数据模型
在QT中,所有的基于项数据的数据模型(Model)都是基于 QAbstractltemModel 类的,这个类定义了视图组件和代理存取数据的接口。
Qt 中与数据模型相关的几个主要的类的层次结构如下图所示。

上图中的抽象类是不能直接使用的,需要由子类继承来实现一些纯虚函数。
| QStringListModel | 用于处理字符串列表数据的数据模型类 |
|---|---|
| QStandardltemModel | 标准的基于项数据的数据模型类,每个项数据可以是任何数据类型 |
| Model 类 | 用途 |
| QFileSystemModel | 计算机上文件系统的数据模型类 |
| QSortFilterProxyModel | 与其他数据模型结合,提供排序和过滤功能的数据模型类 |
| QSqlQueryModel | 用于数据库SQL查询结果的数据模型类 |
| QSqlTableModel | 用于数据库的一个数据表的数据模型类 |
| QSqlRelationalTableModel | 用于关系型数据表的数据模型类 |
视图组件
视图组件(View)就是显示数据模型的数据的界面组件,Qt 提供的视图组件如下:
- QListView:用于显示单列的列表数据,适用于一维数据的操作。
- QTreeView:用于显示树状结构数据,适用于树状结构数据的操作。
- QTableView:用于显示表格状数据,适用于二维表格型数据的操作。
- QColumnView:用多个QListView显示树状层次结构,树状结构的一层用一个QListView显示。
- QHeaderView:提供行表头或列表头的视图组件,如QTableView的行表头和列表头。
视图组件在显示数据时,只需调用视图类的 setModel() 函数,为视图组件设置一个数据模型就可以实现视图组件与数据模型之间的关联,在视图组件上的修改将自动保存到关联的数据模型里,一个数据模型可以同时在多个视图组件里显示数据。
前面介绍了 QListWidget、QTreeWidget 和 QtableWidget 3个可用于数据编辑的组件。这 3 个类称为便利类(convenience classes),它们分别是 3 个视图类的子类,其层次关系如下图所示。

代理(Delegate)
代理就是在视图组件上为编辑数据提供编辑器,如在表格组件中编辑一个单元格的数据时,缺省是使用一个 QLineEdit 编辑框。代理负责从数据模型获取相应的数据,然后显示在编辑器里,修改数据后,又将其保存到数据模型中。
Model/View结构的一些概念
在 Qt 中,所有的数据模型类都从 QAbstractltemModel 继承而来,不管底层的数据结构是如何组织数据的,QAbstractltemModel 的子类都以表格的层次结构表示数据,视图组件通过这种规则来存取模型中的数据,但是表现给用户的形式不一样。

上图数据模型的 3 种常见表现形式。不管数据模型的表现形式是怎么样的,数据模型中存储数据的基本单元都是项(item),每个项有一个行号、一个列号,还有一个父项。在
模型索引
QModelIndex 表示模型索引的类,用于通过数据模型提取或修改数据。
行号和列号
数据模型的基本形式是用行和列定义的表格数据,但这并不意味着底层的数据是用二维数组存储的,使用行和列只是为了组件之间交互方便的一种规定。通过模型索引的行号和列号就可以存取数据。
**要获得一个模型索引,必须提供 3 个参数:**行号、列号、父项的模型索引。
cpp
QModelIndex indexA = model->index(0, 0, QModelIndex());
QModelIndex indexB = model->index(1, 1, QModelIndex());
QModelIndex indexC = model->index(2, 1, QModelIndex());
父项
当数据模型是列表或表格时,使用行号、列号存储数据比较直观,所有数据项的父项就是顶层项;当数据模型是树状结构时,情况比较复杂,树状结构中,项一般习惯于称为节点,一个节点可以有父节点,也可以是其他节点的父节点,在构造数据项的模型索引时,必须指定正确的行号、列号和父节点,如下代码:
cpp
QModelIndex indexA = model->index(0, 0, QModelIndex());
QModelIndex indexC = model->index(2, 1, QModelIndex());
项的角色
在为数据模型的一个项设置数据时,可以赋予其不同项的角色的数据。
为一个项的不同角色定义数据,可以告知视图组件和代理组件如何显示数据。

Qt中的Qt::ItemDataRole种类
通用角色(以及相关类型)
cpp
Qt::ItemDataRole //值 描述
Qt::DisplayRole //0 以文本形式呈现的关键数据。(QString)
Qt::DecorationRole //1 以图标形式呈现为装饰的数据。(QColor, QIcon or QPixmap)
Qt::EditRole //2 适合在编辑器中编辑的格式中的数据。(QString)
Qt::ToolTipRole //3 项目工具提示中显示的数据。(QString)
Qt::StatusTipRole //4 状态栏中显示的数据。(QString)
Qt::WhatsThisRole //5 以"这是什么?"模式显示的项目数据。(QString)
Qt::SizeHintRole //13 将提供给视图的项的大小提示。(QSize)
描述外观和元数据的角色
cpp
Qt::ItemDataRole //值 描述
Qt::FontRole //6 使用默认委托呈现的项目所使用的字体。(QFont)
Qt::TextAlignmentRole //7 使用默认委托呈现的项的文本对齐方式。 (Qt::Alignment)
Qt::BackgroundRole //8 用于使用默认代理渲染的项目的背景画笔。(QBrush)
Qt::ForegroundRole //9 用于使用默认代理渲染的项目的前景画笔(通常为文本颜色)。(QBrush)
Qt::CheckStateRole //10 此角色用于获取项目的选中状态。(Qt::CheckState)
Qt::InitialSortOrderRole //14 此角色用于获取标题视图节的初始排序顺序。(Qt::SortOrder)。
辅助功能角色
cpp
Qt::ItemDataRole //值 描述
Qt::AccessibleTextRole //11 可访问性扩展和插件(如屏幕阅读器)使用的文本。(QString)
Qt::AccessibleDescriptionRole //12 出于可访问性目的对项目的描述。(QString)
用户角色
cpp
Qt::ItemDataRole //值 描述
Qt::UserRole //0x0100 可用于特定应用程序目的的第一个角色。
模型/视图 如何使用
为了直观的看到效果,我们可以把listview、treeview、tableview同时使用,对同一个模型进行显示。
创建视图
cpp
QListView* listView = new QListView;
QTreeView* treeView = new QTreeView;
QTableView* tableView = new QTableView;
//放进布局
QGridLayout* glayout = new QGridLayout(this);
glayout->addWidget(new QLabel("listView"),0,0);
glayout->addWidget(listView,1,0);
glayout->addWidget(new QLabel("treeView"),0,1);
glayout->addWidget(treeView,1,1);
glayout->addWidget(new QLabel("tableView"),2,0);
glayout->addWidget(tableView,3,0);
创建模型
创建model,并分别设置给view
cpp
QStandardItemModel* model = new QStandardItemModel(this);
listView->setModel(model);
treeView->setModel(model);
tableView->setModel(model);
添加项(数据)
- 接口
cpp
//追加
void appendColumn(const QList<QStandardItem *> &items)
void appendRow(const QList<QStandardItem *> &items)
void appendRow(QStandardItem *item)
//插入
void insertColumn(int column, const QList<QStandardItem *> &items)
bool insertColumn(int column, const QModelIndex &parent = QModelIndex())
void insertRow(int row, const QList<QStandardItem *> &items)
void insertRow(int row, QStandardItem *item)
bool insertRow(int row, const QModelIndex &parent = QModelIndex())
//设置项
void setItem(int row, int column, QStandardItem *item)
void setItem(int row, QStandardItem *item)
//添加空项
bool insertRow(int row, const QModelIndex &parent = QModelIndex())
virtual bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex())
bool insertColumn(int column, const QModelIndex &parent = QModelIndex())
virtual bool insertColumns(int column, int count, const QModelIndex &parent = QModelIndex())
给item添加子项
给模型中的QStandardItem添加子项,只对QTreeView起作用
cpp
QStandardItem* item = new QStandardItem("newItem");
auto* subItem = new QStandardItem("one");
subItem->appendRow(new QStandardItem("one_1"));
subItem->appendRow(new QStandardItem("one_2"));
item->appendRow(subItem);
item->appendRow(new QStandardItem("two"));
model->appendRow(item);
model->setItem(6,1,new QStandardItem("What"));

删除项
cpp
bool removeColumn(int column, const QModelIndex &parent = QModelIndex())
virtual bool removeColumns(int column, int count, const QModelIndex &parent = QModelIndex())
bool removeRow(int row, const QModelIndex &parent = QModelIndex())
virtual bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex())
//从model中移除指定的项(内存不会释放),项会以列表的方式返回,需要自己删除
QList<QStandardItem *> takeColumn(int column)
QStandardItem *takeHorizontalHeaderItem(int column)
QStandardItem *takeItem(int row, int column = 0)
QList<QStandardItem *> takeRow(int row)
QStandardItem *takeVerticalHeaderItem(int row)
获取项(查找、获取)
cpp
//查找给定列中使用给定标志匹配给定文本的项列表。
QList<QStandardItem *> findItems(const QString &text, Qt::MatchFlags flags = Qt::MatchExactly, int column = 0) const
//获取model的不可见根,注意:此项并不存在,是虚拟的,所以调用index是无效的
QStandardItem *invisibleRootItem() const
QStandardItem *item(int row, int column = 0) const
//通过下标获取项指针
QStandardItem *itemFromIndex(const QModelIndex &index) const
获取项的下标
cpp
//把元素添加进model之后,可以通过这个函数获取所在的下标
QModelIndex indexFromItem(const QStandardItem *item) const
virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override
清空模型
cpp
void clear()
表头数据(QTreeView、QTableView)
对于QTreeView、QTableView是可以显示表头的,接下来我们来设置表头。
cpp
//设置水平头
void setHorizontalHeaderItem(int column, QStandardItem *item)
void setHorizontalHeaderLabels(const QStringList &labels)
//设置垂直头
void setVerticalHeaderItem(int row, QStandardItem *item)
void setVerticalHeaderLabels(const QStringList &labels)
//获取头部项指针
QStandardItem *horizontalHeaderItem(int column) const
QStandardItem *verticalHeaderItem(int row) const
案例:
cpp
//逐个添加水平头项
model->setHorizontalHeaderItem(0,new QStandardItem("姓名"));
//一次全部添加
model->setHorizontalHeaderLabels(QStringList()<<"姓名"<<"年龄");
//添加垂直头项...略
设置角色数据
模型中的每个项都有一组与之相关联的数据元素,每个元素都有自己的角色。 **视图使用角色向模型指示它需要哪种类型的数据。 **
cpp
//给指定的index项设置对应角色的数据
virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override
virtual bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role = Qt::EditRole) override
//可以同时给指定的index项设置多个(角色,数据)
virtual bool setItemData(const QModelIndex &index, const QMap<int, QVariant> &roles) override
bool clearItemData(const QModelIndex &index)
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override
virtual QMap<int, QVariant> itemData(const QModelIndex &index) const override
demo:QQ列表

项目分析:
- 界面:列表视图(继承列表),每一项是一个窗口(继承项),每个窗口,采用不同布局,每个布局数据比较多,拆分
Contact.h
cpp
#ifndef CONTACT_H_
#define CONTACT_H_
#include <QString>
#include<qrandom.h>
class Contact
{
public:
enum Type { NONE, VIP, SVIP };
Contact(const QString& userName, const QString& nickName)
:m_userName(userName),
m_nickName(nickName),
m_imageName(":/Resource/images/defaultProfile.png"),
m_signature("这人很懒,啥都没写"),
m_type(QRandomGenerator::global()->bounded(3))
{
};
QString m_imageName;
QString m_userName;
QString m_nickName;
QString m_signature;
int m_type;
};
#endif //!CONTACT_H_
这个源文件储存数据信息
ContactItem.h
该类继承qwidget和QStandardItem,给每一项模型提供一系列接口(初始化界面,数据更新)
cpp
#ifndef _CONTACTITEM_H_
#define _CONTACTITEM_H_
#include "Contact.h"
#include <QWidget>
#include <QstandardItem>
class QLabel;
class ContactItem : public QWidget, public QStandardItem
{
public:
ContactItem(Contact* contact, QWidget* parent = nullptr);
void initUi();
void updataContactItem(Contact* contact);
private:
QLabel* m_profileLab{}; // 头像
QLabel* m_nicknameLab{}; //备注名字
QLabel* m_usernameLab{}; //用户名
QLabel* m_typeLab{}; //类型
QLabel* m_signatureLab{}; //签名
Contact* m_contact{};
};
#endif // !_CONTACTITEM_H_
ContactItem.cpp
cpp
#include "ContactItem.h"
#include <QLabel>
#include <QPushButton>
#include <QGridLayout>
#include <QHBoxLayout>
ContactItem::ContactItem(Contact* contact, QWidget* parent)
:QWidget(parent)
{
initUi();
updataContactItem(contact);
}
void ContactItem::initUi()
{
setSizeHint(QSize(width(), 60));
setFixedHeight(60);
//赋值
m_profileLab = new QLabel; //图片,设置固定大小
m_profileLab->setScaledContents(true);
m_profileLab->setFixedSize(40, 40);
m_usernameLab = new QLabel;
m_nicknameLab = new QLabel;
m_signatureLab = new QLabel;
m_typeLab = new QLabel;
//备注、真实名,身份水平布局
auto hboxLayout = new QHBoxLayout;
hboxLayout->addWidget(m_usernameLab);
hboxLayout->addWidget(m_nicknameLab);
hboxLayout->addWidget(m_typeLab);
hboxLayout->addStretch();
//全体网格布局
auto layout = new QGridLayout(this);
layout->addWidget(m_profileLab, 0, 0, 2, 1);
layout->addLayout(hboxLayout, 0, 1);
layout->addWidget(m_signatureLab, 1, 1);
}
void ContactItem::updataContactItem(Contact* contact)
{
m_profileLab->setPixmap(contact->m_imageName);
m_usernameLab->setText(contact->m_userName);
m_nicknameLab->setText(contact->m_nickName);
m_signatureLab->setText(contact->m_signature);
switch (contact->m_type) {
case Contact::NONE:
break;
case Contact::VIP:
m_typeLab->setPixmap(QPixmap(":/Resource/images/vip.png"));
break;
case Contact::SVIP:
m_typeLab->setPixmap(QPixmap(":/Resource/images/vip.png"));
break;
}
}
ContactViews
这个类赋值显示视图,继承QListView,添加一些接口,该类获取数据,添加项(注意储存数据的内存管理)
contactviews.h
cpp
#ifndef _CONTACTSVIEW_H_
#define _CONTACTSVIEW_H_
#include "ContactItem.h"
#include "Contact.h"
#include <QListView>
#include <QList>
#include <memory>
#include <QStandardItemModel>
#include <iostream>
//列表视图,继承
class ContactView : public QListView
{
public:
ContactView(QListView* parent = nullptr);
void initUi();
private:
std::vector<std::unique_ptr<Contact>> m_list{}; //注意内存管理
QStandardItemModel* m_model;
};
#endif // !_CONTACTSVIEW_H_
contact views.cpp
cpp
#include "ContactsView.h"
#include "ContactItem.h"
ContactView::ContactView(QListView* parent)
:QListView(parent),
m_model(new QStandardItemModel(this))
{
this->setModel(m_model); //把模型交给视图
initUi();
connect(this, &QListView::clicked, this, &ContactView::onClicked);
}
void ContactView::initUi()
{
//储存传入测试数据
m_list.emplace_back(new Contact("wy", "hhhh"));
m_list.emplace_back(new Contact("lt", "hhhh"));
m_list.emplace_back(new Contact("w", "hhhh"));
m_list.emplace_back(new Contact("y", "hhhh"));
m_list.emplace_back(new Contact("wyq", "hhhh"));
//遍历QList并且放入视图
for (auto& e : m_list) {
auto itemWidget = new ContactItem(e.get());
m_model->appendRow(itemWidget);
this->setIndexWidget(itemWidget->index(), itemWidget);
}
}