QT开发(三十八)——Model/View框架编程

一、自定义模型

1自定义只读模型

    QAbstractItemModel为自定义模型提供了一个足够灵活的接口,能够支持数据源的层次结构,能够对数据进行增删改操作,还能够支持拖放。QT提供了 QAbstarctListModel和QAbstractTableModel两个类来简化非层次数据模型的开发,适合于结合列表和表格使用。

    自定义模型需要考虑模型管理的的数据结构适合的视图的显示方式。如果模型的数据仅仅用于列表或表格的显示,那么可以使用QAbstractListModel或者 QAbstractTableModel。但如果模型的数据具有层次结构,并且必须向用户显示出层次结构,只能选择QAbstractItemModel。不管底层数据结构是如何组织的,都要直接考虑适应于标准的 QAbstractItemModel的接口,可以让更多视图能够轻松访问到模型。

    自定义一个货币汇率表模型CurrencyModel

    通常,货币的汇率都是相对于某个基准货币(如美元)的汇率。

    CurrencyModel底层使用QMap<QString, double>数据结构进行存储,QString类型的键是货币名字,double类型的值是这种货币相对美元的汇率。

    CurrencyModel.h文件:

#ifndef CURRENCYMODEL_H
#define CURRENCYMODEL_H
 
#include <QAbstractTableModel>
 
class CurrencyModel : public QAbstractTableModel
{
public:
    CurrencyModel(QObject *parent = 0);
 
    void setCurrencyMap(const QMap<QString, double> &map);
    int rowCount(const QModelIndex &parent) const;
    int columnCount(const QModelIndex &parent) const;
    QVariant data(const QModelIndex &index, int role) const;
    QVariant headerData(int section, Qt::Orientation orientation, int role) const;
private:
    QString currencyAt(int offset) const;
    QMap<QString, double> currencyMap;
};
 
#endif // CURRENCYMODEL_H

    CurrencyModel.cpp文件:

#include "CurrencyModel.h"
#include <QString>
 
CurrencyModel::CurrencyModel(QObject *parent):QAbstractTableModel(parent)
{
 
}
 
QVariant CurrencyModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
    {
        return QVariant();
    }
    if (role == Qt::TextAlignmentRole)
    {
        return int(Qt::AlignRight | Qt::AlignVCenter);
    }
    else if (role == Qt::DisplayRole)
    {
        QString rowCurrency = currencyAt(index.row());
        QString columnCurrency = currencyAt(index.column());
        if (currencyMap.value(rowCurrency) == 0.0)
        {
            return "####";
        }
        double amount = currencyMap.value(columnCurrency)
                / currencyMap.value(rowCurrency);
        return QString("%1").arg(amount, 0, 'f', 4);
    }
    return QVariant();
}
 
int CurrencyModel::rowCount(const QModelIndex & parent) const
{
    return currencyMap.count();
}
 
int CurrencyModel::columnCount(const QModelIndex & parent) const
{
    return currencyMap.count();
}
 
QVariant CurrencyModel::headerData(int section, Qt::Orientation, int role) const
{
    if (role != Qt::DisplayRole)
    {
        return QVariant();
    }
    return currencyAt(section);
}
 
QString CurrencyModel::currencyAt(int offset) const
{
    return (currencyMap.begin() + offset).key();
}
 
 void CurrencyModel::setCurrencyMap(const QMap<QString, double> &map)
 {
     beginResetModel();
     currencyMap = map;
     endResetModel();
 }

    Main.cpp文件:

#include "CurrencyModel.h"
#include <QApplication>
#include <QTableView>
#include <QMap>
 
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QMap<QString, double> data;
    data["USD"] = 1.0000;
    data["CNY"] = 0.1628;
    data["GBP"] = 1.5361;
    data["EUR"] = 1.2992;
    data["HKD"] = 0.1289;
 
    QTableView view;
    CurrencyModel *model = new CurrencyModel(&view);
    model->setCurrencyMap(data);
    view.setModel(model);
    view.resize(400, 300);
    view.show();
 
    return a.exec();
}

wKiom1hw9uPjL69GAAB4Qwchh1k245.png

2、自定义可编辑模型

    如果允许用户修改数据,则应该提供可编辑的模型。可编辑模型与只读模型非常相似,至少在显示数据方面几乎是完全一样的,所不同的是可编辑模型需要提供用户编辑数据后,应当如何将数据保存到实际存储值中。

    可编辑模型需要增加

    Qt::ItemFlags QAbstractItemModel::flags(const QModelIndex &index) const;

    bool QAbstractItemModel::setData(const QModelIndex &index, const QVariant &value,int role = Qt::EditRole);

    在QTModel/View模型中,使用委托delegate来实现数据的编辑。在实际创建编辑器前,委托需要检测数据项是不是允许编辑。模型必须让委托知道数据项是否可编辑,可以通过flags()函数返回模型中每个数据项的标记flag来实现。当行和列的索引不一致的时候,允许修改数据项。

Qt::ItemFlags CurrencyModel::flags(const QModelIndex &index) const
 {
     Qt::ItemFlags flags = QAbstractItemModel::flags(index);
     if (index.row() != index.column())
     {
         flags |= Qt::ItemIsEditable;
     }
     return flags;
 }

    告诉Qt如何将委托获得的用户输入的新的数据保存到模型中。

