【嵌入式——QT】Model/View
基本原理
GUI应用程序的一个很重要的功能是由用户在界面上编辑和修改数据,典型的如数据库应用程序,数据库应用程序中,用户在界面上执行各种操作,实际上是修改了界面组件所关联的数据库内的数据。将界面组件与所编辑的数据分离开来,又通过数据源的方式连接起来,是处理界面与数据的一种较好的方式,Qt使用Model/View结构来处理这种关系。
数据模型
QAbstractItemModel
- QAbstractListModel:QStringListModel,处理字符串列表数据的数据模型类;
- QAbstractProxyModel:QSortFilterProxyModel,提供排序和过滤功能的数据模型类;
- QAbstractTableModel:QSqlQueryModel,数据库SQL查询结构的数据模型类;QSqlTableModel,用于数据库的一个数据表的数据模型类;QSqlRelationalTableModel,关系型数据表的数据模型类;
- QStandardItemModel:每个项数据可以使任何数据类型;
- QFileSystemModel:文件系统的数据模型类;
视图组件
- QListView:用于显示单列的列表数据,适用于一维数据的操作;
- QTreeView:用于显示树状结构数据,适用于树状结构数据的操作;
- QTableView:用于显示表格状数据,适用于二维表格型数据的操作;
- QColumnView:用多个QListView显示树状层次结构,树状结构的一层用一个QListView显示;
- QHeaderView:提供行表头或列表头的视图组件,如QTableView的行表头和列表头;
代理
代理(Delegate)就是在视图组件上为编辑数据提供编辑器,如在表格组件中编辑一个单元格的数据时,缺省是使用一个QLineEdit编辑框。
代理负责从数据模型获取相应的数据,然后显示在编辑器里,修改数据后,又将其保存到数据模型中。
QAbstractItemDelegate是所有代理类的基类,它不能直接使用,它的一个子类,是Qt的视图组件缺省使用的代理类。
Model/View结构的一些概念
基本结构
数据模型中的存储数据的基本单元都是项(item),每个项都有一个行号,一个列号,还有一个父项。
模型索引
QModelIndex表示模型索引的类,模型索引提供数据存取的一个临时指针,用于通过数据模型提取或修改数据,因为模型内部数据的结构随时可能改变,所以模型索引是临时的。
行号和列号
数据模型的基本形式是用行和列定义的表格数据,要获得一个模型索引,必须提供行号、列号、父项的模型索引。
父项
当数据模型是列表或表格时,使用行号、列号存储数据比较直观,所有数据项的父项就是顶层项,当数据模型是树状结构时,情况比较复杂,一个节点可以有父节点,也可以是其他节点的父节点,在构造数据项的模型索引时,必须指定正确的行号、列号和父节点。
项的角色
在为数据模式的一个项设置数据时,可以赋予其不同项的角色的数据。
enum ItemDataRole
- Qt::DisplayRole:在视图组件中显示字符串,标准角色;
- Qt::ToolTipRole:鼠标提示消息;
- Qt::UserRole:可以自定义数据;
QFileSystemModel
QFileSystemModel提供了一个可用于访问本机文件系统的数据模型,QFileSystemModel和视图组件QTreeView结合使用,可以用目录树的形式显示本机上的文件系统,如同Windows的资源管理器一样,使用QFileSystemModel提供的接口函数,可以创建目录,删除目录,重命名目录,可以获得文件名称、目录名称、文件大小等参数,还可以获得文件的详细信息。
图示
代码示例
ModelViewDialog.h
#ifndef MODELVIEWDIALOG_H
#define MODELVIEWDIALOG_H
#include <QDialog>
#include <QFileSystemModel>
namespace Ui
{
class ModelViewDialog;
}
class ModelViewDialog : public QDialog
{
Q_OBJECT
public:
explicit ModelViewDialog(QWidget* parent = nullptr);
~ModelViewDialog();
private slots:
void on_treeView_clicked(const QModelIndex& index);
private:
Ui::ModelViewDialog* ui;
QFileSystemModel* model;
};
#endif // MODELVIEWDIALOG_H
ModelViewDialog.cpp
#include "ModelViewDialog.h"
#include "ui_ModelViewDialog.h"
ModelViewDialog::ModelViewDialog(QWidget* parent)
: QDialog(parent)
, ui(new Ui::ModelViewDialog)
{
ui->setupUi(this);
model = new QFileSystemModel(this);
//QDir::currentPath() 获得本机文件系统
model->setRootPath(QDir::currentPath());//设置根目录
ui->treeView->setModel(model);//设置数据模型
ui->listView->setModel(model);//设置数据模型
ui->tableView->setModel(model);//设置数据模型
connect(ui->treeView, SIGNAL(clicked(QModelIndex)), ui->listView, SLOT(setRootIndex(QModelIndex)));
connect(ui->treeView, SIGNAL(clicked(QModelIndex)), ui->tableView, SLOT(setRootIndex(QModelIndex)));
}
ModelViewDialog::~ModelViewDialog()
{
delete ui;
}
void ModelViewDialog::on_treeView_clicked(const QModelIndex& index)
{
ui->checkBox->setChecked(model->isDir(index));//是否目录
ui->labPath->setText(model->filePath(index));//返回节点的目录名或带路径的文件名
ui->labType->setText(model->type(index));//返回描述节点类型的文字
ui->labFileName->setText(model->fileName(index));//返回去除路径的文件夹名称或文件名
int sz = model->size(index)/1024;//如果节点是文件,返回文件大小的字节数,如果节点是文件夹,返回0
if(sz<1024) {
ui->labSize->setText(QString("%1 KB").arg(sz));
} else {
ui->labSize->setText(QString::asprintf("%.1f MB", sz/1024.0));
}
}
QStringListModel
QStringListModel用于处理字符串列表的数据模型,它可以作为QListView的数据模型,在界面上显示和编辑字符串列表。
图示
代码示例
QStringListModelDialog.h
#ifndef QSTRINGLISTMODELDIALOG_H
#define QSTRINGLISTMODELDIALOG_H
#include <QDialog>
#include <QStringListModel>
namespace Ui
{
class QStringListModelDialog;
}
class QStringListModelDialog : public QDialog
{
Q_OBJECT
public:
explicit QStringListModelDialog(QWidget* parent = nullptr);
~QStringListModelDialog();
private slots:
void on_pushButtonAdd_clicked();
void on_pushButtonInsert_clicked();
void on_pushButtonRemove_clicked();
void on_pushButtonClear_clicked();
void on_pushButtonShow_clicked();
void on_listView_clicked(const QModelIndex& index);
void on_pushButtonReset_clicked();
void on_pushButtonClearText_clicked();
private:
Ui::QStringListModelDialog* ui;
QStringListModel* model;
};
#endif // QSTRINGLISTMODELDIALOG_H
QStringListModelDialog.cpp
#include "QStringListModelDialog.h"
#include "ui_QStringListModelDialog.h"
QStringListModelDialog::QStringListModelDialog(QWidget* parent)
: QDialog(parent)
, ui(new Ui::QStringListModelDialog)
{
ui->setupUi(this);
QStringList theStrList;
theStrList<<"北京"<<"上海"<<"天津"<<"河北"<<"河南"<<"山东";
model = new QStringListModel(this);
//将一个字符串列表的内容作为数据模型的初始化数据内容
model->setStringList(theStrList);
ui->listView->setModel(model);
//双击 或 选择并单击后进入编辑状态
ui->listView->setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::SelectedClicked);
}
QStringListModelDialog::~QStringListModelDialog()
{
delete ui;
}
void QStringListModelDialog::on_pushButtonAdd_clicked()
{
model->insertRow(model->rowCount());//在尾部插入一行
QModelIndex index = model->index(model->rowCount()-1, 0);
model->setData(index, "new item", Qt::DisplayRole);
ui->listView->setCurrentIndex(index);//设置当前选中的行
}
void QStringListModelDialog::on_pushButtonInsert_clicked()
{
QModelIndex index = ui->listView->currentIndex();
model->insertRow(index.row());
model->setData(index, "inserted item", Qt::DisplayRole);
ui->listView->setCurrentIndex(index);
}
void QStringListModelDialog::on_pushButtonRemove_clicked()
{
QModelIndex index = ui->listView->currentIndex();
model->removeRow(index.row());
}
void QStringListModelDialog::on_pushButtonClear_clicked()
{
model->removeRows(0, model->rowCount());
}
void QStringListModelDialog::on_pushButtonShow_clicked()
{
QStringList tmpList = model->stringList();
ui->plainTextEdit->clear();
for(int i=0; i<tmpList.count(); i++) {
ui->plainTextEdit->appendPlainText(tmpList.at(i));
}
}
void QStringListModelDialog::on_listView_clicked(const QModelIndex& index)
{
ui->label->setText(QString::asprintf("current:row=%d,column=%d", index.row(), index.column()));
}
void QStringListModelDialog::on_pushButtonReset_clicked()
{
model->removeRows(0, model->rowCount());
QStringList theStrList;
theStrList<<"北京"<<"上海"<<"天津"<<"河北"<<"河南"<<"山东";
model->setStringList(theStrList);
}
void QStringListModelDialog::on_pushButtonClearText_clicked()
{
ui->plainTextEdit->clear();
}
QStandardItemModel
QStandardItemModel是标准的以项数据为基础的标准数据模型类,通常与QTableView组合成Model/View结构。实现通用的二维数据的管理功能。
图示
代码示例
QStandardItemModelDialog.h
#ifndef QSTANDARDITEMMODELDIALOG_H
#define QSTANDARDITEMMODELDIALOG_H
#include <QDialog>
#include <QLabel>
#include <QStandardItemModel>
#include <QItemSelectionModel>
#define FixedColumnCount 6
namespace Ui
{
class QStandardItemModelDialog;
}
class QStandardItemModelDialog : public QDialog
{
Q_OBJECT
public:
explicit QStandardItemModelDialog(QWidget* parent = nullptr);
~QStandardItemModelDialog();
void initModelFromStringList(QStringList& fFileContent);
public slots:
void on_currentChanged(const QModelIndex& current, const QModelIndex& previous);
private slots:
void on_pushButtonOpen_clicked();
void on_pushButtonAdd_clicked();
void on_pushButtonInsert_clicked();
void on_pushButtonRemove_clicked();
void on_pushButtonLeft_clicked();
void on_pushButtonMid_clicked();
void on_pushButtonRight_clicked();
void on_pushButtonBold_clicked();
void on_pushButtonSaveAs_clicked();
private:
Ui::QStandardItemModelDialog* ui;
QStandardItemModel* model;
QLabel* labCurFile;
QLabel* labCellPos;
QLabel* labCellText;
QItemSelectionModel* theSelection;
};
#endif // QSTANDARDITEMMODELDIALOG_H
QStandardItemModelDialog.cpp
#include "QStandardItemModelDialog.h"
#include "ui_QStandardItemModelDialog.h"
#include <QAbstractItemView>
#include <QFileDialog>
#include <QTextStream>
QStandardItemModelDialog::QStandardItemModelDialog(QWidget* parent)
: QDialog(parent)
, ui(new Ui::QStandardItemModelDialog)
{
ui->setupUi(this);
model = new QStandardItemModel(0, FixedColumnCount, this);
theSelection = new QItemSelectionModel(model);
connect(theSelection, SIGNAL(currentChanged(QModelIndex, QModelIndex)), this, SLOT(on_currentChanged(QModelIndex, QModelIndex)));
ui->tableView->setModel(model);
ui->tableView->setSelectionModel(theSelection);
ui->tableView->setSelectionMode(QAbstractItemView::ExtendedSelection);
ui->tableView->setSelectionBehavior(QAbstractItemView::SelectItems);
}
QStandardItemModelDialog::~QStandardItemModelDialog()
{
delete ui;
}
void QStandardItemModelDialog::initModelFromStringList(QStringList& fFileContent)
{
int rowCnt = fFileContent.count();
model->setRowCount(rowCnt);
QString header = fFileContent.at(0);
//用一个或多个空白字符分隔 SkipEmptyParts函数会跳过任何空的部分
QStringList headerList = header.split(QRegExp("\\s+"), QString::SkipEmptyParts);
model->setHorizontalHeaderLabels(headerList);
QStandardItem* item;
QStringList tmpList;
int j;
for(int i=1; i<rowCnt; i++) {
QString aLinText = fFileContent.at(i);
tmpList = aLinText.split(QRegExp("\\s+"), QString::SkipEmptyParts);
for(j=0; j<FixedColumnCount-1; j++) {
item = new QStandardItem(tmpList.at(j));
model->setItem(i-1, j, item);
}
item = new QStandardItem(headerList.at(j));
item->setCheckable(true);
if(tmpList.at(j) == "0") {
item->setCheckState(Qt::Unchecked);
} else {
item->setCheckState(Qt::Checked);
}
model->setItem(i-1, j, item);
}
}
void QStandardItemModelDialog::on_currentChanged(const QModelIndex& current, const QModelIndex& previous)
{
if(current.isValid()) {
// labCellPos->setText(QString::asprintf("current: row=%d , col=%d", current.row(), current.column()));
QStandardItem* item = model->itemFromIndex(current);
// this->labCellText->setText("text:"+item->text());
// QFont font = item->font();
}
}
void QStandardItemModelDialog::on_pushButtonOpen_clicked()
{
QString curPath = QCoreApplication::applicationDirPath();
QString aFileName = QFileDialog::getOpenFileName(this, u8"打开文件", curPath);
if(aFileName.isEmpty()) {
return;
}
QStringList fFileContent;
QFile afile(aFileName);
if(afile.open(QIODevice::ReadOnly |QIODevice::Text)) {
QTextStream aStream(&afile);
ui->plainTextEdit->clear();
while(!aStream.atEnd()) {
QString str = aStream.readLine();
ui->plainTextEdit->appendPlainText(str);
fFileContent.append(str);
}
afile.close();
// labCurFile->setText("currentFile:"+aFileName);
initModelFromStringList(fFileContent);
}
}
void QStandardItemModelDialog::on_pushButtonAdd_clicked()
{
QList<QStandardItem*> itemList;
QStandardItem* item;
//不包含最后一列
for(int i=0; i<FixedColumnCount-1; i++) {
item = new QStandardItem("0");
// item->setCheckable(true);
itemList<<item;
}
QString str = model->headerData(model->columnCount()-1, Qt::Horizontal, Qt::DisplayRole).toString();
item = new QStandardItem(str);
item->setCheckable(true);
itemList<<item;
model->insertRow(model->rowCount(), itemList);
QModelIndex curIndex = model->index(model->rowCount()-1, 0);
theSelection->clearSelection();
theSelection->setCurrentIndex(curIndex, QItemSelectionModel::Select);
}
void QStandardItemModelDialog::on_pushButtonInsert_clicked()
{
QList<QStandardItem*> itemList;
QStandardItem* item;
//不包含最后一列
for(int i=0; i<FixedColumnCount-1; i++) {
item = new QStandardItem("0");
// item->setCheckable(true);
itemList<<item;
}
QString str = model->headerData(model->columnCount()-1, Qt::Horizontal, Qt::DisplayRole).toString();
item = new QStandardItem(str);
item->setCheckable(true);
itemList<<item;
QModelIndex curIndex = theSelection->currentIndex();
int curRow = curIndex.row();
model->insertRow(curRow, itemList);
curIndex = model->index(curRow, 0);
theSelection->clearSelection();
theSelection->setCurrentIndex(curIndex, QItemSelectionModel::Select);
}
void QStandardItemModelDialog::on_pushButtonRemove_clicked()
{
QModelIndex curIndex = theSelection->currentIndex();
if(curIndex.row() == model->rowCount()-1) {
model->removeRow(curIndex.row());
} else {
model->removeRow(curIndex.row());
theSelection->setCurrentIndex(curIndex, QItemSelectionModel::Select);
}
}
void QStandardItemModelDialog::on_pushButtonLeft_clicked()
{
if(!theSelection->hasSelection()) {
return;
}
QModelIndexList selectedIndex = theSelection->selectedIndexes();
for(int i=0; i<selectedIndex.count(); i++) {
QModelIndex aIndex = selectedIndex.at(i);
QStandardItem* item = model->itemFromIndex(aIndex);
item->setTextAlignment(Qt::AlignLeft);
}
}
void QStandardItemModelDialog::on_pushButtonMid_clicked()
{
if(!theSelection->hasSelection()) {
return;
}
QModelIndexList selectedIndex = theSelection->selectedIndexes();
for(int i=0; i<selectedIndex.count(); i++) {
QModelIndex aIndex = selectedIndex.at(i);
QStandardItem* item = model->itemFromIndex(aIndex);
item->setTextAlignment(Qt::AlignCenter);
}
}
void QStandardItemModelDialog::on_pushButtonRight_clicked()
{
if(!theSelection->hasSelection()) {
return;
}
QModelIndexList selectedIndex = theSelection->selectedIndexes();
for(int i=0; i<selectedIndex.count(); i++) {
QModelIndex aIndex = selectedIndex.at(i);
QStandardItem* item = model->itemFromIndex(aIndex);
item->setTextAlignment(Qt::AlignRight);
}
}
void QStandardItemModelDialog::on_pushButtonBold_clicked()
{
if(!theSelection->hasSelection()) {
return;
}
QModelIndexList selectedIndex = theSelection->selectedIndexes();
for(int i=0; i<selectedIndex.count(); i++) {
QModelIndex aIndex = selectedIndex.at(i);
QStandardItem* item = model->itemFromIndex(aIndex);
QFont font = item->font();
font.setBold(Qt::Checked);
item->setFont(font);
}
}
void QStandardItemModelDialog::on_pushButtonSaveAs_clicked()
{
QString curPath = QCoreApplication::applicationDirPath();
QString aFileName = QFileDialog::getSaveFileName(this, u8"选择一个文件", curPath);
if(aFileName.isEmpty()) {
return;
}
QFile file(aFileName);
if(!(file.open(QIODevice::ReadWrite | QIODevice::Text| QIODevice::Truncate))) {
return;
}
QTextStream aStream(&file);
QStandardItem* item;
int i, j;
QString str;
ui->plainTextEdit->clear();
for(int i=0; i<model->columnCount(); i++) {
item = model->horizontalHeaderItem(i);
str = str +item->text()+"\t\t";
}
aStream<<str<<"\n";
ui->plainTextEdit->appendPlainText(str);
for(i=0; i<model->rowCount(); i++) {
str="";
for(j=0; j<model->columnCount()-1; j++) {
item = model->item(i, j);
str=str+item->text()+QString::asprintf("\t\t");
}
item = model->item(i, j);
if(item->checkState() == Qt::Checked) {
str = str+"1";
} else {
str = str+"0";
}
ui->plainTextEdit->appendPlainText(str);
aStream<<str<<"\n";
}
}
自定义代理
QAbstractItemDelegate是所有代理类的抽象基类,QStyledItemDelegate是视图组件使用的缺省的代理类,QItemDelegate也是类似功能的类。
QStyledItemDelegate和QItemDelegate的差别在于前者可以使用样式表设置来绘制组件,因此建议使用QStyledItemDelegate作为自定义代理类组件的基类。
无论从QStyledItemDelegate还是QItemDelegate继承设计自定义代理组件,都会必须实现这四个函数。
- createEditor:创建用于编辑模型数据的widget组件,如QSpinBox组件;
- setEditorData:从数据模型获取数据,供widget组件进行编辑;
- setModelData:将widget上的数据更新到数据模型;
- updateEditorGeometry:用于给widget组件设置一个合适的大小;
详见我之前一段时间所写的文章 文章地址