在 Qt 中对数据处理主要有两种方式:1)直接对包含数据的的数据项 item 进行操作,这种方法简单、易操作,现实方式单一的缺点,特别是对于大数据或在不同位置重复出现的数据必须依次对其进行操作,如果现实方式改变,则在改动程序过程中还需对数据进行重新编码操作,费工费资源。2)采用 model/view 模型,将数据 -- 模型 -- 视图三者串起来,通过约定的接口保证数据的正确显示和显示方式的多样性,当需要重新调整显示时,只需修改视图,保证接口不变,即可以新 view 显示数据。
1/2 两种处理数据方式:
视图与数据绑定在一起:
视图与数据隔离:
在此,我主要介绍第二种模型:model/view模式,在此以QAbstractTableModel/QTableView 为例。
如果是只读模式,model 只要重写以下三个方法:
//a方法:返回模型行数。
int rowCount(const QModelIndex &parent) const;
//b方法:返回模型列数。
int columnCount(const QModelIndex &parent) const;
//c方法:返回index项的role角色的数据。其中index项中可以有多个角色,每个角色都可以有一个数据。
QVariant data(const QModelIndex &index, int role) const;
如果用户要能够编辑数据(编辑模式),model 还需要重写以下两个方法:
//d方法:设置模型中项的内容。
bool QAbstractItemModel::setData(const QModelIndex & index,
const QVariant & value,
int role= Qt::EditRole);
//e方法:返回项的编辑形式,默认情况下只有ItemIsSelectable和ItemIsEnabled,如果要可编辑,需要
//添加ItemIsEditable属性。
Qt::ItemFlags QAbstractTableModel::flags(const QModelIndex & index) const
其中 a/b/c 方法为纯虚函数(pure virtual method),继承的类必须由 coder 自己实现此方法。d/e 方法为虚函数(virtual),coder 在继承了此方法,实现自定义内容后可直接调用基类方法。
在此我们以《c++ gui programming with Qt4》中的 trackEditor 例子作一讲解。
class MyTableModel : public QAbstractTableModel
{
public:
explicit MyTableModel(QList<Track>* tracks, QWidget *parent = 0);
virtual int rowCount(const QModelIndex &parent) const;
virtual int columnCount(const QModelIndex &parent) const;
virtual QVariant data(const QModelIndex &index, int role) const;
virtual QVariant headerData(int section,
Qt::Orientation orientation,
int role) const;
virtual Qt::ItemFlags flags(const QModelIndex &index) const;
virtual bool setData(const QModelIndex &index, const QVariant &value, int role);
private:
QList<Track>* pTracks;
};
其中 “QList<Track>* tracks” 是用来保存 model 所显示的数据集合,在 model 初始化时被赋值,根据 data 方法的算法调用其中数据显示。
MyTableModel::MyTableModel(QList<Track>* tracks, QWidget *parent)
{
Q_UNUSED(parent);
pTracks = tracks;
}
构造函数对 tracks 进行赋值。
int MyTableModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return pTracks->count();
}
int MyTableModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return 2;
}
以上 2 个方法返回 model 的行 / 列数,因为 tracks 中的数据量不确定,所以直接返回其 count 方法,保证每次都是最新值;
QVariant MyTableModel::data(const QModelIndex &index, int role) const
{
if ( !index.isValid()) {
return QVariant();
}
if (Qt::DisplayRole == role || Qt::EditRole == role) {
if (0 == index.column()) {
return pTracks->at(index.row()).getTitle();
} else if (1 == index.column()) {
return pTracks->at(index.row()).getDuration();
}
}
return QVariant();
}
根据数据类型和所在列,返回不同的数据信息,当 index 都不属于以上两种情况时,返回 QVariant 对象。EditRole 保证了当用户编辑数据时,数据显示的是被选中模式而不是直接消失。
bool MyTableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if ( !index.isValid()) {
return false;
}
if (Qt::EditRole == role) {
(*pTracks)[index.row()].setTitle(value.toString());
emit dataChanged(index, index);
return true;
} else {
return QAbstractTableModel::setData(index, value, role);
}
}
根据 coder 处理和传入角色,设置 index 处项的值及 tracks 中对应处的数据并更新 index 的数据显示。当传入数据 coder 不处理时,则直接调用基类方法处理,再辞没有特别指明第几列进行编辑,因为是后面通过 flags 方法来设定了可编辑的列,故此处不用再特别指明。在此请注意,setData 方法是判断的是 Qt::EditRole 角色,data 方法是 Qt::DisplayRole,但是保存、读取的都是 tracks,这正是 coder 要注意的,在不同的过程时,程序所处于的角色不同,但是都操作同样的数据库(tracks),这点要注意。
Qt::ItemFlags MyTableModel::flags(const QModelIndex &index) const
{
if (0 == index.column()) {
return (QAbstractTableModel::flags(index) | Qt::ItemIsEditable);
} else {
return QAbstractTableModel::flags(index);
}
}
model 中每个项的处理标志位默认为(ItemIsEnabled | ItemIsSelectable),coder 可根据要求对不同属性的项进行设置。在此设定第 0 列可编辑,其余列不可编辑。
程序运行结果,分别用 TableView 和 ListView 显示相同的数据:
具体代码如下:共有 maindialg,mytablemodel,tack 三个类,一个 main 运行类。具体界面文件是由 Qt 自己生成的 ui。
track.h
#ifndef TRACK_H
#define TRACK_H
#include <QString>
class Track
{
public:
explicit Track(const QString& title = "", int duration = 0);
QString getTitle() const;
int getDuration() const;
void setDuration(int duration);
void setTitle(QString title);
private:
QString mTitle;
int mDuration;
};
#endif // TRACK_H
track.cpp
#include "track.h"
Track::Track(const QString &title, int duration) :
mTitle(title),
mDuration(duration)
{
}
QString Track::getTitle() const
{
return mTitle;
}
int Track::getDuration() const
{
return mDuration;
}
void Track::setDuration(int duration)
{
mDuration = duration;
}
void Track::setTitle(QString title)
{
mTitle = title;
}
mytablemodel.h
#ifndef MYTABLEMODEL_H
#define MYTABLEMODEL_H
#include <QWidget>
#include <QAbstractTableModel>
#include "track.h"
class MyTableModel : public QAbstractTableModel
{
public:
explicit MyTableModel(QList<Track>* tracks, QWidget *parent = 0);
virtual int rowCount(const QModelIndex &parent) const;
virtual int columnCount(const QModelIndex &parent) const;
virtual QVariant data(const QModelIndex &index, int role) const;
virtual QVariant headerData(int section,
Qt::Orientation orientation,
int role) const;
virtual Qt::ItemFlags flags(const QModelIndex &index) const;
virtual bool setData(const QModelIndex &index, const QVariant &value, int role);
private:
QList<Track>* pTracks;
};
#endif // MYTABLEMODEL_H
mytablemodel.cpp
#include "mytablemodel.h"
MyTableModel::MyTableModel(QList<Track>* tracks, QWidget *parent)
{
Q_UNUSED(parent);
pTracks = tracks;
}
int MyTableModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return pTracks->count();
}
int MyTableModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return 2;
}
QVariant MyTableModel::data(const QModelIndex &index, int role) const
{
if ( !index.isValid()) {
return QVariant();
}
if (Qt::DisplayRole == role) {
if (0 == index.column()) {
return pTracks->at(index.row()).getTitle();
} else if (1 == index.column()) {
return pTracks->at(index.row()).getDuration();
}
}
return QVariant();
}
QVariant MyTableModel::headerData(int section,
Qt::Orientation orientation,
int role) const
{
/*if (Qt::Vertical == orientation) {
return QVariant();
}*/
if (Qt::DisplayRole == role && Qt::Horizontal == orientation) {
switch (section) {
case 0:
return "first";
case 1:
return "second";
}
}
return QVariant();
}
Qt::ItemFlags MyTableModel::flags(const QModelIndex &index) const
{
if (0 == index.column()) {
return (QAbstractTableModel::flags(index) | Qt::ItemIsEditable);
} else {
return QAbstractTableModel::flags(index);
}
}
bool MyTableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if ( !index.isValid()) {
return false;
}
if (Qt::EditRole == role) {
(*pTracks)[index.row()].setTitle(value.toString());
emit dataChanged(index, index);
return true;
} else {
return QAbstractTableModel::setData(index, value, role);
}
}
maindialog.h
#ifndef MAINDIALOG_H
#define MAINDIALOG_H
#include <QDialog>
#include <QTableView>
#include <QListView>
#include "mytablemodel.h"
namespace Ui {
class MainDialog;
}
class MainDialog : public QDialog
{
Q_OBJECT
public:
explicit MainDialog(QWidget *parent = 0);
~MainDialog();
void setTableModel(MyTableModel* model);
void setListModel(MyTableModel* model);
private:
Ui::MainDialog *ui;
QTableView* pTableView;
QListView* pListView;
};
#endif // MAINDIALOG_H
maindialog.cpp
#include<QGridLayout>
#include "maindialog.h"
#include "ui_maindialog.h"
MainDialog::MainDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::MainDialog),
pTableView(new QTableView(this)),
pListView(new QListView(this))
{
ui->setupUi(this);
QVBoxLayout* layout(new QVBoxLayout(this));
layout->addWidget(pTableView);
layout->addWidget(pListView);
setLayout(layout);
setAttribute(Qt::WA_DeleteOnClose);
}
MainDialog::~MainDialog()
{
delete ui;
}
void MainDialog::setTableModel(MyTableModel *model)
{
pTableView->setModel(model);
}
void MainDialog::setListModel(MyTableModel* model)
{
pListView->setModel(model);
}
main.cpp
#include <QApplication>
#include "maindialog.h"
#include "track.h"
#include "mytablemodel.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QList<Track> tracks;
tracks << Track("The Flying Dutchman: Overture", 630)
<< Track("The Flying Dutchman: Wie aus der Fern laengst "
"vergangner Zeiten", 374)
<< Track("The Flying Dutchman: Steuermann, lass die Wacht",
152)
<< Track("Die Walkuere: Ride of the Valkyries", 286)
<< Track("Tannhaeuser: Freudig begruessen wir die edle "
"Halle", 384)
<< Track("Tannhaeuser: Wie Todesahnung - O du mein holder "
"Abendstern", 257)
<< Track("Lohengrin: Treulich gefuert ziehet dahnin", 294)
<< Track("Lohengrin: In fernem Land", 383)
<< Track("Die Meistersinger von Nuernberg: Overture", 543)
<< Track("Die Meistersinger von Nuernberg: Verachtet mir "
"die Meister nicht", 200)
<< Track("Die Meistersinger von Nuernberg: Ehrt eure "
"deutschen Meister", 112)
<< Track("Goetterdaemmerung: Funeral Music", 469)
<< Track("Tristan und Isolde: Mild und leise, wie er "
"laechelt", 375);
MyTableModel model(&tracks);
MainDialog* w(new MainDialog(0));
w->setTableModel(&model);
w->setListModel(&model);
w->show();
return a.exec();
}