bool CurrencyModel::setData(const QModelIndex &index, const QVariant &value, int role)
 {
     if (index.isValid() && index.row() != index.column() && role == Qt::EditRole)
     {
         QString columnCurrency = headerData(index.column(), Qt::Horizontal, Qt::DisplayRole).toString();
         QString rowCurrency = headerData(index.row(), Qt::Vertical, Qt::DisplayRole).toString();
         currencyMap.insert(columnCurrency, value.toDouble() * currencyMap.value(rowCurrency));
         emit dataChanged(index, index);
         return true;
     }
     return false;
 }

    增加data()函数的可编辑属性

QVariant CurrencyModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
    {
        return QVariant();
    }
    if (role == Qt::TextAlignmentRole)
    {
        return int(Qt::AlignRight | Qt::AlignVCenter);
    }
    else if (role == Qt::DisplayRole || role == Qt::EditRole)
    {
        QString rowCurrency = currencyAt(index.row());
        QString columnCurrency = currencyAt(index.column());
        if (currencyMap.value(rowCurrency) == 0.0)
        {
            return "####";
        }
        double amount = currencyMap.value(columnCurrency)
                / currencyMap.value(rowCurrency);
        return QString("%1").arg(amount, 0, 'f', 4);
    }
    return QVariant();
}

3、自定义模型应用实例

QStandardItemModel是一个通用的模型类,能够以任意的方式组织数据(如、线性、非线性),数据组织的基本单位为QStandardItem数据项,每个数据项可以根据数据角色存储多个数据,每一个数据项能够对数据状态进行控制(如可编辑、可选)。

wKioL1hw9wjxD6zmAACajaEvq28283.png

QVariant是变体类型,常用于设计返回类型可变的函数。

工程中常用模型设计如下:

A、解析数据源中的数据(数据库、串口、网络)

B、将解析后的数据存入QStandarItem数据项

C、根据数据间的关系在QStandardItemModel对象中组织数据项

D、选择合适的视图显示数据

wKioL1hw9yXx9sV6AABgZNGDLTE184.png

实例:

在文件中以行的形式存储考试成绩信息(ID,Name,Score),开发GUI程序显示文件中的信息(计算平均成绩,查找最好成绩和最差成绩,刷新显示和删除)。

    系统架构图:

wKiom1hw90Owk_lqAACUSEDT_u8111.png

    架构图用于定义模块功能。

    核心类图:

wKioL1hw916BdnrwAABIw5Wg0GU635.png

类图用于定义具体功能的接口。

每个核心类用于实现架构图中每一层的具体功能。

DataSource类设计

设计数据源、读取数据

对数据进行解析,生成数据对象

wKiom1hw936Tb7n3AAA081iuFT0611.png

DataScore类设计

封装数据源中的一组完整数据

提供返回具体数据值的接口函数

wKioL1hw95-SzpsFAAAyTShgDFE702.png

DataModel类设计

使用QStandardItemModel标准模型类作为类成员

DataScore类对象作为单位组织数据

wKioL1hw97mzYRxXAABEoDM0EUo876.png

    DataAppUI界面类设计

wKiom1hw99TCEGImAAA6Fa7f920531.png

    右键菜单的实现:

    A、定义菜单QMenu对象

    B、连接菜单中的QAction对象到槽函数

    C、定义事件过滤器,处理ConetxtMenu事件

    D、在当前鼠标的位置打开菜单对象


    DataSource.h文件:

#ifndef DATASOURCE_H
#define DATASOURCE_H
 
#include <QObject>
#include <QList>
#include "DataScore.h"
 
class DataSource : public QObject
{
    Q_OBJECT
private:
    QList<DataScore> m_data;
    bool parse(QString line, DataScore& info);
public:
    explicit DataSource(QObject* parent = 0);
    bool setDataSource(const QString source);
    QList<DataScore> fetchData();
    int count() const;
};
 
#endif // DATASOURCE_H

    DataSource.cpp文件:

#include "DataSource.h"
#include <QFile>
#include <QTextStream>
#include <QStringList>
 
DataSource::DataSource(QObject *parent) : QObject(parent)
{
}
 
bool DataSource::parse(QString line, DataScore& info)
{
    bool ret = true;
    QStringList list = line.split(",", QString::SkipEmptyParts);
    if(list.count() == 3)
    {
        QString id = list[0].trimmed();
        QString name = list[1].trimmed();
        QString score = list[2].trimmed();
        int value = score.toInt(&ret);
        if(ret && (0 <= value) && (value <= 100))
        {
            info = DataScore(id, name, value);
        }
        else
        {
            ret = false;
        }
    }
    else
    {
        ret = false;
    }
    return ret;
}
 
bool DataSource::setDataSource(const QString source)
{
    bool ret = true;
    QFile file(source);
    if(file.open(QFile::ReadOnly | QFile::Text))
    {
        QTextStream in(&file);
        while(!in.atEnd())
        {
            DataScore score;
            if(parse(in.readLine(), score))
            {
                m_data.append(score);
            }
        }
        file.close();
    }
    else
    {
        ret = false;
    }
 
    return ret;
}
 
QList<DataScore> DataSource::fetchData()
{
    QList<DataScore> ret = m_data;
    m_data.clear();
    return ret;
}
 
int DataSource::count() const
{
    return m_data.count();
}

 

    DataScore.h文件:

#ifndef DATASCORE_H
#define DATASCORE_H
 
#include <QObject>
#include <QString>
 
class DataScore : public QObject
{
    Q_OBJECT
private:
    QString m_id;
    QString m_name;
    int m_score;
public:
    explicit DataScore(QObject *parent = 0);
    DataScore(QString id, QString name, int score, QObject *parent = 0);
    DataScore(const DataScore& another);
    DataScore& operator=(const DataScore& another);
    QString id() const;
    QString name() const;
    int score() const;
};
 
