这是我对 Qt 的 model/view 内容理解的第二篇 blog,在第一篇文章中,介绍 QTableView 和 QAbstractTableModel,实现显示了对数据源的显示,但是显示的格式和修改的模式都是按照 View 控件的自显示方式。在此,使用 Qt 自带的 QStyledItemDelegate 类实现对特定行 / 列的显示 / 修改模式实现,实现过程中不出现对 item 的代码生成,对 item 的生成由程序自动完成。
在此同样以《c++ gui programming with Qt4》中的 trackEditor 例子作一讲解。
在此,我们首先应当考虑以下几个问题:
1)有一个代理类加到 View 中,处理特定的 View 内容。
2)代理类要完成以下几项工作:a)当用户修改数据时生成用户要求的控件(用户每次修改数据时在相应的位置都会生成控件,所以当控件用完后,应 del 释放资源)。b)设置在生成的修改控件中显示的内容。c)设置要写到 model 中的数据内容。d)设置当结束修改数据后,View 显示的内容。
在此我们需要实现以下 4 个类。
cpp
/**
@brief 保存显示数据的类。
*/
class Track
/**
@brief 继承的委托类。
*/
class TrackDelegate : public QStyledItemDelegate
/**
@brief 继承的模型类。
*/
class TrackModel : public QAbstractTableModel
/**
@brief 用于组装显示的控件类。
*/
class TrackEditor : public QDialog
在此,Track TrackModel TrackEditor 的功能在 Qt model/view 理解 1 中做过介绍,在此只列出 code,不再过多介绍。在此主要介绍 TrackDelegate。
Track h 文件
cpp
#ifndef TRACK_H
#define TRACK_H
#include <QString>
/**
@projectName ItemView
@author qiaowei
@date 2018-12-22
@version 1.0
@description 保存的音频数据,包括音频名称和时长
**/
class Track
{
public:
explicit Track(const QString& title = "", int duration = 0);
QString getTitle() const;
void setTitle(const QString& title);
int getDuration() const;
void setDuration(int duration);
private:
/**
@author qioawei
@date 2018-12-22
@description 音频名称
**/
QString title_;
/**
@author qioawei
@date 2018-12-22
@description 音频时长(单位:秒)
**/
int duration_;
};
#endif // TRACK_H
Track.cpp
cpp
#include "track.h"
Track::Track(const QString &title, int duration) :
title_(title),
duration_(duration)
{
}
QString Track::getTitle() const
{
return title_;
}
void Track::setTitle(const QString &title)
{
title_ = title;
}
int Track::getDuration() const
{
return duration_;
}
void Track::setDuration(int duration)
{
duration_ = duration;
}
TrackModel.h
cpp
#ifndef TRACKMODEL_H#define TRACKMODEL_H
#include <QWidget>
#include <QAbstractTableModel>
#include <QList>
#include "track.h"
#include <QObject>
/**
@brief 继承的模型类。
*/
class TrackModel : public QAbstractTableModel
{
Q_OBJECT
public:
explicit TrackModel(QList<Track>* tracks, QObject* parent = 0);
~TrackModel();
virtual int rowCount(const QModelIndex &parent) const;
virtual int columnCount(const QModelIndex &parent) const;
virtual QVariant data(const QModelIndex &index, int role) const;
virtual bool setData(const QModelIndex &index,
const QVariant &value,
int role);
virtual Qt::ItemFlags flags(const QModelIndex &index) const;
private:
QList<Track>* tracks;
};
#endif // TRACKMODEL_H
TrackEditor h 文件
cpp
#ifndef TRACKEDITOR_H#define TRACKEDITOR_H
#include <QDialog>
#include "track.h"
QT_BEGIN_NAMESPACE
class QTableView;
class TrackModel;
class QAbstractTableModel;
QT_END_NAMESPACE
namespace Ui {
class TrackEditor;
}
/**
@brief 用于组装显示的控件类。
*/
class TrackEditor : public QDialog
{
Q_OBJECT
public:
explicit TrackEditor(QList<Track>* tracks, QWidget *parent = 0);
~TrackEditor();
private:
Ui::TrackEditor *ui;
QTableView* tableView;
TrackModel* model;
//QAbstractTableModel* model;
};
#endif // TRACKEDITOR_H
TrackDelegate h 文件
cpp
#ifndef TRACKDELEGATE_H#define TRACKDELEGATE_H
#include <QObject>
#include <QStyledItemDelegate>
QT_BEGIN_NAMESPACE
class QPainter;
QT_END_NAMESPACE
/**
@brief 继承的委托类。
*/
class TrackDelegate : public QStyledItemDelegate
{
public:
explicit TrackDelegate(QObject* parent = 0);
~TrackDelegate();
virtual QWidget* createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const;
virtual void setEditorData(QWidget* parent,
const QModelIndex& index) const;
virtual void setModelData(QWidget* editor,
QAbstractItemModel* model,
const QModelIndex& index) const;
virtual void updateEditorGeometry(QWidget* editor,
const QStyleOptionViewItem& option,
const QModelIndex& index) const;
virtual void paint(QPainter* painter,
const QStyleOptionViewItem &option,
const QModelIndex &index) const;
virtual QSize sizeHint(const QStyleOptionViewItem &option,
const QModelIndex &index) const;
private:
bool isRightColumn(const QModelIndex& index, const int column) const;
private slots:
void commitAndCloseEditor();
private:
static const int columnNumber;
};
#endif // TRACKDELEGATE_H
TrackDelegate cpp 文件
cpp
#include "trackdelegate.h"
#include <QTimeEdit>
#include <QPainter>
#include <QApplication>
#include "trackmodel.h"
const int TrackDelegate::columnNumber = 1;
TrackDelegate::TrackDelegate(QObject* parent) :
QStyledItemDelegate(parent)
{
}
TrackDelegate::~TrackDelegate()
{
}
QWidget* TrackDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
if (isRightColumn(index, TrackDelegate::columnNumber)) {
QTimeEdit *timeEdit = new QTimeEdit(parent);
timeEdit->setDisplayFormat("hh:mm");
//当控件结束编辑内容时,触发释放资源
connect(timeEdit, &QTimeEdit::editingFinished,
this, &TrackDelegate::commitAndCloseEditor);
//int secs = index.model()->data(index, Qt::DisplayRole).toInt();
int secs = index.model()->data(index, Qt::EditRole).toInt();
QTime time(secs / 60, secs % 60);
timeEdit->setTime(time);
return timeEdit;
} else {
return QStyledItemDelegate::createEditor(parent,
option,
index);
}
}
void TrackDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
if ( !index.isValid()) {
return;
}
QTimeEdit* timeEditor = qobject_cast<QTimeEdit*>(editor);
if ( !timeEditor) {
return;
}
if (isRightColumn(index, TrackDelegate::columnNumber)) {
int secs = index.model()->data(index, Qt::EditRole).toInt();
QTime time(secs / 60, secs % 60);
timeEditor->setTime(time);
} else {
QStyledItemDelegate::setEditorData(editor, index);
}
}
void TrackDelegate::setModelData(QWidget *editor,
QAbstractItemModel *model,
const QModelIndex &index) const
{
if ( !index.isValid()) {
return;
}
QTimeEdit* timeEditor = qobject_cast<QTimeEdit*>(editor);
if ( !timeEditor) {
return;
}
if (isRightColumn(index, TrackDelegate::columnNumber)) {
QTime time = timeEditor->time();
int secs = time.hour() * 60 + time.minute();
model->setData(index, secs, Qt::EditRole);
} else {
QStyledItemDelegate::setModelData(editor,
model,
index);
}
}
void TrackDelegate::updateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
Q_UNUSED(index);
editor->setGeometry(option.rect);
}
void TrackDelegate::paint(QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
if (isRightColumn(index, TrackDelegate::columnNumber)) {
int secs = index.model()->data(index, Qt::EditRole).toInt();
QString text = QString("%1:%2")
.arg(secs / 60, 2, 10, QChar('0'))
.arg(secs % 60, 2, 10, QChar('0'));
//获取项风格设置
QStyleOptionViewItem myOption = option;
myOption.displayAlignment = Qt::AlignRight | Qt::AlignVCenter;
painter->drawText(option.rect, text);
} else {
QStyledItemDelegate::paint(painter, option, index);
}
}
QSize TrackDelegate::sizeHint(const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
return option.rect.size();
}
void TrackDelegate::commitAndCloseEditor()
{
QTimeEdit* editor = qobject_cast<QTimeEdit*>(sender());
emit commitData(editor);
emit closeEditor(editor);
}
bool TrackDelegate::isRightColumn(const QModelIndex &index,
const int column) const
{
if ( !index.isValid()) {
return false;
}
if (index.column() == column) {
return true;
} else {
return false;
}
}
createEditor 用于创建用户自己需要的显示数据控件。
setEditorData 用于设置显示控件中显示的具体数据信息。
setModelData 用户设置模型的数据,也可理解为当显示的数据发生变化后,用户 update 模型数据,保持显示 / 储存内容一致。
paint 由用户自己绘制要显示的内容信息(很重要,当你点击时间框修改时间,要改的内容为原显示内容并允许你修改,而不是数据变为 00:00,让你修改)。
commitAndCloseEditor 创建关于 commitData 和 closeEditor 的信号槽链接,保证当代理控件的数据修改完成后,释放信号,保存数据(保存数据步骤由系统自动完成)。
在此要求注意内容:
在 TrackModel 类中的 setData 方法应当注意,data 的值应从 value 得出而不是通过 model 的 data 得出,model 得出的数据是原来保存的,而不是用户修改的。