model 数据模型都是基于QAbstractItemModel类,数据无需存储在数据模型里,数据可以是其他类,文件、数据库或任何数据源。
mode类有 QAbstractListMode、QStringListMode、QStandardItemModel、QAbstractTableMode、QSqlQueryModel、QSqlTableModel、QFileSystemModel。
数据模型中存储数据的基本单元都是项(item),每个项有一个行号、一个列号、还有一个父项。列表和表格模式下,所有的项都有一个相同的顶层项。数据模型的存取的每个数据都有一个模型索引(model index),视图组件和代理都是通过模型索引来获取数据。QModelIndex表示模型索引的类。
1.Q_INVOKABLE virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const = 0;
2.Q_INVOKABLE virtual QModelIndex parent(const QModelIndex &child) const = 0;
3.Q_INVOKABLE virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const = 0;
4.Q_INVOKABLE virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
5.virtual QMap itemData(const QModelIndex &index) const;
同时也可以子类化QAbstractItemModel、QAbstractListModel或QAbstractTableModel来创建自定义模型。
要实现从这些基类派生出自定义的类,必须要重写rowCount()、columnCount()、data()
函数。
另外可编辑的模型需要实现setData()
和flags()
函数,后者返回值需包含Qt::ItemIsEditable
。
#ifndef MYBOOKTABLEMODEL_HPP
#define MYBOOKTABLEMODEL_HPP
#include
#include
#include
#include
//MSVC编译器界面显示乱码问题
#if _MSC_VER >= 1600
#pragma execution_character_set("utf-8")
#endif
//一本书的属性
struct Book
{
QString name = "None"; // 书名
QString publisher = "None"; // 出版社
QString type = "None"; // 类别
double price = 0.0; // 价格
};
//属性对应的表头(列表头)
const QStringList titles = {"书名", "出版社", "类别", "价格"};
class MyBookTableModel : public QAbstractTableModel
{
public:
explicit MyBookTableModel(QWidget* parent = Q_NULLPTR) : QAbstractTableModel(parent){}
~MyBookTableModel(){}
virtual int rowCount(const QModelIndex &/*parent*/ = QModelIndex()) const override
{
// 书本数量
return bookList.size();
}
virtual int columnCount(const QModelIndex &/*parent*/ = QModelIndex()) const override
{
// 书本属性值数量
return titles.size();
}
// 核心函数,View类从Model中取数据,传入的参数在之前的文章中有介绍
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const
{
if(!index.isValid() || index.column()>=columnCount() || index.row()>=rowCount()) return QVariant();
switch(role)
{
// 显示数据用
case Qt::DisplayRole:
switch(index.column())
{
case 0:
return bookList[index.row()].name;
break;
case 1:
return bookList[index.row()].publisher;
break;
case 2:
return bookList[index.row()].type;
break;
case 3:
return bookList[index.row()].price;
break;
default:
return QVariant();
break;
}
break;
// 对齐处理
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
break;
// 其余不处理
default:
return QVariant();
break;
}
}
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const
{
// 水平表头显示信息
switch(role)
{
case Qt::DisplayRole:
if(orientation==Qt::Horizontal && section>=0 && section<=columnCount())
return titles.at(section);
break;
default:
break;
}
return QAbstractItemModel::headerData(section, orientation, role);
}
// 编辑相关函数
virtual Qt::ItemFlags flags(const QModelIndex &index) const override
{
return Qt::ItemIsEditable | QAbstractTableModel::flags(index);
}
// 修改核心函数
virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override
{
if(role != Qt::EditRole || !index.isValid() || index.row()>=rowCount() || index.column()>=columnCount()) return false;
bool ok = false;
switch(index.column())
{
case 0: // 书名
bookList[index.row()].name = value.toString();
break;
case 1: // 出版社
bookList[index.row()].publisher = value.toString();
break;
case 2: // 类型
bookList[index.row()].type = value.toString();
break;
case 3: // 价格,并做简单输入判断
value.toDouble(&ok);
if(ok) bookList[index.row()].price = value.toDouble(&ok);
else return false;
break;
default:
return false;
break;
}
emit dataChanged(index, index);
return true;
}
private:
// 行修改函数:添加多行和删除多行
virtual bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override
{
// 起始行row超限时,修正到两端插入
if(row > rowCount()) row = rowCount();
if(row < 0) row = 0;
// 需要将修改部分的代码使用begin和end函数包起来
beginInsertRows(parent, row, row+count-1);
// 添加数据
for(int i = 0; i < count; ++i) bookList.insert(bookList.begin()+row+i, Book());
endInsertRows();
emit dataChanged(createIndex(row, 0), createIndex(row+count-1, columnCount()-1));
return true;
}
virtual bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex())
{
if(row < 0 || row >= rowCount() || row + count > rowCount()) return false;
// 需要将修改部分的代码使用begin和end函数包起来
beginRemoveRows(parent, row, row+count-1);
// 删除数据
for(int i = 0; i < count; ++i)
{
bookList.remove(row);
}
endRemoveRows();
return true;
}
public:
// 2个简单公有接口
// 最后添加一行
void appendRow()
{
// insertRow为内联函数,重写insertRows后即有
insertRow(rowCount());
}
// 最后删除一行
void popBack()
{
removeRow(rowCount()-1);
}
private:
// 存储书本信息的数组
QVector bookList;
};
#endif // MYTABLEMODEL_HPP
#include "mainwindow.h"
#include
#include "mytablemodel.hpp"
#include
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// MainWindow w;
// w.show();
// view和model联动
QTableView *tbl = new QTableView;
MyBookTableModel model;
tbl->setModel(&model);
// 隐藏滚动条
tbl->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
tbl->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
// 准备主界面
QWidget w;
// 2个按钮,用来改变行数,采用垂直布局
QVBoxLayout *v = new QVBoxLayout;
QPushButton *btn1 = new QPushButton("添加一行");
QPushButton *btn2 = new QPushButton("删除最末行");
qApp->connect(btn1, &QPushButton::clicked, [&](){
model.appendRow();
});
qApp->connect(btn2, &QPushButton::clicked, [&](){
model.popBack();
});
v->addWidget(btn1);
v->addWidget(btn2);
// 主界面的布局采用水平布局,左边是表格,右边是刚才的2个按键
QHBoxLayout *h = new QHBoxLayout;
h->addWidget(tbl);
h->addLayout(v);
// 设置主界面的布局
w.setLayout(h);
w.show();
// 当内容改变时自适应列宽
qApp->connect(&model, &MyBookTableModel::dataChanged,[&](){
tbl->resizeColumnsToContents();
int width = 0;
for(int i = 0; i < model.columnCount(); ++i)
{
width += tbl->columnWidth(i);
}
width += tbl->verticalHeader()->width();
// 设置表格最小宽度
tbl->setMinimumWidth(width);
// 设置主窗口大小以刷新界面布局
w.resize(w.minimumWidth(), 300);
});
// 最开始添加2行
model.appendRow();
model.appendRow();
return a.exec();
}
视图组件基于QAbstractItemView继承而来,有QListView、QTreeView、QTableView等。
而便利类如QlistWidget、Qtreewidget、QtableWidge 是前面3个类的子类,这些便利类没有数据模型,但实际上是用项的方式集成了数据模型的功能,所有便利类只适用小型数据的显示和编辑,缺乏对大型数据的灵活处理。
视图通过setModel函数设置数据模型,视图通过setItemDelegateForColumn或setItemDelegateForRow设置代理。
函数:
1.void setRootIndex(const QModelIndex &index) Q_DECL_OVERRIDE; //设置视图的根节点
2. virtual void setModel(QAbstractItemModel *model); //设置选择数据模型
3.QAbstractItemModel *model() const;
4.QModelIndex currentIndex() const;
5.QModelIndex rootIndex() const;
6.void setItemDelegateForRow(int row, QAbstractItemDelegate *delegate);
7.void setItemDelegateForColumn(int column, QAbstractItemDelegate *delegate);
信号:
1.void clicked(const QModelIndex &index);
Qt使用抽象类QAbstractItemDelegate(基类)来描述代理,Qt实现了两个代理类(子类),QStyledItemDelegate和QItemDelegate,这两个委托之中只能使用其中一个,其区别在于QItemDelegate总是使用一种默认的样式绘制数据项,而QStyledItemDelegate使用当前的样式来绘制数据项,通常使用的是QStyledItemDelegate。Qt默认使用QStyledItemDelegate。
4个函数,原型是固定的。需要重新定义,即一般继承QStyledItemDelegate类,在子类重新定义实现。
1.createeditor()函数创建用于编辑模型数据的widget组件,如一个qcombobox组件。
2.seteditordata()从数据模型获取数据,供widget组件进行编辑。
3.setmodeldata()将widget上的数据更新到数据模型。
4.updateeditorgeometry()用于给widget组件设置一个合适的大小。
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更新数据。
一般和qtreeview结合使用。
是可访问本机文件系统的数据模型,并且是采用单独的线程获取目录文件结构。需要调用setrootpath设置根目录。而QDirModel也可以获取文件目录和文件,但不是采用单独的线程。
函数:
// QFileSystemModel specific API
1. QModelIndex setRootPath(const QString &path);
基类是QAbstractListModel,一般和qlistView视图结合使用。
用于处理字符串列表的数据模型。
setstringlist函数初始化数据模型的字符串列表的内容。
stringlist函数返回数据模型内的字符串内容
1. QStringList stringList() const;
2. void setStringList(const QStringList &strings);
通常和QTableView结合使用。
是标准的以项数据(item data)为基础的标准数据模型,
QStandardItemModel :每个项是一个QStandardItem类的变量,用于存储项的数据、字体格式、对齐方式等。
常用的函数有:
QStandardItemModel :
1.QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
2.bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) Q_DECL_OVERRIDE;
3.QStandardItem *itemFromIndex(const QModelIndex &index) const;
4.QModelIndex indexFromItem(const QStandardItem *item) const;
5.void setItem(int row, int column, QStandardItem *item);
6.void setRowCount(int rows);
7.void insertRow(int row, const QList
8.QStandardItem *item(int row, int column = 0) const;
QStandardItem
1.inline QString text() const
{
return qvariant_cast(data(Qt::DisplayRole));
}
2. inline void setText(const QString &text);
3. explicit QStandardItem(const QString &text);
model函数是通过模型索引获取到模型
在QStandardItemModel可以取到QModelIndex 和QStandardItem ,并且两者可以itemFromIndex或者indexFromItem相互转换。
QItemSelectionModel 用于跟踪视图组件的单元格选择状态的类。
theModel = new QStandardItemModel(2,FixedColumnCount,this); //数据模型
theSelection = new QItemSelectionModel(theModel);//Item选择模型
//选择当前单元格变化时的信号与槽
connect(theSelection,SIGNAL(currentChanged(QModelIndex,QModelIndex)),
this,SLOT(on_currentChanged(QModelIndex,QModelIndex)));
//为tableView设置数据模型
ui->tableView->setModel(theModel); //设置数据模型
ui->tableView->setSelectionModel(theSelection);//设置选择模型
QStandardItemModel可以使用setItem函数再配合QStandardItem添加数据。
aItem=new QStandardItem(tmpList.at(j));//创建item
theModel->setItem(i-1,j,aItem); //为模型的某个行列位置设置Item
效率最高的做法是:
1.调用setRowCount设置行数。
2.调用setdata设置显示的数据。
pmodel2->setData(pmodel2->index(nrow, ncol), strText, Qt::EditRole);//效率提升很大
1.模型视图上控件的拖拽
https://blog.csdn.net/baidu_16370559/article/details/129124680?spm=1001.2014.3001.5502
2.QTableView 点击表头进行排序
前提是mode应该是继承自或者就是QAbstractItemModel或QStandardItemModel。
把QTableView 对象的setSortingEnabled()函数设为True后就可以了。点击表头排序的背后调用的是QTableView 类的sortByColumn()这个函数。sortByColumn内部调用的是mode的sort函数。所以mode必须是重写好的sort。
如果数据实时变化,自动排好序显示,上面这种排序办法不行。只有先把数据排好序,然后添加到mode,最后再显示。