#endif // DATASCORE_H

    DataScore.cpp文件:

#include "DataScore.h"
 
DataScore::DataScore(QObject *parent) : QObject(parent)
{
    m_id = "NULL";
    m_name = "NULL";
    m_score = -1;
}
 
DataScore::DataScore(QString id, QString name, int score, QObject *parent)
{
    m_id = id;
    m_name = name;
    m_score = score;
}
 
DataScore::DataScore(const DataScore& another)
{
    m_id = another.m_id;
    m_name = another.m_name;
    m_score = another.m_score;
}
 
DataScore& DataScore::operator=(const DataScore& another)
{
    if(this != &another)
    {
        m_id = another.m_id;
        m_name = another.m_name;
        m_score = another.m_score;
    }
    return *this;
}
 
QString DataScore::id() const
{
   return m_id;
}
 
QString DataScore::name() const
{
    return m_name;
}
 
int DataScore::score() const
{
    return m_score;
}

    DataModel.h文件:

#ifndef DATAMODEL_H
#define DATAMODEL_H
 
#include <QObject>
#include <QStandardItem>
#include <QTableView>
#include "DataScore.h"
 
class DataModel : public QObject
{
    Q_OBJECT
    QStandardItemModel m_model;
public:
    explicit DataModel(QObject *parent = 0);
    bool add(const DataScore info);
    bool add(const QList<DataScore> list);
    bool remove(const int index);
    DataScore getRecord(const int row) const;
    int count() const;
    void setView(QTableView& view);
    void clear();
};
 
#endif // DATAMODEL_H

    DataModel.cpp文件:

#include "DataModel.h"
#include <QStandardItem>
#include <QVariant>
 
DataModel::DataModel(QObject *parent) : QObject(parent)
{
}
 
bool DataModel::add(const DataScore info)
{
    bool ret = true;
    QStandardItem* root = m_model.invisibleRootItem();
    QStandardItem* id = new QStandardItem();
    QStandardItem* name = new QStandardItem();
    QStandardItem* score = new QStandardItem();
 
    if( m_model.rowCount() > 0 )
    {
        QStringList list;
        list.append("ID");
        list.append("Name");
        list.append("Score");
        m_model.setHorizontalHeaderLabels(list);
    }
    if( (root != NULL) && (id != NULL) && (name != NULL) && (score != NULL) )
    {
        id->setData(info.id(), Qt::DisplayRole);
        name->setData(info.name(), Qt::DisplayRole);
        score->setData(info.score(), Qt::DisplayRole);
 
        id->setEditable(false);
        name->setEditable(false);
        score->setEditable(false);
 
        int currentRow = count();
 
        root->setChild(currentRow, 0, id);
        root->setChild(currentRow, 1, name);
        root->setChild(currentRow, 2, score);
    }
    else
    {
        ret = false;
    }
    return ret;
}
 
bool DataModel::add(const QList<DataScore> list)
{
    bool ret = true;
    for(int i = 0; i < list.count(); i++)
    {
        ret = ret && add(list[i]);
    }
    return ret;
}
 
bool DataModel::remove(const int index)
{
    bool ret = true;
    if((0 <= index) && (index <= count()))
    {
        m_model.removeRow(index);
    }
    else
    {
        ret = false;
    }
    return ret;
}
 
DataScore DataModel::getRecord(const int row) const
{
    DataScore ret;
    if((0 <= row) && (row <= count()))
    {
        QModelIndex index0 = m_model.index(row, 0, QModelIndex());
        QModelIndex index1 = m_model.index(row, 1, QModelIndex());
        QModelIndex index2 = m_model.index(row, 2, QModelIndex());
        QVariant v0 = index0.data();
        QVariant v1 = index1.data();
        QVariant v2 = index2.data();
        ret = DataScore(v0.toString(), v1.toString(), v2.toInt());
    }
    return ret;
}
 
int DataModel::count() const
{
    return m_model.rowCount();
}
 
void DataModel::setView(QTableView& view)
{
    view.setModel(&m_model);
}
 
void DataModel::clear()
{
    m_model.clear();
}

    DataAppUI.h文件:

#ifndef DATAAPPUI_H
#define DATAAPPUI_H
 
#include <QWidget>
#include <QTableView>
#include <QPushButton>
#include <QMenu>
#include "DataModel.h"
 
class DataAppUI : public QWidget
{
    Q_OBJECT  
private:
    DataModel m_model;
    QTableView m_view;
 
    QPushButton m_refresh;
    QPushButton m_clear;
    QPushButton m_score;
 
    QMenu m_menu;
private slots:
    void onRefresh();
    void onClear();
    void onScore();
    void onDelete();
public:
    DataAppUI(QWidget *parent = 0);
    bool eventFilter(QObject *watched, QEvent *event);
    ~DataAppUI();
};
 
#endif // DATAAPPUI_H

    DataAppUI.cpp文件:

#include "DataAppUI.h"
#include "DataModel.h"
#include "DataScore.h"
#include "DataSource.h"
#include <QEvent>
#include <QMessageBox>
 
