目录
样例001:现有模型中使用视图Using views with an existing model
样例002:使用模型索引
样例003:使用模型
样例004:使用模型的多个视图
样例005:委托实例
样例006:选择项目
样例006:读取选择状态
样例007:更新选择
样例008:选择模型中的所有项目
样例008:创建新数据模型
样例010:项目视图便利类之列表控件:List widgets
样例011:项目视图便利类之列表控件:Tree widgets
样例012:项目视图便利类之表格控件:QTableWidget
本篇继上一篇QT官方文档的QT Model/View 编程,实现了上一篇中全部的实例。这些样例并不能代表QT的MVC编程全貌。旨在更清晰的阐述上一篇教程的内容。所以这两篇内容并不能真正的教会我们怎样利用QT的MVC编程去实现真实的开发项目,只是让我们了解学习了应该怎样去开始工作。这两篇内容最大的用处是教会我们怎样理解QT中MVC编程模式,QT给了我们什么工具。具体的开发需要我们参考QT给我们的具体实现工具类。
QFileSystemModel是一个维护目录内容信息的模型。因此,它本身不保存任何数据项,而只是表示本地归档系统上的文件和目录。
QFileSystemModel提供了一个现成的模型来进行实验,并且可以轻松地配置为使用现有数据。使用此模型,我们可以展示如何设置模型以与现成视图一起使用,并探索如何使用模型索引操作数据。
QListView和QTreeView类是最适合与QFileSystemModel一起使用的视图。下面的示例在树视图中显示目录的内容,在列表视图中的相同信息旁边。视图共享用户的选择,以便在两个视图中突出显示所选项目。
我利用 QT Creator 创建了Application(QT) ——》 Qt widgets Application项目,如图:
利用向导模式创建GUI程序,旨在使用向导创建CMake项目文件。最后我把本样例不需要的文件全部给删除了。如下图:
本样例代码全部集中于main主文件。main.cpp代码:
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//创建一个QSplitter对象,以承载显示程序。
QSplitter *splitter = new QSplitter;
//创建文件系统模型对象。
QFileSystemModel *model = new QFileSystemModel;
//给模型加载数据,也就是当前文件系统路径信息。
//这个现成的专用类QFileSystemModel,已帮我们做了所有的一切,我说的是数据。
model->setRootPath(QDir::currentPath());
//创建一个树视图对象
QTreeView *tree = new QTreeView(splitter);
//视图对象加载模型
tree->setModel(model);
//给视图对象设置模型索引
tree->setRootIndex(model->index(QDir::currentPath()));
//创建一个列表视图对象。
//这里所做的工作和上面tree对象做的一样。
QListView *list = new QListView(splitter);
list->setModel(model);
list->setRootIndex(model->index(QDir::currentPath()));
//给我们的窗体部件加上程序标头
splitter->setWindowTitle("Two views onto the same file system model");
splitter->show();
return a.exec();
}
编译运行和的结果:
视图的构造方式与其他小部件相同。设置一个视图来显示模型中的项目,只需调用其setModel()函数,并将目录模型作为参数即可。我们通过在每个视图上调用setRootIndex()函数过滤模型提供的数据,从文件系统模型中为当前目录传递合适的模型索引。
本例中使用的index()函数对于QFileSystemModel是唯一的;我们为它提供一个目录,它返回一个模型索引。模型索引在模型类中讨论。
该函数的其余部分只显示拆分器小部件中的视图,并运行应用程序的事件循环。
总结:本实例给我们展示了,QFileSystemModel模型被设置为使用来自某个文件系统的数据。对setRootPath()的调用告诉模型文件系统上的哪个驱动器将公开给视图。我们创建了两个视图,以便以两种不同的显示方式检查模型中的项目。通过本实例,让我们了解了M(模型)与V(视图)是怎样工作的。本样例还没有给我们展示委托(C,控制器)的工作方式。
为了演示如何使用模型索引从模型中检索数据,我们设置了一个没有视图的QFileSystemModel,并在小部件中显示文件和目录的名称。虽然这并没有显示使用模型的正常方式,但它演示了模型在处理模型索引时使用的约定。
QFileSystemModel加载是异步的,以最小化系统资源使用。在处理这个模型时,我们必须考虑到这一点。
这个样例在QT官方文档中是不完整的。代码中使用了lambda表达式。我们是没有办法以最小代码去实现的。这里我依旧创建了GUI工程,只不过这次没有删除自动生成的GUI文件类。同时,我依旧按样例001的代码思路实现了视图,但这在本样例中并不重要。我把正常的函数替换了lambda表达式。也只有这样我们的程序才能运行起来。
这是整个项目文件,包括Widget.h文件代码:
#ifndef WIDGET_H
#define WIDGET_H
#include
#include
#include
#include
#include
#include
#include
class Widget : public QDialog
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
QVBoxLayout *layout = new QVBoxLayout;
//创建一个系统文件模型
QFileSystemModel *model = new QFileSystemModel;
//创建一个QSplitter对象,以承载显示程序。
QSplitter *splitter = new QSplitter;
//创建一个树视图对象
QTreeView *tree;
public slots:
void query_numCount(const QString &directory);
};
#endif // WIDGET_H
这里是Widget.pp源码:
#include "widget.h"
Widget::Widget(QWidget *parent)
: QDialog(parent)
{
this->layout->addWidget(this->splitter);
this->setLayout(this->layout);
connect(model, SIGNAL(directoryLoaded(const QString &)),this,SLOT(query_numCount(const QString &)));
model->setRootPath(QDir::currentPath());
//创建一个树视图对象
this->tree = new QTreeView(this->splitter);
//视图对象加载模型
this->tree->setModel(model);
//给视图对象设置模型索引
this->tree->setRootIndex(model->index(QDir::currentPath()));
//this->splitter->show();
}
void Widget::query_numCount(const QString &directory)
{
QModelIndex parentIndex = model->index(directory);
int numRows = model->rowCount(parentIndex);
for (int row = 0; row < numRows; ++row)
{
QModelIndex index = model->index(row, 0, parentIndex);
QString text = model->data(index, Qt::DisplayRole).toString();
// Display the text in a widget.
qDebug()<
这里是main.cpp文件源码:
#include
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
上述示例演示了用于从模型中检索数据的基本原理:
样例运行的效果:
在样例001、002中其实已经实现了模型的使用。作为官方教程的样例,我还是依旧照做实现了。
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// Unindented for quoting purposes:
QStringList numbers;
numbers << "One" << "Two" << "Three" << "Four" << "Five";
QAbstractItemModel *model = new QStringListModel(numbers);
QListView *view = new QListView;
view->setModel(model);
view->show();
return a.exec();
}
这个样例其实在样例001中也已经实现,不多说,开始实验:
#include
#include
#include
#include "mytablemodel.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//这里我们隐藏细节,不必理会做了些什么
//这是我们准备好的模型及数据
myTableModel * tableModel = new myTableModel;
//这里是本样例的重点。
QTableView *firstTableView = new QTableView;
QTableView *secondTableView = new QTableView;
firstTableView->setModel(tableModel);
secondTableView->setModel(tableModel);
firstTableView->show();
secondTableView->show();
return a.exec();
}
这里实现的委托使用QSpinBox提供编辑工具,主要用于显示整数的模型。尽管我们为此设置了一个自定义的基于整数的表模型,但我们可以很容易地使用QStandardItemModel,因为自定义委托控制数据输入。我们构造一个表视图来显示模型的内容,这将使用自定义委托进行编辑。
我们从QStyledItemDelegate子类化委托,因为我们不想编写自定义显示函数。然而,我们仍然必须提供管理编辑器小部件的功能。
请注意,构造委托时未设置编辑器小部件。我们只在需要时构造编辑器小部件。
在本例中,当表视图需要提供一个编辑器时,它要求委托提供一个适用于正在修改的项的编辑器小部件。createEditor()函数提供了代理设置合适的小部件所需的一切
注意,我们不需要保留指向编辑器小部件的指针,因为视图负责在不再需要时销毁它。
我们在编辑器上安装委托的默认事件过滤器,以确保它提供用户期望的标准编辑快捷方式。可以向编辑器添加其他快捷方式,以允许更复杂的行为;
该视图通过调用我们稍后为这些目的定义的函数来确保编辑器的数据和几何体设置正确。我们可以根据视图提供的模型索引创建不同的编辑器。例如,如果我们有一列整数和一列字符串,我们可以返回QSpinBox或QLineEdit,这取决于正在编辑的列。
委托必须提供将模型数据复制到编辑器中的函数。在本例中,我们读取存储在显示角色中的数据,并相应地设置数字显示框中的值。
在本例中,我们知道编辑器小部件是一个数字显示框,但我们可以为模型中不同类型的数据提供不同的编辑器,在这种情况下,我们需要在访问其成员函数之前将小部件转换为适当的类型。
当用户完成编辑数字显示框中的值时,视图通过调用setModelData()函数要求代理将编辑的值存储在模型中。
由于视图管理委托的编辑器小部件,我们只需要使用提供的编辑器内容更新模型。在这种情况下,我们确保数字显示框是最新的,并使用指定的索引使用其包含的值更新模型。
标准QStyledItemDelegate类通过发出closeEditor()信号通知视图完成编辑。该视图确保编辑器小部件被关闭和销毁。在这个例子中,我们只提供简单的编辑工具,所以我们永远不需要发出这个信号。
所有对数据的操作都是通过QabstracteModel提供的接口执行的。这使得委托在很大程度上独立于它所处理的数据类型,但为了使用某些类型的编辑器小部件,必须做出一些假设。在本例中,我们假设模型始终包含整数值,但我们仍然可以将此委托用于不同类型的模型,因为QVariant为意外数据提供了合理的默认值。
代理负责管理编辑器的几何体。创建编辑器时,以及项目在视图中的大小或位置更改时,必须设置几何图形。幸运的是,视图提供了视图选项对象中所有必要的几何体信息。
在这种情况下,我们只使用项目矩形中的视图选项提供的几何信息。呈现具有多个元素的项的委托不会直接使用项矩形。它将相对于项中的其他元素定位编辑器。
在模型类中,我们不能忽略了flags函数,如果忽略,我们将无法触发编辑功能:
Qt::ItemFlags myTableModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
return Qt::ItemIsEnabled|Qt::ItemIsEditable;
}
这里是主main.cpp代码:
#include
#include
#include "mytablemodel.h"
#include "spinboxdelegate.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//这里我们隐藏细节,不必理会做了些什么
//这是我们准备好的模型及数据
myTableModel * tableModel = new myTableModel;
//创建委托
SpinBoxDelegate * myDelegate = new SpinBoxDelegate;
//创建视图
QTableView *firstTableView = new QTableView;
firstTableView->setModel(tableModel);
firstTableView->setItemDelegate(myDelegate);
firstTableView->setItemDelegateForColumn(0,myDelegate);//将表格的第一列进行委任。
firstTableView->show();
return a.exec();
}
这里是委托类的代码:
#include "spinboxdelegate.h"
SpinBoxDelegate::SpinBoxDelegate(QObject *parent)
: QStyledItemDelegate{parent}
{
}
QWidget *SpinBoxDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &/* option */,
const QModelIndex &/* index */) const
{
qDebug()<<"我已经运行";
QSpinBox *editor = new QSpinBox(parent);
//editor->setFrame(false);
editor->setMinimum(10);
editor->setMaximum(100);
return editor;
}
void SpinBoxDelegate::setEditorData(QWidget *editor,
const QModelIndex &index) const
{
int value = index.model()->data(index, Qt::EditRole).toInt();
QSpinBox *spinBox = static_cast(editor);
spinBox->setValue(value);
}
void SpinBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const
{
QSpinBox *spinBox = static_cast(editor);
spinBox->interpretText();
int value = spinBox->value();
model->setData(index, value, Qt::EditRole);
}
void SpinBoxDelegate::updateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem &option,
const QModelIndex &/* index */) const
{
editor->setGeometry(option.rect);
}
关于模型类,我们以最简单的方式,实现了数据模型,代码如下:
#include "mytablemodel.h"
myTableModel::myTableModel(QObject *parent)
: QAbstractTableModel(parent)
{
}
QVariant myTableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if(role!=Qt::DisplayRole)
return QVariant();
if(orientation==Qt::Vertical)
return QVariant(section);
if(orientation==Qt::Horizontal)
{
if(section==0) return QVariant(mHeadName.value(0));
if(section==1) return QVariant(mHeadName.value(1));
if(section==2) return QVariant(mHeadName.value(2));
if(section==3) return QVariant(mHeadName.value(3));
}
return 0;
}
int myTableModel::rowCount(const QModelIndex &parent) const
{
return modelData.count();
}
int myTableModel::columnCount(const QModelIndex &parent) const
{
return 4;
}
QVariant myTableModel::data(const QModelIndex &index, int role) const
{
if(!index.isValid())
return QVariant();
if(role == Qt::DisplayRole || role == Qt::EditRole)
{
const int row=index.row();
switch(index.column())
{
case 0: return modelData.at(row).name;
case 1: return (modelData.at(row).sex==0)?"woman":"man";
case 2:return modelData.at(row).age;
case 3:return modelData.at(row).score;
}
}
return QVariant();
}
Qt::ItemFlags myTableModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
return Qt::ItemIsEnabled|Qt::ItemIsEditable;
}
本样例只是在样例005的基础上加了几行选择项目代码,并没有大的变动。这个功能相对于比较简单。
因为本示例只改动了样例005的main.cpp文件,其他文件未改动。这里贴出了main.cpp代码。其他参照样例005代码。
#include
#include
#include
#include "mytablemodel.h"
#include "spinboxdelegate.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//这里我们隐藏细节,不必理会做了些什么
//这是我们准备好的模型及数据
myTableModel * tableModel = new myTableModel;
//创建委托
SpinBoxDelegate * myDelegate = new SpinBoxDelegate;
//创建视图
QTableView *firstTableView = new QTableView;
//视图加载模型
firstTableView->setModel(tableModel);
//获取视图的默认选择模型
QItemSelectionModel *selectionModel = firstTableView->selectionModel();
//选择视图将显示在表左上角的一些项。为此,我们需要检索与要选择区域中的左上和右下项目对应的模型索引:
QModelIndex topLeft;
QModelIndex bottomRight;
topLeft = tableModel->index(0, 0, QModelIndex());
bottomRight = tableModel->index(5, 2, QModelIndex());
QItemSelection selection(topLeft, bottomRight);
selectionModel->select(selection, QItemSelectionModel::Select);
topLeft = tableModel->index(0, 0, QModelIndex());
bottomRight = tableModel->index(5, 2, QModelIndex());
//视图加载委托
firstTableView->setItemDelegate(myDelegate);
//firstTableView->setItemDelegateForColumn(0,myDelegate);//将表格的第一列进行委任。
firstTableView->setGeometry(100,100,500,400);
firstTableView->show();
return a.exec();
}
可以使用selectedIndexes()函数读取存储在选择模型中的模型索引。这将返回一个未排序的模型索引列表,只要我们知道它们用于哪个模型,我们就可以对其进行迭代:
本样例使用基于范围的for循环来迭代和修改与选择模型返回的索引相对应的项。
选择模型发出信号以指示选择中的变化。这些组件通知其他组件对整个选择和项目模型中当前关注的项目的更改。我们可以将selectionChanged()信号连接到一个插槽,并检查模型中在选择更改时选择或取消选择的项目。该槽由两个QItemSelection对象调用:一个包含对应于新选择项的索引列表;另一个包含与新取消选择的项相对应的索引。
我们提供了一个接收selectionChanged()信号的插槽,用字符串填充所选项目,并清除取消选择项目的内容。
本例中我们从新构建了项目,加入window组件来实验选择变更信号。
本实例内容添加的比较多,包括选择模型和选择模型事件槽。下面是实验结果和重要代码(完整代码在本篇内容最后提供连接,所有代码暂不考虑代码的健壮性):
这是main.cpp代码:
#include
#include
#include
#include
#include "mytablemodel.h"
#include "spinboxdelegate.h"
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.setGeometry(100,100,700,500);
//这里我们隐藏细节,不必理会做了些什么
//这是我们准备好的模型及数据
myTableModel * tableModel = new myTableModel;
//创建委托
SpinBoxDelegate * myDelegate = new SpinBoxDelegate;
//创建视图
QTableView *firstTableView = new QTableView;
//视图加载模型
firstTableView->setModel(tableModel);
//获取视图的默认选择模型
QItemSelectionModel *selectionModel = firstTableView->selectionModel();
//选择视图将显示在表左上角的一些项。为此,我们需要检索与要选择区域中的左上和右下项目对应的模型索引:
QModelIndex topLeft;
QModelIndex bottomRight;
topLeft = tableModel->index(0, 0, QModelIndex());
bottomRight = tableModel->index(5, 2, QModelIndex());
QItemSelection selection(topLeft, bottomRight);
selectionModel->select(selection, QItemSelectionModel::Select);
const QModelIndexList indexes = selectionModel->selectedIndexes();
for (const QModelIndex &index : indexes) {
QString text = QString("(%1,%2)").arg(index.row()).arg(index.column());
tableModel->setData(index, text); //这里并没有设置数据的意思,只是得出我们选择的定位。
}
w.connect(selectionModel,SIGNAL(currentChanged(QModelIndex,QModelIndex)),&w,SLOT(changeCurrent(const QModelIndex &,const QModelIndex &)));
//视图加载委托
//firstTableView->setItemDelegate(myDelegate);
firstTableView->setItemDelegateForColumn(3,myDelegate);//将表格的第一列进行委任。
//w.layout()->addWidget(firstTableView);
w.setCentralWidget(firstTableView);
w.show();
return a.exec();
}
这里是mainwindow.cpp代码:
void MainWindow::changeCurrent(const QModelIndex ¤t,
const QModelIndex &previous)
{
QStatusBar *myStatusBar = this->statusBar();
myStatusBar->showMessage(
tr("Moved from (%1,%2) to (%3,%4)")
.arg(previous.row()).arg(previous.column())
.arg(current.row()).arg(current.column()));
}
完整代码在本篇内容最后连接。
#include
#include
#include
#include
#include "mytablemodel.h"
#include "spinboxdelegate.h"
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.setGeometry(100,100,700,500);
//这里我们隐藏细节,不必理会做了些什么
//这是我们准备好的模型及数据
myTableModel * tableModel = new myTableModel;
//创建委托
SpinBoxDelegate * myDelegate = new SpinBoxDelegate;
//创建视图
QTableView *firstTableView = new QTableView;
//视图加载模型
firstTableView->setModel(tableModel);
//获取视图的默认选择模型
QItemSelectionModel *selectionModel = firstTableView->selectionModel();
//选择视图将显示在表左上角的一些项。为此,我们需要检索与要选择区域中的左上和右下项目对应的模型索引:
QModelIndex topLeft;
QModelIndex bottomRight;
topLeft = tableModel->index(0, 0, QModelIndex());
bottomRight = tableModel->index(5, 2, QModelIndex());
QItemSelection selection(topLeft, bottomRight);
selectionModel->select(selection, QItemSelectionModel::Select);
selectionModel->select(selection, QItemSelectionModel::Toggle);
const QModelIndexList indexes = selectionModel->selectedIndexes();
for (const QModelIndex &index : indexes) {
QString text = QString("(%1,%2)").arg(index.row()).arg(index.column());
tableModel->setData(index, text); //这里并没有设置数据的意思,只是得出我们选择的定位。
}
w.connect(selectionModel,SIGNAL(currentChanged(QModelIndex,QModelIndex)),&w,SLOT(changeCurrent(const QModelIndex,const QModelIndex)));
//视图加载委托
//firstTableView->setItemDelegate(myDelegate);
firstTableView->setItemDelegateForColumn(3,myDelegate);//将表格的第3列进行委任。
//w.layout()->addWidget(firstTableView);
w.setCentralWidget(firstTableView);
w.show();
return a.exec();
}
请参照上例理解:
#include
#include
#include
#include
#include "mytablemodel.h"
#include "spinboxdelegate.h"
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.setGeometry(100,100,700,500);
//这里我们隐藏细节,不必理会做了些什么
//这是我们准备好的模型及数据
myTableModel * tableModel = new myTableModel;
//创建委托
SpinBoxDelegate * myDelegate = new SpinBoxDelegate;
//创建视图
QTableView *firstTableView = new QTableView;
//视图加载模型
firstTableView->setModel(tableModel);
//获取视图的默认选择模型
QItemSelectionModel *selectionModel = firstTableView->selectionModel();
//选择视图将显示在表左上角的一些项。为此,我们需要检索与要选择区域中的左上和右下项目对应的模型索引:
QModelIndex parent = QModelIndex();
QModelIndex topLeft;
QModelIndex bottomRight;
//topLeft = tableModel->index(0, 0, QModelIndex());
//bottomRight = tableModel->index(5, 2, QModelIndex());
topLeft = tableModel->index(0, 0, parent);
bottomRight = tableModel->index(tableModel->rowCount(parent)-1,tableModel->columnCount(parent)-1, parent);
QItemSelection selection(topLeft, bottomRight);
//selectionModel->select(selection, QItemSelectionModel::Select);
//selectionModel->select(selection, QItemSelectionModel::Toggle);
selectionModel->select(selection, QItemSelectionModel::Select);
const QModelIndexList indexes = selectionModel->selectedIndexes();
for (const QModelIndex &index : indexes) {
QString text = QString("(%1,%2)").arg(index.row()).arg(index.column());
tableModel->setData(index, text); //这里并没有设置数据的意思,只是得出我们选择的定位。
}
w.connect(selectionModel,SIGNAL(currentChanged(QModelIndex,QModelIndex)),&w,SLOT(changeCurrent(const QModelIndex,const QModelIndex)));
//视图加载委托
//firstTableView->setItemDelegate(myDelegate);
firstTableView->setItemDelegateForColumn(3,myDelegate);//将表格的第3列进行委任。
//w.layout()->addWidget(firstTableView);
w.setCentralWidget(firstTableView);
w.show();
return a.exec();
}
本样例是前面所有知识点汇总实现。具体讲解了数据模型类的最简单实现。
模型/视图组件之间的功能分离允许创建可以利用现有视图的模型。这种方法允许我们使用标准的图形用户界面组件(如QListView、QTableView和QTreeView)显示来自各种源的数据。
QabstracteModel类提供了一个接口,该接口足够灵活,可以支持以分层结构排列信息的数据源,允许以某种方式插入、删除、修改或排序数据。它还支持拖放操作。
QAbstractListModel和QAbstract TableModel类为更简单的非层次数据结构提供接口支持,并且更容易用作简单列表和表模型的起点。
在本节中,我们将创建一个简单的只读模型,以探索模型/视图体系结构的基本原理。在本节后面,我们将修改这个简单模型,以便用户可以修改项目。
为现有数据结构创建新模型时,重要的是考虑应使用哪种类型的模型来提供数据接口。如果数据结构可以表示为项的列表或表,则可以将QAbstractListModel或QAbstract TableModel子类化,因为这些类为许多函数提供了合适的默认实现。
然而,如果底层数据结构只能由层次树结构表示,则有必要将QAbstractItemModel子类化。在简单树模型示例中采用了这种方法。
在本节中,我们实现了一个基于字符串列表的简单模型,因此QAbstractListModel提供了一个理想的基类。
无论底层数据结构采用何种形式,通常最好在专用模型中使用允许更自然地访问底层数据结构的API来补充标准QabstracteModel API。这使得用数据填充模型变得更容易,但仍然允许其他通用模型/视图组件使用标准API与模型交互。
下面描述的模型为此提供了一个自定义构造函数。
这里实现的模型是基于标准QStringListModel类的简单、非层次、只读数据模型。它有一个QStringList作为其内部数据源,并且只实现了一个功能模型所需的功能。为了简化实现,我们将QAbstractListModel子类化,因为它为列表模型定义了合理的默认行为,并且它公开了一个比QAbstractionModel类更简单的接口。
在实现模型时,重要的是要记住,QAbstractItemModel本身并不存储任何数据,它只是提供了视图用来访问数据的接口。对于最小的只读模型,只需要实现一些函数,因为大多数接口都有默认实现。
除了模型的构造函数,我们只需要实现两个函数:rowCount()返回模型中的行数,data()返回与指定模型索引对应的数据项。
行为良好的模型还实现了headerData(),为树视图和表视图提供了在其标题中显示的内容。
请注意,这是一个非层次模型,因此我们不必担心父子关系。如果我们的模型是分层的,我们还必须实现index()和parent()函数。
字符串列表内部存储在stringList私有成员变量中。
本样例我们希望模型中的行数与字符串列表中的字符串数相同。所以rowCount函数的返回值是:
return stringList.count(); //这样做,只为了样例更简单。
对于视图中的项,我们希望返回字符串列表中的字符串。data()函数负责返回与索引参数对应的数据项。
如果提供的模型索引有效,行号在字符串列表中的项目范围内,并且请求的角色是我们支持的角色,那么我们只返回有效的QVariant。
某些视图(如QTreeView和QTableView)能够显示标题和项目数据。如果模型显示在带有标题的视图中,我们希望标题显示行和列编号。我们可以通过子类化headerData()函数来提供有关标头的信息。
同样,只有当角色是我们支持的角色时,我们才返回有效的QVariant。在决定要返回的确切数据时,还考虑了标题的方向。
并非所有视图都显示带有项目数据的标题,而那些显示标题的视图可能会被配置为隐藏它们。尽管如此,建议您实现headerData()函数,以提供有关模型提供的数据的相关信息。
一个项目可以有多个角色,根据指定的角色提供不同的数据。我们模型中的项只有一个角色,DisplayRole,因此我们返回项的数据,而不考虑指定的角色。但是,我们可以在其他角色中重用我们为DisplayRole提供的数据,例如视图可以用来显示工具提示中项目信息的工具提示角色。
只读模型显示了如何向用户提供简单的选择,但对于许多应用程序,可编辑列表模型更有用。我们可以修改只读模型,通过更改我们为只读实现的data()函数,并通过实现两个额外的函数:flags()和setData()来编辑项。将以下函数声明添加到类定义中
委托在创建编辑器之前检查项是否可编辑。模型必须让代理知道其项是可编辑的。为此,我们为模型中的每个项返回正确的标志;在这种情况下,我们启用所有项目,并使其可选择和可编辑
请注意,我们不必知道代理如何执行实际编辑过程。我们只需要为委托提供一种方法来设置模型中的数据。这是通过setData()函数实现的
在此模型中,字符串列表中与模型索引相对应的项被提供的值替换。但是,在修改字符串列表之前,必须确保索引有效,项的类型正确,并且支持角色。按照惯例,我们坚持角色是EditRole,因为这是标准项委托使用的角色。但是,对于布尔值,可以使用Qt::CheckStateRole并设置Qt::ItemIsUserCheckable标志;然后使用复选框编辑值。该模型中的底层数据对于所有角色都是相同的,因此这个细节只是使模型与标准组件的集成更加容易。
设置数据后,模型必须让视图知道某些数据已更改。这是通过发出dataChanged()信号实现的。由于只有一项数据发生了变化,信号中指定的项目范围仅限于一个模型索引。
还需要更改data()函数以添加Qt::EditRole测试
可以更改模型中的行数和列数。在字符串列表模型中,只有更改行数才有意义,因此我们只重新实现插入和删除行的函数。它们在类定义中声明。
由于此模型中的行对应于列表中的字符串,insertRows()函数在指定位置之前将多个空字符串插入到字符串列表中。插入的字符串数等于指定的行数。
父索引通常用于确定模型中应添加行的位置。在这种情况下,我们只有一个顶级字符串列表,所以我们只在该列表中插入空字符串。
模型首先调用beginInsertRows()函数,通知其他组件行数即将更改。该函数指定要插入的第一行和最后一行的行号,以及其父项的模型索引。更改字符串列表后,它调用endInsertRows()完成操作,并通知其他组件模型的维度已更改,返回true表示成功。
从模型中删除行的函数也很容易编写。要从模型中删除的行由给定的位置和行数指定。我们忽略父索引以简化实现,只从字符串列表中删除相应的项。
beginRemoveRows()函数始终在删除任何基础数据之前调用,并指定要删除的第一行和最后一行。这允许其他组件在数据不可用之前访问数据。删除行后,模型发出endRemoveRows()以完成操作,并让其他组件知道模型的维度已更改。
对模型操作的实现:
#include "stringlistmodel.h"
StringListModel::StringListModel(QObject *parent)
: QAbstractListModel(parent)
{
}
QVariant StringListModel::headerData(int section, Qt::Orientation orientation, int role) const
{
// FIXME: 实现我!
if(role!=Qt::DisplayRole)
return QVariant();
if(orientation==Qt::Vertical)
return QVariant(section);
if(orientation==Qt::Horizontal)
{
for(int i=0;imyHeadData.count();i++)
{
if(section == i)
{
return this->myStringList.at(i);
}
}
}
return 0;
}
bool StringListModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role)
{
if (value != headerData(section, orientation, role)) {
// FIXME: Implement me!
emit headerDataChanged(orientation, section, section);
return true;
}
return false;
}
int StringListModel::rowCount(const QModelIndex &parent) const
{
// 对于列表模型,只有根节点(无效的父节点)应返回列表的大小。为所有人
// 其他(有效)父级,rowCount()应该返回0,这样它就不会成为树模型。
if (parent.isValid())
return 0;
return myStringList.count();
// FIXME: 实现我!
}
bool StringListModel::hasChildren(const QModelIndex &parent) const
{
// FIXME: 实现我!
return false;
}
bool StringListModel::canFetchMore(const QModelIndex &parent) const
{
// FIXME: 实现我!
return false;
}
void StringListModel::fetchMore(const QModelIndex &parent)
{
// FIXME: 实现我!
}
QVariant StringListModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (index.row() >= myStringList.size())
return QVariant();
if(role == Qt::DisplayRole || role == Qt::EditRole)
{
const int row = index.row();
//const int column = index.column();
return this->myStringList.at(row);
}
// FIXME: 实现我!
return QVariant();
}
bool StringListModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (data(index, role) != value) {
// FIXME: 实现我!
myStringList.replace(index.row(), value.toString());
emit dataChanged(index, index, {role});
return true;
}
return false;
}
Qt::ItemFlags StringListModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
return QAbstractItemModel::flags(index) | Qt::ItemIsEditable; // FIXME: Implement me!
}
bool StringListModel::insertRows(int row, int count, const QModelIndex &parent)
{
beginInsertRows(parent, row, row + count - 1);
// FIXME: 实现我!
for (int i = 0; i < count; ++i)
{
myStringList.insert(row, "");
}
endInsertRows();
return true;
}
bool StringListModel::removeRows(int row, int count, const QModelIndex &parent)
{
beginRemoveRows(parent, row, row + count - 1);
// FIXME: 实现我!
endRemoveRows();
for (int i = 0; i < count; ++i)
{
myStringList.removeAt(row);
}
return true;
}
基于项的小部件具有反映其用途的名称:QListWidget提供项列表,QTreeWidget显示多级树结构,而QTableWidget则提供单元格项表。每个类继承QAbstractItemView类的行为,该类实现了项选择和标题管理的通用行为。
#include "mainwindow.h"
#include
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.setGeometry(100,100,500,300);
QListWidget *listWidget = new QListWidget(&w);
listWidget->setGeometry(0,0,500,300);
listWidget->setAlternatingRowColors(true);
new QListWidgetItem("Sycamore", listWidget);
new QListWidgetItem("Chestnut", listWidget);
new QListWidgetItem("Mahogany", listWidget);
new QListWidgetItem("GuJianNan", listWidget);
QListWidgetItem *newItem = new QListWidgetItem;
newItem->setText("itemText");
listWidget->insertItem(3, newItem);
newItem->setToolTip("toolTipText");
newItem->setStatusTip("ToolTipText");
newItem->setWhatsThis("whatsThisText");
listWidget->sortItems(Qt::AscendingOrder);
listWidget->sortItems(Qt::DescendingOrder);
w.show();
return a.exec();
}
本样例实现了多个功能,但实现都比较简单。
#include "mainwindow.h"
#include
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.setGeometry(100,100,500,300);
QTreeWidget *treeWidget = new QTreeWidget(&w);
treeWidget->setGeometry(0,0,500,300);
treeWidget->setColumnCount(2);
QStringList headers;
headers << "Subject"<<"Default";
treeWidget->setHeaderLabels(headers);
QTreeWidgetItem *cities = new QTreeWidgetItem(treeWidget);
cities->setText(0, "Cities");
QTreeWidgetItem *osloItem = new QTreeWidgetItem(cities);
osloItem->setText(0, "Oslo");
osloItem->setText(1, "Yes");
QTreeWidgetItem *planets = new QTreeWidgetItem(treeWidget, cities);
planets->setText(0,"测试");
treeWidget->setCurrentItem(osloItem);
QTreeWidgetItem *currentItem = treeWidget->currentItem();
QTreeWidgetItem *parent = currentItem->parent();
int index;
if (parent)
{
qDebug()<<"我有父级项";
index = parent->indexOfChild(treeWidget->currentItem());
delete parent->takeChild(index);
}
else
{
qDebug()<<"我没有父级项";
index = treeWidget->indexOfTopLevelItem(treeWidget->currentItem());
delete treeWidget->takeTopLevelItem(index);
}
treeWidget->setCurrentItem(planets);
currentItem = treeWidget->currentItem();
parent = currentItem->parent();
QTreeWidgetItem *newItem;
if (parent)
{
newItem = new QTreeWidgetItem(parent, treeWidget->currentItem());
newItem->setText(0,"我是新加项目");
newItem->setText(1,"yes");
}
else
{
newItem = new QTreeWidgetItem(treeWidget, treeWidget->currentItem());
newItem->setText(0,"我是新加项目");
newItem->setText(1,"yes");
}
w.show();
return a.exec();
}
#include "mainwindow.h"
#include
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
QTableWidget *tableWidget;
tableWidget = new QTableWidget(9, 3, &w);
int row = 0;
int column = 0;
//tr("%1").arg(pow(row, column+1)):这一句不用在意,只是返回一个QString类型的字符串。
QTableWidgetItem *newItem = new QTableWidgetItem("我是新加项目");
tableWidget->setItem(row, column, newItem);
QTableWidgetItem *valuesHeaderItem = new QTableWidgetItem("Values");
tableWidget->setHorizontalHeaderItem(0, valuesHeaderItem);
tableWidget->setGeometry(0,0,500,300);
w.setGeometry(100,100,500,300);
w.show();
return a.exec();
}
QT Model/View 编程官方教程实例