DataAppUI::DataAppUI(QWidget *parent):QWidget(parent)
{
    m_view.setParent(this);
    m_view.move(10, 10);
    m_view.resize(300, 200);
    m_view.installEventFilter(this);
 
    m_refresh.setParent(this);
    m_refresh.move(10, 220);
    m_refresh.resize(80, 30);
    m_refresh.setText("Refresh");
 
    m_clear.setParent(this);
    m_clear.move(100, 220);
    m_clear.resize(80,30);
    m_clear.setText("Clear");
 
    m_score.setParent(this);
    m_score.move(190, 220);
    m_score.resize(80, 30);
    m_score.setText("Score");
 
    m_menu.addAction("Delete");
 
    m_model.setView(m_view);
 
    connect(&m_refresh, SIGNAL(clicked()), this, SLOT(onRefresh()));
    connect(&m_clear, SIGNAL(clicked()), this, SLOT(onClear()));
    connect(&m_score, SIGNAL(clicked()), this, SLOT(onScore()));
    connect(m_menu.actions()[0], SIGNAL(triggered()), this, SLOT(onDelete()));
 
    onRefresh();
}
 
void DataAppUI::onRefresh()
{
    DataSource source;
    m_model.clear();
    if(source.setDataSource("C:/Users/apple/Desktop/source.txt"))
    {
        m_model.add(source.fetchData());
    }
    else
    {
        QMessageBox::critical(this, "Data Source Error", "Set Data Source Error", QMessageBox::Ok);
    }
}
 
void DataAppUI::onClear()
{
    m_model.clear();
}
 
void DataAppUI::onScore()
{
    int min = 256;
    int max = 0;
    int average = 0;
 
    if( m_model.count() > 0 )
    {
        for(int i=0; i<m_model.count(); i++)
        {
            DataScore info = m_model.getRecord(i);
 
            if( info.score() < min )
            {
                min = info.score();
            }
            if( info.score() > max )
            {
                max = info.score();
            }
            average += info.score();
        }
        average /= m_model.count();
        QMessageBox::information(this, "Statistic", QString().sprintf("Min: %d\nMax: %d\nAverage: %d", min, max, average), QMessageBox::Ok);
    }
    else
    {
        QMessageBox::information(this, "Statistic", "No data record!", QMessageBox::Ok);
    }
}
 
void DataAppUI::onDelete()
{
    m_model.remove(m_view.currentIndex().row());
}
 
bool DataAppUI::eventFilter(QObject *watched, QEvent *event)
{
    if((watched == &m_view) && (event->type() == QEvent::ContextMenu))
    {
        m_menu.exec(cursor().pos());
    }
    return QWidget::eventFilter(watched, event);
}
 
DataAppUI::~DataAppUI()
{
 
}

    Main.cpp文件:

#include "DataAppUI.h"
#include <QApplication>
 
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    DataAppUI appUI;
    appUI.show();
 
    return app.exec();
}

wKiom1hw-AjBcbUHAABdZXt6-Pc269.png

二、自定义委托

1、委托简介

    Model/View 框架中没有包含一个完整而独立的组件用于管理用户的交互。为了获得对用户输入控制的灵活性,交互工作交给了委托Delegate去完成。View 将用户输入委托给Delegate处理,而自己不去处理用户输入。委托提供一种输入能力,并且能够在某些View中提供交互情形下的渲染,比如在table中通过双击单元格即可编辑内容等。委托的标准接口被定义在QAbstractItemDelegate类中。

    QT用委托的方式完成了用户和模型之间的交互,本质上是基于经典的MVC架构思想。Delegate可以用于渲染内容,通过paint()和sizeHint()函数来完成。对于简单的基于组件Delegate,可以通过继承QItemDelegate或者 QStyledItemDelegate来实现,可以避免要完全重写QAbstractItemDelegate中所需要的所有函数,因为对于一些相对比较通用的函数,在QItemDelegate或者QStyledItemDelegat中已经有了一个默认的实现。 

    QT提供的标准组件使用QItemDelegate提供编辑功能的支持,如QListViewQTableView和QTreeView。View使用的Delegate可以通过itemDelegate()函数获得setItemDelegate()函数则可以为一个标准组件设置自定义的Delegate。

    QT4.4版本后提供了两个可以被继承的Delegate类:QItemDelegate和 QStyledItemDelegate,默认Delegate是QStyledItemDelegate。QItemDelegate和 QStyledItemDelegate可以相互替代,用于给View 组件提供绘制和编辑的功能,主要区别在于QStyledItemDelegate使用当前的风格(style)去绘制组件。因此,在自定义Delegate或者需要使用Qt style sheets时,建议使用 QStyledItemDelegate作为父类。使用这两个类的代码通常是一样的,除了需要使用style进行绘制的部份。    如果delegate不支持为数据类型进行绘制,或者希望自己绘制数据项,那么就可以继承QStyledItemDelegate类,并且重写paint()或者还需要重写sizeHint()函数。paint()函数会被每一个数据项独立调用,而sizeHint()函数则可以定义每一个数据项的大小。在重写paint() 函数的时候,通常需要用if语句找到需要进行渲染的数据类型并进行绘制,其他的数据类型需要调用父类的实现进行绘制。 

2、委托工作机制

委托是视图中处理用户输入的组件。委托对象负责创建和显示用户输入上下文。委托能够提供编辑时需要的上下文环境(编辑器),不同委托提供的编辑器类型不同(文本框、单选框、下拉框等),编辑器使用模型索引从模型中获取数据,并将编辑结果写入模型。

委托的本质是为视图提供数据编辑的上下文环境,是一个产生界面元素的工厂类,能够使用和设置模型中的数据。

    委托工作过程如下:双击视图中的某个数据项时,委托开始创建编辑器(createEditor),将编辑器大小与数据项大小适配(updateEditorGeometry),然后通过模型索引到模型中获取数据项的数据并将数据设置到编辑器显示(setEditorData),编辑数据后提交数据将会触发信号commitData(QWidget *editor),将数据保存到模型中(setModelData),然后将模型中的数据设置到编辑器(setEditorData),关闭编辑器(触发closeEditor信号),最后显示在视图中。

3、自定义委托

    自定义委托需要重写以下函数:

    [virtual] QWidget *QItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const

        创建编辑器

    [virtual] void QItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const

        通过索引从模型中获取数据并设置到编辑器

    [virtual] void QItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const

        将编辑后的数据写入模型

    [virtual] void QItemDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const

        更新编辑器大小

    [virtual] void QItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const

        重绘编辑器

    [signal] void QAbstractItemDelegate::closeEditor(QWidget *editor, QAbstractItemDelegate::EndEditHint hint = NoHint)

        编辑器组件关闭信号

    [signal] void QAbstractItemDelegate::commitData(QWidget *editor)

    数据提交信号

    委托的使用:

    tableView->setItemDelegate(delegate);

        指定视图中所有数据项的委托

    tableView->setItemDelegateForColumn(1, delegate);

    指定视图中第1列的所有项的委托

下拉框委托实例:

ComboBoxDelegate.h文件:

#ifndef COMBOBOXDELEGATE_H
#define COMBOBOXDELEGATE_H
 
#include <QItemDelegate>
 
class ComboBoxDelegate : public QItemDelegate
{
    Q_OBJECT
public:
    ComboBoxDelegate(QObject* parent = 0);
 
    QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const;
    void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const;
    void setEditorData(QWidget *editor, const QModelIndex &index) const;
    void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const;
};
 
#endif // COMBOBOXDELEGATE_H

ComboBoxDelegate.cpp文件:

#include "ComboBoxDelegate.h"
#include <QComboBox>
 
ComboBoxDelegate::ComboBoxDelegate(QObject* parent):QItemDelegate(parent)
{
 
}
 
QWidget* ComboBoxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    QWidget* ret = NULL;
    if(index.data().type() == QVariant::String)
    {
        QComboBox *combo = new QComboBox(parent);
        if(combo != NULL)
        {   //在下拉框中增加选项
            combo->addItem("C");
            combo->addItem("C++");
            combo->addItem("Java");
            combo->addItem("Python");
        }
        ret = combo;
    }
    else
    {
        ret = QItemDelegate::createEditor(parent, option, index);
    }
    return ret;
}
 
void ComboBoxDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    editor->setGeometry(option.rect);
}
 
void ComboBoxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
    if(index.data().type() == QVariant::String)
    {
        QComboBox *combo = dynamic_cast<QComboBox*>(editor);
        if(combo != NULL)
        {
            for(int i = 0; i < combo->count(); i++)
            {
                if(combo->itemText(i) == index.data().toString())
                {
                    combo->setCurrentIndex(i);
                    break;
                }
            }
        }
    }
    else
    {
        QItemDelegate::setEditorData(editor, index);
    }
}
 
void ComboBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
    if(index.data().type() == QVariant::String)
    {
        QComboBox* combo = dynamic_cast<QComboBox*>(editor);
        if(combo != NULL)
        {
            model->setData(index, combo->currentText(), Qt::DisplayRole);
        }
    }
    else
    {
        QItemDelegate::setModelData(editor, model, index);
    }
}

4、修改视图的默认显示方式

委托作为视图的一部分,需要承担数据显示的部分工作,视图负责确定模型中数据项的组织方式在视图中显示(列表、树形结构、表格),视图中的委托负责具体数据项的显示和编辑(数据值,编辑器),视图和委托共同完成了数据显示功能和数据编辑功能。

视图中数据项的显示方式可以在自定义委托中修改,需要重写以下函数:

A、重写paint函数,在paint函数中自定义数据显示方式

B、重写editorEvent函数,在editorEvent函数中处理交互事件

数据项的进度条显示实例:

ProgressBarDelegate.h文件:

#ifndef PROGRESSBARDELEGATE_H
#define PROGRESSBARDELEGATE_H
 
#include <QItemDelegate>
 
class ProgressBarDelegate : public QItemDelegate
{
    Q_OBJECT
public:
    ProgressBarDelegate(QObject* parent = 0);
    void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
    bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index);
};
 
#endif // PROGRESSBARDELEGATE_H

    ProgressBarDelegate.cpp文件:

#include "ProgressBarDelegate.h"
#include <QApplication>
#include <QEvent>
 
ProgressBarDelegate::ProgressBarDelegate(QObject* parent):QItemDelegate(parent)
{
}
 
void ProgressBarDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    if(index.data().type() == QVariant::Int)
    {
        const int delta = 4;
        int progress = index.data().toInt();
        QStyleOptionProgressBar progressBarOption;
        int top = option.rect.top() + delta;
        int left = option.rect.left() + delta;
        int width = option.rect.width() - 2*delta;
        int height = option.rect.height() - 2*delta;
 
        progressBarOption.rect = QRect(left, top, width, height);
        progressBarOption.minimum = 0;
        progressBarOption.maximum = 100;
        progressBarOption.progress = progress;
        progressBarOption.textVisible = true;
        progressBarOption.text = QString().sprintf("%d", progress) + "%";
        progressBarOption.textAlignment = Qt::AlignCenter;
        QApplication::style()->drawControl(QStyle::CE_ProgressBar, &progressBarOption, painter);
    }
    else
    {
        QItemDelegate::paint(painter, option, index);
    }
}
 
bool ProgressBarDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)
{
    bool ret = true;
    if(event->type() != QEvent::MouseButtonDblClick)
    {
        QItemDelegate::editorEvent(event, model, option, index);
    }
    return ret;
}

    设置某一列数据项使用ProgressBarDelegate

    m_view.setItemDelegateForColumn(2, &m_delegate);

wKioL1hw-E6AYGobAAATCpPsgGM593.png

    注意:设置ProgressBarDelegate的列的数据项的值必须是0-100的整数

三、编程实例

1、自定义视图

自定义一个柱状图的视图类。

HistogramView.h文件:

#ifndef HISTOGRAMVIEW_H
#define HISTOGRAMVIEW_H
 
#include <QAbstractItemView>
 
class HistogramView : public QAbstractItemView
{
    Q_OBJECT
public:
    HistogramView(QWidget *parent=0);
 
    QRect visualRect(const QModelIndex &index)const;
    void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible);
    QModelIndex indexAt(const QPoint &point) const;
 
    void paintEvent(QPaintEvent *);
    void mousePressEvent(QMouseEvent *);
 
    void setSelectionModel(QItemSelectionModel * selectionModel);
    QRegion itemRegion(QModelIndex index);
 
protected slots:
    void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
    void selectionChanged(const QItemSelection & selected, const QItemSelection & deselected );
 
protected:
    QModelIndex moveCursor(QAbstractItemView::CursorAction cursorAction,
                            Qt::KeyboardModifiers modifiers);
    int horizontalOffset() const;
    int verticalOffset() const;
    bool isIndexHidden(const QModelIndex &index) const;
    void setSelection ( const QRect&rect, QItemSelectionModel::SelectionFlags flags );
    QRegion visualRegionForSelection(const QItemSelection &selection) const;
 
private:
    QItemSelectionModel *selections;
 
    QList<QRegion> listRegionM;
    QList<QRegion> listRegionF;
    QList<QRegion> listRegionS;
 
};
 
#endif // HISTOGRAMVIEW_H

HistogramView.cpp文件:

#include "HistogramView.h"
#include <QMouseEvent>
#include <QPainter>
 
HistogramView::HistogramView(QWidget *parent):QAbstractItemView(parent)
{}
 
void HistogramView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
{
    QAbstractItemView::dataChanged(topLeft, bottomRight);
 
    viewport()->update();
}
 
QRect HistogramView::visualRect(const QModelIndex &index) const
{
 
}
 
void HistogramView::scrollTo(const QModelIndex &index, ScrollHint hint)
{}
 
QModelIndex HistogramView::indexAt(const QPoint &point) const
{
    QPoint newPoint(point.x(),point.y());
 
    QRegion region;
    foreach(region,listRegionM)
    {
        if (region.contains(newPoint))
        {
            int row = listRegionM.indexOf(region);
            QModelIndex index = model()->index(row, 3,rootIndex());
            return index;
        }
    }
 
    return QModelIndex();
}
 
QModelIndex HistogramView::moveCursor(QAbstractItemView::CursorAction cursorAction,Qt::KeyboardModifiers modifiers)
{}
 
int HistogramView::horizontalOffset() const
{
}
 
int HistogramView::verticalOffset() const
{}
 
bool HistogramView::isIndexHidden(const QModelIndex &index) const
{}
 
void HistogramView::setSelectionModel(QItemSelectionModel * selectionModel)
{
    selections = selectionModel;
}
 
void HistogramView::mousePressEvent(QMouseEvent *e)
{
    QAbstractItemView::mousePressEvent(e);
    setSelection(QRect(e->pos().x(),e->pos().y(),1,1),QItemSelectionModel::SelectCurrent);
}
 
QRegion HistogramView::itemRegion(QModelIndex index)
{
    QRegion region;
 
    if (index.column() == 3)
        region = listRegionM[index.row()];
 
    return region;
}
 
void HistogramView::setSelection ( const QRect &rect, QItemSelectionModel::SelectionFlags flags )
{
     int rows = model()->rowCount(rootIndex());
     int columns = model()->columnCount(rootIndex());
     QModelIndex selectedIndex;
 
     for (int row = 0; row < rows; ++row)
     {
         for (int column = 1; column < columns; ++column)
         {
             QModelIndex index = model()->index(row, column, rootIndex());
             QRegion region = itemRegion(index);
 
             if (!region.intersected(rect).isEmpty())
             selectedIndex = index;
         }
     }
 
     if(selectedIndex.isValid())
         selections->select(selectedIndex,flags);
     else
     {
         QModelIndex noIndex;
         selections->select(noIndex, flags);
     }
}
 
QRegion HistogramView::visualRegionForSelection(const QItemSelection &selection) const
{}
 
void HistogramView::selectionChanged(const QItemSelection & selected, const QItemSelection & deselected )
{
    viewport()->update();
}
 
void HistogramView::paintEvent(QPaintEvent *)
{
    QPainter painter(viewport());
 
    painter.setPen(Qt::black);
    int x0 = 40;
    int y0 = 250;
 
    // draw coordinate
    painter.drawLine(x0, y0, 40, 30);
    painter.drawLine(38, 32, 40, 30);
    painter.drawLine(40, 30, 42, 32);
    painter.drawText(5, 45, tr("income"));
 
    for (int i=1; i<5; i++) {
        painter.drawLine(-1,-i*50,1,-i*50);
        painter.drawText(-20,-i*50,tr("%1").arg(i*5));
    }
 
    // x轴
    painter.drawLine(x0, y0, 540, 250);
    painter.drawLine(538, 248, 540, 250);
    painter.drawLine(540, 250, 538, 252);
    painter.drawText(500, 270, tr("name"));
    int row;
    // name
    int posD = x0+20;
    for (row = 0; row < model()->rowCount(rootIndex()); row++)
    {
        QModelIndex index = model()->index(row, 0, rootIndex());
        QString dep = model()->data(index).toString();
 
        painter.drawText(posD,y0+20,dep);
        posD += 50;
    }
    // income
    int posM = x0+20;
    for (row = 0; row < model()->rowCount(rootIndex()); row++)
    {
        QModelIndex index = model()->index(row, 3, rootIndex());
        int income = model()->data(index).toDouble();
 
        int width = 10;
 
        if (selections->isSelected(index))
            painter.setBrush(QBrush(Qt::darkBlue,Qt::SolidPattern));
        else
            painter.setBrush(Qt::blue);
 
        painter.drawRect(QRectF(posM + 10, y0-income/25, width, income/25));
        QRegion regionM(posM + 10, y0-income/25, width, income/25);
        listRegionM << regionM;
 
        posM += 50;
    }
}

2、自定义委托

A、自定义日期委托

DateDelegate.h文件:

#ifndef DATEDELEGATE_H
#define DATEDELEGATE_H
 
#include <QItemDelegate>
 
class DateDelegate : public QItemDelegate
{
    Q_OBJECT
public:
    DateDelegate(QObject* parent = 0);
    QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const;
    void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const;
    void setEditorData(QWidget *editor, const QModelIndex &index) const;
    void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const;
};
 
#endif // DATEDELEGATE_H

    DateDelegate.cpp文件:

#include "DateDelegate.h"
#include <QDateTimeEdit>
 
DateDelegate::DateDelegate(QObject* parent):QItemDelegate(parent)
{
 
}
 
QWidget* DateDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    QWidget* ret = NULL;
    if(index.data().type() == QVariant::String)
    {
        QDateTimeEdit* date = new QDateTimeEdit(parent);
        date->setDisplayFormat("yyyy-MM-dd");
        date->setCalendarPopup(true);
        ret = date;
    }
    else
    {
        ret = QItemDelegate::createEditor(parent, option, index);
    }
    return ret;
}
 
void DateDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    editor->setGeometry(option.rect);
}
 
void DateDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
    if(index.data().type() == QVariant::String)
    {
        QString datestr = index.data().toString();
        QDate date = QDate::fromString(datestr, Qt::ISODate);
        QDateTimeEdit* edit = dynamic_cast<QDateTimeEdit*>(editor);
        edit->setDate(date);
    }
    else
    {
        QItemDelegate::setEditorData(editor, index);
    }
}
 
void DateDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
    if(index.data().type() == QVariant::String)
    {
        QDateTimeEdit* edit = dynamic_cast<QDateTimeEdit*>(editor);
        QDate date = edit->date();
        model->setData(index, QVariant(date.toString(Qt::ISODate)));
    }
    else
    {
        QItemDelegate::setModelData(editor, model, index);
    }
}

B、自定义下拉列表框委托

    ComboBoxDelegate.h文件:

#ifndef COMBOBOXDELEGATE_H
#define COMBOBOXDELEGATE_H
 
#include <QItemDelegate>
 
class ComboBoxDelegate : public QItemDelegate
{
    Q_OBJECT
public:
    ComboBoxDelegate(QObject* parent = 0);
 
    QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const;
    void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const;
    void setEditorData(QWidget *editor, const QModelIndex &index) const;
    void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const;
};
 
#endif // COMBOBOXDELEGATE_H

    ComboBoxDelegate.cpp文件:

#include "ComboBoxDelegate.h"
#include <QComboBox>
 
ComboBoxDelegate::ComboBoxDelegate(QObject* parent):QItemDelegate(parent)
{
 
}
 
QWidget* ComboBoxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    QWidget* ret = NULL;
    if(index.data().type() == QVariant::String)
    {
        QComboBox *combo = new QComboBox(parent);
        if(combo != NULL)
        {   //在下拉框中增加选项
            combo->addItem(QString::fromLocal8Bit("工人"));
            combo->addItem(QString::fromLocal8Bit("农民"));
            combo->addItem(QString::fromLocal8Bit("医生"));
            combo->addItem(QString::fromLocal8Bit("律师"));
            combo->addItem(QString::fromLocal8Bit("军人"));
        }
        ret = combo;
    }
    else
    {
        ret = QItemDelegate::createEditor(parent, option, index);
    }
    return ret;
}
 
void ComboBoxDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    editor->setGeometry(option.rect);
}
 
void ComboBoxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
    if(index.data().type() == QVariant::String)
    {
        QComboBox *combo = dynamic_cast<QComboBox*>(editor);
        if(combo != NULL)
        {
            for(int i = 0; i < combo->count(); i++)
            {
                if(combo->itemText(i) == index.data().toString())
                {
                    combo->setCurrentIndex(i);
                    break;
                }
            }
        }
    }
    else
    {
        QItemDelegate::setEditorData(editor, index);
    }
}
 
void ComboBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
    if(index.data().type() == QVariant::String)
    {
        QComboBox* combo = dynamic_cast<QComboBox*>(editor);
        if(combo != NULL)
        {
            model->setData(index, combo->currentText(), Qt::DisplayRole);
        }
    }
    else
    {
        QItemDelegate::setModelData(editor, model, index);
    }
}

C、自定义整数旋转框委托

SpinDelegate.h文件:

#ifndef SPINDELEGATE_H
#define SPINDELEGATE_H
 
#include <QItemDelegate>
 
class SpinDelegate : public QItemDelegate
{
public:
    SpinDelegate(QObject *parent = 0);
    QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE;
    void setEditorData(QWidget *editor, const QModelIndex &index) const;
    void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const;
    void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const;
};
 
#endif // SPINDELEGATE_H

SpinDelegate.cpp文件:

#include "SpinDelegate.h"
#include <QSpinBox>
 
SpinDelegate::SpinDelegate(QObject *parent):QItemDelegate(parent)
{
}
 
QWidget *SpinDelegate::createEditor(QWidget *parent,
    const QStyleOptionViewItem &/* option */,
    const QModelIndex &/* index */) const
{
    QSpinBox *editor = new QSpinBox(parent);
    editor->setRange(1000,10000);
 
    editor->installEventFilter(const_cast<SpinDelegate*>(this));
 
    return editor;
}
 
void SpinDelegate::setEditorData(QWidget *editor,
                                     const QModelIndex &index) const
{
    int value = index.model()->data(index).toInt();
 
    QSpinBox *spin = static_cast<QSpinBox*>(editor);
 
    spin->setValue(value);
}
 
void SpinDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
                                    const QModelIndex &index) const
{
    QSpinBox *spin = static_cast<QSpinBox*>(editor);
    int value = spin->value();
 
    model->setData(index, value);
}
 
void SpinDelegate::updateEditorGeometry(QWidget *editor,
    const QStyleOptionViewItem &option, const QModelIndex &/* index */) const
{
    editor->setGeometry(option.rect);
}

3、程序应用

定义界面类

    Mainwindow.h文件:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H
 
#include <QMainWindow>
#include <QStandardItemModel>
#include "DateDelegate.h"
#include "ComboBoxDelegate.h"
#include "SpinDelegate.h"
#include "HistogramView.h"
#include <QTableView>
#include <QFile>
#include <QSplitter>
#include <QTableView>
 
class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = 0);
private:
    DateDelegate *datedelegate;
    ComboBoxDelegate *comboboxdelegate;
    SpinDelegate *spindelegate;
    QStandardItemModel *model;
    QTableView *tableview;
    HistogramView *histogramview;
    QTableView *currencyview;
};
 
#endif // MAINWINDOW_H

    Mainwindow.cpp文件:

#include "mainwindow.h"
#include <QTextStream>
 
MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent)
{
    model = new QStandardItemModel(4, 4, this);
    model->setHeaderData(0,Qt::Horizontal,QObject::tr("Name"));
    model->setHeaderData(1,Qt::Horizontal,QObject::tr("Birthday"));
    model->setHeaderData(2,Qt::Horizontal,QObject::tr("Job"));
    model->setHeaderData(3,Qt::Horizontal,QObject::tr("Income"));
 
    tableview = new QTableView(this);
    tableview->setModel(model);
    datedelegate = new DateDelegate(this);
    comboboxdelegate = new ComboBoxDelegate(this);
    spindelegate = new SpinDelegate(this);
    tableview->setItemDelegateForColumn(1, datedelegate);
    tableview->setItemDelegateForColumn(2, comboboxdelegate);
    tableview->setItemDelegateForColumn(3, spindelegate);
 
    histogramview = new HistogramView(this);
    histogramview->setModel(model);
 
    QFile file(":/data.tab");
    if (file.open(QFile::ReadOnly | QFile::Text))
    {
         QTextStream stream(&file);
         QString line;
 
         model->removeRows(0, model->rowCount(QModelIndex()), QModelIndex());
         int row = 0;
         do
         {
                 line = stream.readLine();
                 if (!line.isEmpty())
                 {
                     model->insertRows(row, 1, QModelIndex());
                     QStringList pieces = line.split(",", QString::SkipEmptyParts);
                     model->setData(model->index(row, 0, QModelIndex()),pieces.value(0));
                     model->setData(model->index(row, 1, QModelIndex()),pieces.value(1));
                     model->setData(model->index(row, 2, QModelIndex()),pieces.value(2));
                     model->setData(model->index(row,3, QModelIndex()),pieces.value(3));
                     row++;
                 }
            } while (!line.isEmpty());
            file.close();
     }
 
    QItemSelectionModel *selectionModel = new QItemSelectionModel(model);
    tableview->setSelectionModel(selectionModel);
    histogramview->setSelectionModel(selectionModel);
 
    connect(selectionModel,SIGNAL(selectionChanged(const QItemSelection, const QItemSelection)),
            histogramview,SLOT(selectionChanged(const QItemSelection, const QItemSelection)));
    connect(selectionModel,SIGNAL(selectionChanged(const QItemSelection, const QItemSelection)),
            tableview,SLOT(selectionChanged(const QItemSelection, const QItemSelection)));
 
     QSplitter *splitter = new QSplitter(this);
     splitter->setOrientation(Qt::Vertical);
 
     tableview->setMaximumHeight(150);
     histogramview->setMaximumHeight(300);
     splitter->addWidget(tableview);
     splitter->addWidget(histogramview);
     setCentralWidget(splitter);
     setWindowTitle(tr("table View"));
     setMinimumSize(600, 480);
}

    Main.cpp文件:

#include "mainwindow.h"
#include <QApplication>
 
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
 
    return a.exec();
}


wKiom1hw-ISiVANFAABKK_Actas649.png



    本文源码见附件