Qt例子学习笔记 - Examples/Qt-6.2.0/charts/barmodelmapper

//将数据模型与条形图结合使用
//让我们从创建 CustomTableModel 类的实例开始。
//CustomTableModel 类是从 QAbstractTableModel 派生的,它是为本示例而创建的。
//此类的构造函数使用图表示例所需的数据填充模型的内部数据存储。

    m_model = new CustomTableModel;

//我们现在有一个模型,其中包含我们希望在图表和 QTableView 中显示的数据
//首先,我们创建 QTableView 并告诉它使用模型作为数据源。
//为了很好地呈现数据,设置了表格视图的最小宽度,并将其标题调整大小模式更改为拉伸。

    //create table view and add model to it
    QTableView *tableView = new QTableView;
    tableView->setModel(m_model);
    tableView->setMinimumWidth(300);
    tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
    tableView->verticalHeader()->setSectionResizeMode(QHederView::Stretch);
    m_model->setParent(tableView);

//现在我们需要一个 QChart 实例来在图表上显示相同的数据。 我们还启用动画。
//它可以更轻松地查看对模型数据的修改如何影响图表。

    QChart *chart = new QChart;
    chart->setAnimationOptions(QChart::AllAnimations);

//下面代码的第一行创建了新的柱状图系列。
//变量 firstRow 和 rowCount 用于定义自定义模型映射。
//自定义映射允许仅从模型中获取部分数据。
//在这种情况下,从索引为 3 的行开始的 5 行数据。
//以下三行创建 QVBarModelMapper 类的实例
//并指定条形集的数据应从索引为 1 到 4 的模型的列中获取
//为了在系列和模型之间建立连接,我们将这两个对象都设置为 QVBarModelMapper。
//最后将系列添加到图表中。

    QBarSeries *series = new QBarSeries;

    int first = 3;
    int count = 5;
    QVBarModelMaper *mapper = new QVBarModelMapper(this);
    mapper->setFirstBarSetColumn(1);
    mapper->setLastBarSetColumn(4);
    mapper->setFirstRow(first);
    mapper->setRowCount(count);
    mapper->setSeries(m_model);
    mapper->setModel(m_model);
    chart->addSeries(series);

//为了在 QTableView 中显示哪些数据对应于哪个条形集,本示例使用表格着色。
//当系列添加到图表时,它会根据当前选择的主题分配一种颜色。
//下面的代码从系列中提取该颜色并使用它来创建彩色 QTableView。
//视图的着色不是 QChart 功能的一部分。

    //for storing color hex from the series
    QString seriesColorHex = "#000000";

    //get the color of the series and use it for showing the mapped area
    QList<QBarSet *>barsets = series->barSets();
    for(int i = 0; i <barsets.count(); i++)
    {
        seriesColorHex = "#" + QString::number(barsets.at(i)->brush().color().rgb(),16).right(6).toUpper();
        m_model->addMapping(seriesColorHex,QRect(1 + i,first,1,barsets.at(i)->count());
    }

//我们希望在图表的轴上放置类别来描述数据的含义
//下一个片段展示了如何做到这一点。

    QStringList categories;
    categories << "April"<<"May"<<"June"<<"July"<<"August";
    QBarCategoryAxis *axisX = new QBarCategoryAxis();
    axisX->append(categories);
    chart->addAxis(axisX,Qt::AlignBottom);
    series->attachAxis(axisX);
    QValueAxis *axisY = new QValueAxis();
    chart->addAxis(axisY,Qt::AlignLeft);
    series->attachAxis(axisY);

//为了避免设置 QGraphicsScene,我们使用 QChartView 类为我们完成它。
//QChart 对象指针用作 QChartView 构造函数的参数。
//为了使渲染看起来更好,打开抗锯齿并设置图表视图小部件的最小尺寸

    QChartView *chartView = new QChartView(chart);
    chartView->setRenderHint(QPainter::Antialiasing);
    chartView->setMinimumSize(640,480);

//最后,我们将两个小部件放在一个布局中,并将该布局用作应用程序布局。

    //create maim layout
    QGridLayout *mainLayout = new QGridLayout;
    mainLayout->addWidget(tableView,1,0);
    mainLayout->addWidget(chartView,1,1);
    mainLayout->setColumnStretch(1,1);
    mainLayout->setColumnStretch(0,0);
    setLayout(mainLayout);

main.cpp

#include 
#include "tablewidget.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    TableWidget w;
    w.show();
    return a.exec();
}

customtablemodel.h

#ifndef CUSTOMTABLEMODEL_H
#define CUSTOMTABLEMODEL_H

#include 
#include 
#include 

//QAbstractTableModel 为将其数据表示为项目的二维数组的模型提供了标准接口。
//不直接使用,但必须子类化
//由于该模型提供了比 QAbstractItemModel 更专业的接口
//因此它不适合与树视图一起使用,
//尽管它可以用于向 QListView 提供数据。
//如果您需要表示一个简单的项目列表
//并且只需要一个模型来包含单列数据
//则对 QAbstractListModel 进行子类化可能更合适。
//rowCount() 和 columnCount() 函数返回表的维度。
//要检索与模型中的项目相对应的模型索引
//请使用 index() 并仅提供行号和列号。
//子类化
//在继承 QAbstractTableModel 时,您必须实现 rowCount()、columnCount() 和 data()。
//index() 和 parent() 函数的默认实现由 QAbstractTableModel 提供。
//表现良好的模型也将实现 headerData()。
//可编辑模型需要实现 setData(),
//并实现 flags() 以返回包含 Qt::ItemIsEditable 的值。
//为可调整大小的数据结构提供接口的模型可以提供
//insertRows()、removeRows()、insertColumns() 和 removeColumns() 的实现。
//在实现这些功能时,重要的是调用适当的功能,以便所有连接的视图都知道任何更改:
//insertRows() 实现必须在将新行插入数据结构之前调用beginInsertRows(),
//并且在之后必须立即调用 endInsertRows()。
//insertColumns() 实现必须在将新列插入数据结构之前调用 beginInsertColumns(),
//并且在之后必须立即调用 endInsertColumns()。
//removeRows() 实现必须在从数据结构中删除行之前调用
//beginRemoveRows() ,之后必须立即调用 endRemoveRows() 。
//removeColumns() 实现必须在从数据结构中删除列之前调用 beginRemoveColumns(),
//之后必须立即调用 endRemoveColumns()。

class CustomTableModel : public QAbstractTableModel
{
    Q_OBJECT
public:
    explicit CustomTableModel(QObject *parent = 0);
    virtual ~CustomTableModel();
    //[pure virtual invokable] int QAbstractItemModel::rowCount(const QModelIndex &parent = QModelIndex()) const
    //返回给定父项下的行数。
    //当父项有效时,这意味着 rowCount 正在返回父项的子项数。
    //注意:在实现基于表的模型时,rowCount() 在父级有效时应返回 0。
    //注意:这个函数可以通过元对象系统和 QML 调用。 请参阅 Q_INVOKABLE。
    int rowCount(const QModelIndex &parent = QModelIndex()) const;
    //[pure virtual invokable] int QAbstractItemModel::columnCount(const QModelIndex &parent = QModelIndex()) const
    //返回给定父级的子级的列数。
    //在大多数子类中,列数与父类无关。
    /*
        int DomModel::columnCount(const QModelIndex &parent) const
        {
            Q_UNUSED(parent)
            return 3;
        }

    */
    //注意:在实现基于表的模型时,
    //columnCount() 应在父项有效时返回 0。
    //注意:这个函数可以通过元对象系统和 QML 调用。 请参阅 Q_INVOKABLE。s
    int columnCount(const QModelIndex &parent = QModelIndex()) const;
    
    //[virtual invokable] QVariant QAbstractItemModel::headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const
    //返回具有指定orientation的标题中给定角色和部分的数据。
    //对于水平标题, section number对应于列号。
    //同样,对于垂直标题, section number对应于行号。

    //enum Qt::Orientationflags Qt::Orientations
    //此类型用于表示对象的方向。
    //Qt::Horizontal
    //Qt::Vertica
    QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
    //[pure virtual invokable] QVariant QAbstractItemModel::data(const QModelIndex &index, int role = Qt::DisplayRole) const
    //返回存储在给定角色下的数据,用于索引引用的项目。
    //注意:如果您没有要返回的值,请返回无效的 QVariant 而不是返回 0。
    //注意:这个函数可以通过元对象系统和 QML 调用。 请参阅 Q_INVOKABLE。
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
    //[virtual invokable] bool QAbstractItemModel::setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole)
    //将索引处项目的角色数据设置为值。
    //成功则返回真; 否则返回false。
    //如果成功设置了数据,则应发出 dataChanged() 信号。
    //基类实现返回 false。 必须为可编辑模型重新实现此函数和 data()。
    bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
    //[virtual invokable] Qt::ItemFlags QAbstractItemModel::flags(const QModelIndex &index) const
    //返回给定索引的项目标志。
    //基类实现返回启用项目 (ItemIsEnabled) 并允许选择项目
    //(ItemIsSelectable) 的标志组合。
    Qt::ItemFlags flags(const QModelIndex &index) const;

    void addMapping(QString color, QRect area);
    void clearMapping() { m_mapping.clear(); }

private:
    QList<QList<qreal> *> m_data;
    QMultiHash<QString, QRect> m_mapping;
    int m_columnCount;
    int m_rowCount;
};

#endif // CUSTOMTABLEMODEL_H

customtablemodel.cpp

#include "customtablemodel.h"
#include 
#include 
#include 
#include 
#include 

CustomTableModel::CustomTableModel(QObject *parent) :
    QAbstractTableModel(parent)
{
    m_columnCount = 6;
    m_rowCount = 12;

    // m_data
    //QList 是 Qt 的通用容器类之一。
    //它将其项目存储在相邻的内存位置并提供基于索引的快速访问。
    //QVector 在 Qt 5 中曾经是一个不同的类,但现在是 QList 的一个简单别名。
    //QList 和 QVarLengthArray 提供类似的 API 和功能。
    //它们通常可以互换,但会产生性能后果。 以下是用例的概述:
    //QList 应该是您的默认首选。QList 应该是您默认的首选。
    //QVarLengthArray 提供了一个在堆栈上保留空间的数组,但如果需要,可以动态地增长到堆上。
    //用于通常较小的短寿命容器是很好的。
    //如果您需要一个真正的链表,它可以保证在列表中间插入恒定时间,并使用迭代器到项目而不是索引,请使用 std::list。
    //注意:QList 和 QVarLengthArray 都保证与 C 兼容的数组布局。
    //注意:Qt 5 中的 QList 并不总是具有与 C 兼容的数组布局,
    //我们经常建议使用 QVector 来代替,以获得更可预测的性能。
    //在 Qt 6 中不再是这种情况,两个类现在共享一个实现并且可以互换使用。
    //下面是一个存储整数的 QList 和一个存储 QString 值的 QList 的示例:
    /*
        QList integerList;
        QList stringList;
    */
    //QList 将其项存储在连续内存数组中。
    //通常,列表是以初始大小创建的。
    //例如,以下代码构造了一个包含 200 个元素的 QList:
    //QList list(200);
    //这些元素会自动使用默认构造的值进行初始化。
    //如果要使用不同的值初始化列表,请将该值作为第二个参数传递给构造函数:
    //QList list(200,"Pass");
    //您还可以随时调用 fill() 以使用值填充列表。
    //QList 使用基于 0 的索引,
    //就像 C++ 数组一样。
    //要访问特定索引位置的项目,您可以使用 operator[]()。
    //在非常量列表上,operator[]() 返回对可以在赋值左侧使用的项的引用:
    /*
        if(list[0] == "Liz")
            list[0] = "Elizabeth";
    */
    //对于只读访问,另一种语法是使用 at():
    /*
        for(qsizetype i = 0; i < list.size(); i++)
            if(list.at(i) == "Alfonso")
                cout<<"Found Alfonso at positiojn "<
    //at() 可能比 operator[]() 更快,因为它永远不会导致发生深拷贝。
    //访问存储在 QList 中的数据的另一种方法是调用 data()。
    //该函数返回一个指向列表中第一项的指针。
    //您可以使用指针直接访问和修改存储在列表中的元素。
    //如果您需要将 QList 传递给接受普通 C++ 数组的函数,则该指针也很有用。

    for (int i = 0; i < m_rowCount; i++) {
        QList<qreal> *dataList = new QList<qreal>(m_columnCount);
        for (int k = 0; k < dataList->size(); k++) {
            if (k % 2 == 0)
                //void QList::replace(qsizetype i, QList::parameter_type value)
                //void QList::replace(qsizetype i, QList::rvalue_ref value)
                //用值替换索引位置 i 处的项目。
                //i 必须是列表中的有效索引位置(即 0 <= i < size())。
                dataList->replace(k, i * 50 + QRandomGenerator::global()->bounded(20));
            else
                //QRandomGenerator 可用于从高质量随机数生成器生成随机值。
                //与 C++ 随机引擎一样, QRandomGenerator 可以通过构造函数使用用户提供的值进行seed。
                //seed此类生成的数字序列是确定性的。也就是说,给定相同的种子数据,
                //QRandomGenerator 将生成相同的数字序列。
                //但给定不同的种子,结果应该有很大不同。
                //QRandomGenerator::securelySeed() 可用于创建一个QRandomGenerator,
                //该 QRandomGenerator 使用 QRandomGenerator::system() 进行安全seed
                //意味着它生成的数字序列无法轻易预测。
                //此外, QRandomGenerator::global() 返回 QRandomGenerator 的全局实例,Qt 将确保安全seed。
                //这个对象是线程安全的,可以在大多数用途中共享,并且总是从 QRandomGenerator::system()
                //QRandomGenerator::system() 可用于访问系统的密码安全随机生成器。
                //在 Unix 系统上,它相当于从 /dev/urandom 或 getrandom() 或 getentropy() 系统调用读取。
                //该类可以生成 32 位或 64 位数量,
                //或填充这些数量的数组
                //生成新值的最常见方法是调用 generate()、generate64() 或 fillRange() 函数。 人们会将其用作:
                //quint32 value = QRandomGenerator::global()->generate();
                //此外,它还提供了一个浮点函数 generateDouble()
                //该函数返回 [0, 1) 范围内的数字(即,包含零且不包含 1)
                //还有一组方便的函数,可以方便地获得一个有界整数范围内的随机数
                dataList->replace(k, QRandomGenerator::global()->bounded(100));
                //Seeding and determinism
                //QRandomGenerator 可以使用特定的种子数据进行播种。
                //完成后,对象生成的数字将始终相同,如下例所示:
                /*
                    QRandomGenerator prng1(1234),prng2(1234);
                    Q_ASSERT(prng1.generate() == prng2.generate());
                    Q_ASSERT(prng1.generate64() == prng2.generate());
                */
                //种子数据采用一个或多个 32 位字的形式。
                //理想的种子大小大约等于 QRandomGenerator 类本身的大小。
                //由于种子数据的混合
                //QRandomGenerator 不能保证不同的种子会产生不同的序列。
                //QRandomGenerator::global() 就像所有由
                //QRandomGenerator::securelySeded() 创建的生成器一样
                //总是从 QRandomGenerator::system() 中生成种子
                //因此不可能让它产生相同的序列。

                //批量数据
                //在确定性模式下运行时, QRandomGenerator 可用于批量数据生成。
                //事实上,建议不需要加密安全或真正随机数据的应用程
                //序使用常规 QRandomGenerator 而不是 QRandomGenerator::system() 来满足其随机数据需求。
                //为了方便使用,QRandomGenerator 提供了一个可以方便使用的全局对象,如下例所示:
                /*
                    int x = QRandomGenerator::global()->generate();
                    int y = QRandomGenerator::global()->generate();
                    int w = QRandomGenerator::global()->bounded(16384);
                    int h = QRandomGenerator::global()->bounded(16384);
                */
            //int QRandomGenerator::bounded(int highest)
            //生成一个介于 0(含)和最高(不含)之间的随机 32 位数量。 最高必须为正。
            //请注意,此函数不能用于获取 int 的完整 32 位范围内的值。 相反,使用 generate() 并转换为 int。
        }
        m_data.append(dataList);
    }
}

CustomTableModel::~CustomTableModel()
{
    //template  void qDeleteAll(ForwardIterator begin, ForwardIterator end)
    //使用 C++ 删除运算符删除 [begin, end) 范围内的所有项目。 项目类型必须是指针类型(例如,QWidget *)
    //Example:
    /*
        QList list;
        list.append(new Employee("Blackpool","Stephen"));
        list.append(new Employee("Twist","Oliver"));

        qDeleteAll(list.begin(),list.end());
        list.clear();
    */
    //请注意, qDeleteAll() 不会从容器中删除项目;
    //它只是对它们调用 delete 。
    //在上面的例子中,我们在容器上调用 clear() 来删除项目。
    //该函数还可用于删除存储在关联容器中的项目,例如 QMap 和 QHash。
    //此函数只会删除每个容器中存储的对象; 用作键的对象不会被删除。
    qDeleteAll(m_data);
}

int CustomTableModel::rowCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent);
    return m_data.count();
}

int CustomTableModel::columnCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent);
    return m_columnCount;
}

QVariant CustomTableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    //enum Qt::ItemDataRole
    //模型中的每个项目都有一组与之关联的数据元素
    //每个元素都有自己的角色。
    //视图使用角色来向模型指示它需要哪种类型的数据。
    //自定义模型应该返回这些类型的数据。
    //通用角色(和相关类型)是:
    //Qt::DisplayRole 要以文本形式呈现的关键数据。 (QString)
    //Qt::DecorationRole 要呈现为图标形式的装饰的数据。 (QColor、QIcon 或 QPixmap)
    //Qt::EditRole 适合在编辑器中编辑的形式中的数据。 (QString)
    //Qt::ToolTipRole 显示在项目工具提示中的数据。 (QString)
    //Qt::StatusTipRole 状态栏中显示的数据。 (QString)
    //Qt::WhatsThisRole 为“这是什么?”中的项目显示的数据 模式。 (QString)
    //Qt::SizeHintRole 将提供给视图的项目的大小提示。 (Q尺寸)


    if (role != Qt::DisplayRole)
        return QVariant();

    if (orientation == Qt::Horizontal)
        return QString("201%1").arg(section);
    else
        return QString("%1").arg(section + 1);
}

//QModelIndex 此类用作从 QAbstractItemModel 派生的项目模型的索引。
//项目视图、委托和选择模型使用索引来定位模型中的项目。
//模型使用 QAbstractItemModel::createIndex() 函数创建新的 QModelIndex 对象。
//可以使用 QModelIndex 构造函数构造无效的模型索引。
//在引用模型中的顶级项目时,无效索引通常用作父索引。
//模型索引是指模型中的项目
//,并包含指定它们在这些模型中的位置所需的所有信息。
//每个索引位于给定的行和列中,并且可能有一个父索引;
//使用 row()、column() 和 parent() 获取此信息。
//模型中的每个顶级项目都由一个没有父索引的模型索引表示 - 在这种情况下,
//parent() 将返回一个无效的模型索引,
//相当于使用 QModelIndex( 的零参数形式构造的索引) ) 构造函数。
//模型索引引用模型中的项,并包含指定它们在这些模型中的位置所需的所有信息。
//每个索引位于给定的行和列中,并且可能有一个父索引;
//使用 row()、column() 和 parent() 获取此信息。
//模型中的每个顶级项目都由一个没有父索引的模型索引表示 - 在这种情况下
//parent() 将返回一个无效的模型索引,
//相当于使用 QModelIndex( 的零参数形式构造的索引) ) 构造函数。
//要获取引用模型中现有项目的模型索引,
//请使用所需的行和列值以及父项的模型索引调用 QAbstractItemModel::index()。
//当引用模型中的顶级项目时,提供 QModelIndex() 作为父索引
//model() 函数返回索引作为 QAbstractItemModel 引用的模型。
//child() 函数用于检查模型中索引下的项目。
//兄弟()函数允许您在与索引相同的级别上遍历模型中的项目。
//注意:模型索引应立即使用,然后丢弃。
//在调用更改模型结构或删除项目的模型函数后,您不应依赖索引来保持有效。
//如果您需要随着时间的推移保留模型索引,请使用 QPersistentModelIndex。

//QAbstractItemModel 类定义了项目模型必须使用的标准接口,
//以便能够与模型/视图架构中的其他组件进行互操作。
//它不应该被直接实例化。
//相反,您应该将其子类化以创建新模型。
//QAbstractItemModel 类是模型/视图类之一,
//是 Qt 模型/视图框架的一部分。
//它可以用作 QML 中的项目视图元素或 Qt Widgets 模块中的项目视图类的底层数据模型。

//如果您需要一个模型与项目视图一起使用,例
//如 QML 的列表视图元素或 C++ 小部件 QListView 或 QTableView,
//您应该考虑子类化 QAbstractListModel 或 QAbstractTableModel 而不是此类。
//底层数据模型作为表的层次结构向视图和委托公开。
//如果您不使用层次结构,那么模型就是一个简单的行和列表。
//每个项目都有一个由 QModelIndex 指定的唯一索引。
//可以通过模型访问的每个数据项都有一个关联的模型索引。
//您可以使用 index() 函数获取此模型索引。
//每个索引可能有一个sibling() 索引; 子项有一个 parent() 索引。
//每个项目都有许多与之关联的数据元素,
//可以通过为模型的 data() 函数指定一个角色(参见 Qt::ItemDataRole)来检索它们。
//可以使用 itemData() 函数同时获取所有可用角色的数据。
//每个角色的数据都是使用特定的 Qt::ItemDataRole 设置的。
//单个角色的数据使用 setData() 单独设置,
//或者可以使用 setItemData() 为所有角色设置。
//可以使用 flags() 查询项目(参见 Qt::ItemFlag),
//以查看它们是否可以被选择、拖动或以其他方式操作。
//如果一个项目有子对象,hasChildren() 为相应的索引返回 true。
//该模型对于层次结构的每个级别都有一个 rowCount() 和一个 columnCount()。
//可以使用 insertRows()、insertColumns()、removeRows() 和 removeColumns() 插入和删除行和列。
//该模型发出信号以指示变化。
//例如,只要模型提供的数据项发生更改,就会发出 dataChanged()。
//对模型提供的标头的更改会导致发出 headerDataChanged()。
//如果底层数据的结构发生变化,模型可以发出 layoutChanged() 来指示任何附加的视图,它们应该重新显示任何显示的项目,并考虑到新结构。
//可以使用 match() 函数在模型中可用的项目中搜索特定数据。
//要对模型进行排序,您可以使用 sort()。
//子类化
//注意:模型子类化参考中提供了一些子类化模型的一般指南。
//在继承 QAbstractItemModel 时,至少必须实现 index()、
//parent()、rowCount()、columnCount() 和 data()。 这些函数用于所有只读模型,并构成可编辑模型的基础。
//您还可以重新实现 hasChildren() 以为 rowCount() 的实现成本高昂的模型提供特殊行为。
//这使得模型可以限制视图请求的数据量,并且可以用作实现模型数据的惰性填充的一种方式。
//要在模型中启用编辑,您还必须实现 setData() 并重新实现 flags() 以确保返回 ItemIsEditable。
//您还可以重新实现 headerData() 和 setHeaderData() 来控制模型标题的显示方式。
//在重新实现 setData() 和 setHeaderData() 函数时
//必须分别显式地发出 dataChanged() 和 headerDataChanged() 信号。
//自定义模型需要创建模型索引供其他组件使用。
//为此,请使用该项目的合适行号和列号以及它的标识符(作为指针或整数值)调用 createIndex()。
//这些值的组合对于每个项目必须是唯一的。
//自定义模型通常在其他重新实现的函数中使用这些唯一
//标识符来检索项目数据并访问有关项目父项和子项的信息。
//有关唯一标识符的更多信息,请参阅简单树模型示例。
//没有必要支持 Qt::ItemDataRole 中定义的每个角色。
//根据模型中包含的数据类型,可能只有实现 data() 函数才能为一些更常见的角色返回有效信息才有用。
//大多数模型至少为 Qt::DisplayRole 提供项目数据的文本表示
//行为良好的模型还应为 Qt::ToolTipRole 和 Qt::WhatsThisRole 提供有效信息。
//支持这些角色使模型能够与标准 Qt 视图一起使用。
//但是,对于一些处理高度专业化数据的模型,只为用户定义的角色提供数据可能是合适的。
//为可调整大小的数据结构提供接口的模型可以提供
//insertRows()、removeRows()、insertColumns() 和 removeColumns() 的实现。
//在实现这些功能时,重要的是在模型尺寸发生变化之前和之后通知任何连接的视图:
//insertRows() 实现必须在将新行插入数据结构之前调用 beginInsertRows(),然后立即调用 endInsertRows()。
//insertColumns() 实现必须在将新列插入数据结构之前调用 beginInsertColumns(),然后立即调用 endInsertColumns()。
//removeRows() 实现必须在从数据结构中删除行之前调用 beginRemoveRows(),然后立即调用 endRemoveRows()。
//removeColumns() 实现必须在从数据结构中删除列之前调用 beginRemoveColumns(),然后立即调用 endRemoveColumns()。
//这些函数发出的私有信号让附加组件有机会在任何数据变得不可用之前采取行动。
//插入和删除操作与这些开始和结束函数的封装还使模型能够正确管理持久模型索引。
//如果您希望正确处理选择,则必须确保调用这些函数。
//如果您插入或删除带有子项的项,则不需要为子项调用这些函数。
//换句话说,父项将处理其子项。
//要创建增量填充的模型,您可以重新实现 fetchMore() 和 canFetchMore()。
//如果重新实现 fetchMore() 向模型添加行,则必须调用 beginInsertRows() 和 endInsertRows()。

//模型/视图编程简介
//Qt 包含一组项目视图类,
//,这些类使用模型/视图架构来管理数据之间的关系以及数据呈现给用户的方式。
//此体系结构引入的功能分离为开发人员提供了更大的灵活性来自定义项目的表示
//并提供标准模型接口以允许将广泛的数据源与现有的项目视图一起使用。
//在本文档中,我们简要介绍了模型/视图范式
//概述了所涉及的概念,并描述了项目视图系统的架构。
//解释了体系结构中的每个组件,并给出了显示如何使用提供的类的示例。

//模型/视图编程简介
//Qt 包含一组项目视图类,
//这些类使用模型/视图架构来管理数据之间的关系以及数据呈现给用户的方式。
//此体系结构引入的功能分离为开发人员提供了更大的灵活性来自定义项目的表示,
//并提供标准模型接口以允许将广泛的数据源与现有的项目视图一起使用。
//在本文档中,我们简要介绍了模型/视图范式,概述了所涉及的概念
//并描述了项目视图系统的架构。
//解释了体系结构中的每个组件,并给出了显示如何使用提供的类的示例。
//模型/视图架构
//模型-视图-控制器 (MVC) 是一种源自 Smalltalk 的设计模式,通常在构建用户界面时使用。
//在设计模式中,Gamma 等人。写:
//MVC 由三种对象组成。
//Model 是应用程序对象
//View 是它的屏幕展示
//Controller 定义了用户界面对用户输入的反应方式。
//在 MVC 之前,用户界面设计倾向于将这些对象混为一谈。
//MVC 将它们解耦以增加灵活性和重用性。
//如果视图和控制器对象组合在一起,
//结果就是模型/视图架构。
//这仍然将数据的存储方式与向用户呈现的方式分开
//但提供了一个基于相同原则的更简单的框架
//这种分离使得可以在几个不同的视图中显示相同的数据
//并实现新的视图类型,而无需更改底层数据结构。
//为了灵活处理用户输入,我们引入了委托的概念。
//在此框架中使用委托的优势在于,它允许自定义呈现和编辑数据项的方式。
//模型/视图架构模型与数据源通信,为架构中的其他组件提供接口。通
//信的性质取决于数据源的类型和模型的实现方式。
//视图从模型中获取模型索引;
//这些是对数据项的引用。通过向模型提供模型索引,视图可以从数据源检索数据项。
//在标准视图中,委托呈现数据项。编辑项目时,委托使用模型索引直接与模型通信。
//通常,模型/视图类可以分为上述三组:
//模型、视图和委托
//这些组件中的每一个都由抽象类定义,
//这些抽象类提供公共接口,在某些情况下,还提供功能的默认实现。
//。抽象类旨在被子类化,以提供其他组件所期望的全套功能;
//这也允许编写专门的组件。
//模型、视图和委托使用信号和槽相互通信:
//来自模型的信号通知视图有关数据源保存的数据的更改。
//来自视图的信号提供有关用户与正在显示的项目的交互的信息。
//来自委托的信号在编辑期间用于告诉模型和视图有关编辑器的状态。
//Models
//所有项目模型都基于 QAbstractItemModel 类
//这个类定义了一个接口,视图和委托使用它来访问数据。
//数据本身不必存储在模型中;
//它可以保存在由单独的类、文件、数据库或某些其他应用程序组件提供的数据结构或存储库中。
//模型类的部分介绍了围绕模型的基本概念。
//QAbstractItemModel 提供了一个数据接口
//该接口足够灵活
//可以处理以表格、列表和树的形式表示数据的视图。
//但是,在为列表和类似表的数据结构实现新模型时,
//QAbstractListModel 和 QAbstractTableModel 类是更好的起点,
//因为它们提供了常用函数的适当默认实现。
//这些类中的每一个都可以被子类化以提供支持特殊类型列表和表格的模型。
//创建新模型部分讨论了子类化模型的过程。
//Qt 提供了一些现成的模型,可用于处理数据项:
//QStringListModel 用于存储 QString 项的简单列表。
//QStandardItemModel 管理更复杂的项目树结构,每个项目都可以包含任意数据。
//QFileSystemModel 提供有关本地文件系统中文件和目录的信息
//QSqlQueryModel、QSqlTableModel 和 QSqlRelationalTableModel 用于使用模型/视图约定访问数据库。
//如果这些标准模型不符合您的要求,
//您可以将 QAbstractItemModel、QAbstractListModel 或 QAbstractTableModel 
//子类化以创建您自己的自定义模型。
//Views
//为不同类型的视图提供了完整的实现:
//QListView 显示项目列表
//QTableView 在表格中显示模型中的数据
//QTreeView 在分层列表中显示模型数据项。
//这些类中的每一个都基于 QAbstractItemView 抽象基类
//虽然这些类是现成的实现,但它们也可以被子类化以提供自定义视图。
//可用视图在有关视图类的部分中进行了检查。
//Delegates
//QAbstractItemDelegate 是模型/视图框架中委托的抽象基类。
//默认委托实现由 QStyledItemDelegate 提供
//它被 Qt 的标准视图用作默认委托。
//但是,QStyledItemDelegate 和 QItemDelegate 是为视图中的项目绘制和提供编辑器的独立替代方案。
//它们之间的区别在于 QStyledItemDelegate 使用当前样式来绘制其项目。
//因此,我们建议在实现自定义委托或使用 Qt 样式表时使用 QStyledItemDelegate 作为基类。
//Sorting
//在模型/视图架构中有两种处理排序的方法; 选择哪种方法取决于您的基础模型。
//如果您的模型是可排序的
//,即,如果它重新实现了 QAbstractItemModel::sort() 函数
//则 QTableView 和 QTreeView 都提供了一个 API,允许您以编程方式对模型数据进行排序。
//此外,您可以启用交互式排序(即允许用户通过单击视图的标题对数据进行排序)
//通过将 QHeaderView::sortIndicatorChanged() 信号连接到 
//QTableView::sortByColumn() 槽或 QTreeView::sortByColumn() 插槽,分别。
//另一种方法是,如果您的模型没有所需的接口,或者您想使用列表视图来呈现数据, 
//那么在将数据呈现在视图中之前,使用代理模型来转换模型的结构。 代理模型部分详细介绍了这一点。
//Convenience classes
//许多方便的类是从标准视图类派生出来的,
//以便于依赖 Qt 的基于项目的项目视图和表格类的应用程序。
//它们不打算被子类化。
//此类类的示例包括 QListWidget、QTreeWidget 和 QTableWidget。
//这些类不如视图类灵活,并且不能与任意模型一起使用。
//我们建议您使用模型/视图方法来处理项目视图中的数据,除非您强烈需要基于项目的类集。
//如果您希望在仍然使用基于项目的界面的同时利用模型/视图方法提供的功能,
//请考虑使用视图类,例如 QListView、QTableView 和带有 QStandardItemModel 的 QTreeView。
//Using Models and Views
//以下部分解释了如何在 Qt 中使用模型/视图模式。 每个部分都包含一个示例,然后是一个显示如何创建新组件的部分。
//Two models included in Qt
//Qt 提供的两个标准模型是 QStandardItemModel 和 QFileSystemModel
//QStandardItemModel 是一个多用途模型,可用于表示列表、表和树视图所需的各种不同数据结构。
//该模型还包含数据项。
//QFileSystemModel 是一个模型,用于维护有关目录内容的信息。
//因此,它本身不保存任何数据项,而只是表示本地文件系统上的文件和目录
//QFileSystemModel 提供了一个随时可用的模型来进行试验
//并且可以轻松地配置为使用现有数据。
//使用这个模型,我们可以展示如何建立一个模型来与现成的视图一起使用,并探索如何使用模型索引来操作数据。
//Using views with an existing model
//QListView 和 QTreeView 类是最适合与 QFileSystemModel 一起使用的视图
//下面给出的示例在列表视图中的相同信息旁边显示树视图中目录的内容。
//视图共享用户的选择,以便在两个视图中突出显示所选项目。
//我们设置了一个 QFileSystemModel 以便它可以使用,并创建一些视图来显示目录的内容。
//这显示了使用模型的最简单方法。
//模型的构建和使用是从单个 main() 函数中执行的:
/*
    int main(int argc,char *argv[])
    {
        QApplication app(argc,argv);
        QSplitter *splitter = new  QSplitter;

        QFileSystemModel *model = new QFileSytemModel;
        model->setRootPath(QDir::currentPath());
*/
//该模型被设置为使用来自某个文件系统的数据。
//对 setRootPath() 的调用告诉模型文件系统上的哪个驱动器向视图公开。
//我们创建了两个视图,以便我们可以通过两种不同的方式检查模型中保存的项目:
/*
    QTreeView *tree = new QTreeView(splitter);
    tree->setModel(model);
    tree->setRootIndex(model->index(QDir::currentPath()));

    QListView *list = new QListView(splitter);
    list->setModel(model);
    list->setRootIndex(model->index(QDir::currentPath()));
*/
//视图的构建方式与其他小部件相同。
//设置一个视图来显示模型中的项目,只需调用它的 setModel() 函数,并将目录模型作为参数。
//我们通过在每个视图上调用 setRootIndex() 函数来过滤模型提供的数据,
//从当前目录的文件系统模型中传递一个合适的模型索引。
//本例中使用的 index() 函数是 QFileSystemModel 独有的;
//我们为它提供一个目录,它返回一个模型索引。
//模型索引在模型类中讨论。本例中使用的 index() 函数是 QFileSystemModel 独有的;
//我们为它提供一个目录,它返回一个模型索引。 模型索引在模型类中讨论。
//该函数的其余部分仅显示拆分器小部件中的视图,并运行应用程序的事件循环:
/*
    splitter->setWindowTitle("Two views onto the same file system model");
    splitter->show();
    return app.exec();
}
*/
//拆分器允许用户通过拖动它们之间的边界来控制子部件的大小。
//任何数量的小部件都可以由单个拆分器控制。
//QSplitter 的典型用途是创建多个小部件并使用 insertWidget() 或 addWidget() 添加它们。
//以下示例将并排显示 QListView、QTreeView 和 QTextEdit,并带有两个分隔符句柄:
/*
    QSplitter *splitter = new QSplitter(parent);
    QListView *listview = new QListView;
    QTreeView *treeview = new QTreeView;
    QTextEdit *textedit = new QTextEdit;
    splitter->addWidget(listview);
    splitter->addWidget(treeview);
    splitter->addWidget(textedit);
*/

//如果在调用 insertWidget() 或 addWidget() 时小部件已经在 QSplitter 中,它将移动到新位置。
//这可用于稍后在拆分器中重新排序小部件。
//您可以使用 indexOf()、widget() 和 count() 来访问拆分器内的小部件。
//默认的 QSplitter 水平(并排)布置其子项;
//您可以使用 setOrientation(Qt::Vertical) 将其子项垂直放置。
//默认情况下,所有小部件都可以根据用户的意愿大或小
//介于小部件的 minimumSizeHint()(或 minimumSize())和 maximumSize() 之间。
//默认情况下,QSplitter 会动态调整其子项的大小。
//如果您希望 QSplitter 仅在调整大小操作结束时调整子项的大小,请调用 setOpaqueResize(false)。
//小部件之间大小的初始分布是通过将初始大小乘以拉伸因子来确定的。
//您还可以使用 setSizes() 来设置所有小部件的大小。 函数 size() 返回用户设置的大小。
//或者,您可以分别使用 saveState() 和 restoreState() 从 QByteArray 保存和恢复小部件的大小。
//当你 hide() 一个孩子时,它的空间将分配给其他孩子。 当您再次 show() 它时,它将被恢复。
//Model Classes
//在检查如何处理选择之前
//您可能会发现检查模型/视图框架中使用的概念很有用。
//Basic concepts
//在模型/视图架构中,模型提供了视图和委托用来访问数据的标准接口。
//在 Qt 中,标准接口由 QAbstractItemModel 类定义。
//无论数据项如何存储在任何底层数据结构中,
//QAbstractItemModel 的所有子类都将数据表示为包含项表的分层结构。
//视图使用此约定来访问模型中的数据项,
//但它们在向用户呈现此信息的方式方面不受限制。
//模型还通过信号和插槽机制通知任何附加的视图有关数据更改的信息。
//本节描述了一些基本概念,这些概念对于其他组件通过模型类访问数据项的方式至关重要。
//更高级的概念将在后面的部分中讨论。
//Model indexes
//为确保数据的表示与其访问方式保持分离,引入了模型索引的概念。
//可以通过模型获得的每条信息都由模型索引表示。
//视图和委托使用这些索引来请求要显示的数据项。
//因此,只有模型需要知道如何获取数据,模型管理的数据类型可以相当笼统地定义。
//模型索引包含一个指向创建它们的模型的指针,这可以防止在处理多个模型时出现混淆。
//QAbstractItemModel *model = index.model();
//模型索引提供对信息片段的临时引用,可用于通过模型检索或修改数据。
//由于模型可能会不时重新组织其内部结构,因此模型索引可能会失效,不应存储。
//如果需要对一条信息进行长期引用,则必须创建一个持久模型索引。
//这提供了对模型保持最新的信息的参考。
//临时模型索引由 QModelIndex 类提供,持久模型索引由 QPersistentModelIndex 类提供。
//要获得与数据项对应的模型索引,
//必须为模型指定三个属性:行号、列号和父项的模型索引。
//以下部分详细描述和解释了这些属性。
//Rows and columns
//在最基本的形式中,模型可以作为一个简单的表来访问,其中的项目按其行号和列号定位。
//这并不意味着底层数据存储在数组结构中;
//使用行号和列号只是允许组件相互通信的约定。
//我们可以通过向模型指定其行号和列号来检索有关任何给定项目的信息
//并且我们会收到一个表示该项目的索引:
//QModeIndex index = model->index(row,column,...);
//为简单的单级数据结构(如列表和表格)提供接口的模型不需要提供任何其他信息,
//但正如上面的代码所示,我们需要在获取模型索引时提供更多信息。
//行和列该图显示了基本表模型的表示
//其中每个项目都由一对行号和列号定位。
//我们通过将相关的行号和列号传递给模型来获得引用数据项的模型索引。
/*
    QModelIndex indexA = model->index(0,0,QModelIndex());
    QModelIndex indexB = model->index(1,1,QModelIndex());
    QModelIndex indexC = model->index(2,1,QModelIndex());
*/
//模型中的顶级项目始终通过将 QModelIndex() 指定为其父项目来引用。 这将在下一节中讨论。
//Parents of items
//当在表格或列表视图中使用数据时,
//模型提供的类似表格的项目数据界面是理想的;
//行号和列号系统完全映射到视图显示项目的方式。
//但是,树视图等结构要求模型向其中的项目公开更灵活的接口。
//因此,每个项目也可以是另一个项目表的父项,
//就像树视图中的顶级项目可以包含另一个项目列表一样。
//当请求模型项的索引时,我们必须提供有关该项父项的一些信息
//在模型之外,引用项的唯一方法是通过模型索引,因此还必须给出父模型索引:
//QModelIndex index = model->index(row,column,parent);
//父项、行和列 该图显示了树模型的表示
//其中每个项都由父项、行号和列号引用。
//项目“A”和“C”表示为模型中的顶级兄弟:
/*
    QModelIndex indexA = model->index(0,0,QModelIndex());
    QModelIndex indexB = model->index(2,1,QModelIndex());
*/
//项目“A”有许多子项。 使用以下代码获得项目“B”的模型索引:
//QModelIndex indexB = model->index(1,0,indexA);

//Item roles
//模型中的项目可以为其他组件扮演各种角色,
//允许为不同情况提供不同类型的数据。
//例如,Qt::DisplayRole 用于访问可以在视图中显示为文本的字符串。
//通常,项目包含许多不同角色的数据,标准角色由 Qt::ItemDataRole 定义。
//QVaraint value = model->data(index,role);
//项目角色角色向模型指示正在引用哪种类型的数据。
//视图可以以不同的方式显示角色,因此为每个角色提供适当的信息很重要。
//创建新模型部分更详细地介绍了角色的一些特定用途。
//Qt::ItemDataRole 中定义的标准角色涵盖了项目数据的最常见用途。
//通过为每个角色提供适当的项目数据,模型可以向视图和委托提供关于项目应该如何呈现给用户的提示。
//不同类型的视图可以根据需要自由解释或忽略此信息。
//还可以为特定于应用程序的目的定义其他角色。
//Summary
//模型索引以独立于任何底层数据结构的方式为视图和委托提供有关模型提供的项目位置的信息。
//项通过其行号和列号以及其父项的模型索引来引用。
//模型索引是由模型在其他组件(例如视图和委托)的请求下构建的。
//如果在使用 index() 请求索引时为父项指定了有效的模型索引,则返回的索引引用模型中该父项下的项。
//获得的索引是指该项目的子项。
//如果在使用 index() 请求索引时为父项指定了无效的模型索引,则返回的索引引用模型中的顶级项。
//该角色区分与项目关联的不同类型的数据。
//使用模型索引
//为了演示如何使用模型索引从模型中检索数据,我们设置了一个没有视图的 QFileSystemModel,
//并在小部件中显示文件和目录的名称。
//虽然这并没有显示使用模型的正常方式,但它演示了模型在处理模型索引时使用的约定
//QFileSystemModel 加载是异步的,以最大限度地减少系统资源的使用。
//我们在处理这个模型时必须考虑到这一点。
//我们通过以下方式构建文件系统模型:
/*
    QFileSystemModel *model = new QFileSystemModel;
    connect(model,&QFileSystemModel::directoryLoaded,[model](const QString& directory){
        QModelIndex parentIndex = model->index(directory);
        int numRows = model->rowCount(parentIndex);
    });
    model->setRootPath(QDir::currentPath);
*/
//在这种情况下,我们首先设置一个默认的 QFileSystemModel。
//我们将它连接到一个 lambda,在其中我们将使用该模型提供的 index() 的特定实现来获取父索引。
//在 lambda 中,我们使用 rowCount() 函数计算模型中的行数。
//最后,我们设置 QFileSystemModel 的根路径,以便它开始加载数据并触发 lambda。

//为简单起见,我们只对模型第一列中的项目感兴趣。
//我们依次检查每一行,获取每一行中第一项的模型索引,并读取模型中为该项存储的数据。
/*
    for(int row = 0; row < numRows; ++row)
    {
        QModelIndex index = model->index(row,0,parentIndex);
    //为了获得模型索引,我们指定行号、列号(第一列为零)以及我们想要的所有项目的父项的适当模型索引。
    //使用模型的 data() 函数检索存储在每个项目中的文本。
    //我们指定模型索引和 DisplayRole 以获取字符串形式的项目数据。
        QString text = model->data(index, Qt::DisplayRole).toString();
        //Display the text in a widget
    }
    //上面的示例演示了用于从模型中检索数据的基本原则:
    //可以使用 rowCount() 和 columnCount() 找到模型的维度。 
    //这些函数通常需要指定父模型索引。
    //模型索引用于访问模型中的项目。
    //需要行、列和父模型索引来指定项目。
    //要访问模型中的顶级项目,请使用 QModelIndex() 指定一个空模型索引作为父索引。
    //项目包含不同角色的数据。 要获取特定角色的数据,模型索引和角色都必须提供给模型。
*/
//Further reading
//可以通过实现 QAbstractItemModel 提供的标准接口来创建新模型。
//在创建新模型部分,我们通过创建一个方便的、随时可用的模型来保存字符串列表来演示这一点。

//View Classes
//Concepts
在模型/视图架构中,视图从模型中获取数据项并将它们呈现给用户。
//数据的呈现方式不需要类似于模型提供的数据表示
//并且可能与用于存储数据项的底层数据结构完全不同。
//内容和表示的分离是通过使用 QAbstractItemModel 提供的标准模型接口、
//QAbstractItemView 提供的标准视图接口以及使用以通用方式表示数据项的模型索引来实现的
//视图通常管理从模型获得的数据的整体布局。
//他们可以自己渲染单个数据项,或者使用委托来处理渲染和编辑功能。
//除了呈现数据,视图还处理项目之间的导航以及项目选择的某些方面。
//这些视图还实现了基本的用户界面功能,例如上下文菜单和拖放。
//视图可以为项目提供默认的编辑工具,或者它可以与委托一起提供自定义编辑器。
//可以在没有模型的情况下构建视图,但必须提供模型才能显示有用的信息。
//视图通过使用可以为每个视图单独维护或在多个视图之间共享的选择来跟踪用户选择的项目。
//一些视图,例如 QTableView 和 QTreeView,显示标题和项目。
//这些也是由视图类 QHeaderView 实现的。 标头通常访问与包含它们的视图相同的模型。
//它们使用 QAbstractItemModel::headerData() 函数从模型中检索数据,并且通常以标签的形式显示标题信息。
//可以从 QHeaderView 类继承新的标题,为视图提供更专业的标签。

//Using an existing view
//Qt 提供了三个随时可用的视图类,它们以大多数用户熟悉的方式呈现来自模型的数据。
//QListView 可以将模型中的项目显示为简单列表,或以经典图标视图的形式显示。
//QTreeView 将模型中的项目显示为列表的层次结构,允许以紧凑的方式表示深度嵌套的结构。
//QTableView 以表格的形式呈现模型中的项目,很像电子表格应用程序的布局。
//上面显示的标准视图的默认行为对于大多数应用程序来说应该足够了。
//它们提供基本的编辑功能,并且可以进行定制以满足更专业的用户界面的需要。
//Using a model
//我们以我们创建的字符串列表模型作为示例模型,用一些数据设置它,
//并构造一个视图来显示模型的内容。 这一切都可以在单个函数中执行:
/*
    int main(int argc,char *argv[])
    {
        QApplication app(argc,argv);
        //Unindented for quoting purposes:
        QStringList numbers;
        numbers<<"One"<<"Two"<<"Three"<<"Four"<<"Five";
        QAbstractItemModel *model = new StringListModel(numbers);
//请注意,StringListModel 被声明为 QAbstractItemModel。
//这允许我们使用模型的抽象接口,
//并确保代码仍然有效,即使我们用不同的模型替换字符串列表模型。
//QListView 提供的列表视图足以显示字符串列表模型中的项目。
//我们构造视图,并使用以下代码行设置模型:
        QListView *view = new QListView;
        view->setModel(model);
//视图以正常方式显示:
        view->show;
        return app.exec();
    }
*/
//视图呈现模型的内容,通过模型的接口访问数据。
//当用户尝试编辑项目时,视图使用默认委托来提供编辑器小部件。
//上图显示了 QListView 如何表示字符串列表模型中的数据。
//由于模型是可编辑的,视图自动允许使用默认委托编辑列表中的每个项目。
//使用模型的多个视图
//在同一个模型上提供多个视图只是为每个视图设置相同的模型。
//在下面的代码中,我们创建了两个表视图,每个视图都使用我们为此示例创建的相同的简单表模型:
/*
    QTableView *firstTableView = new QTableView;
    QTableView *secondTableView = new QTableView;

    firstTableView->setModel(model);
    secondTableView->setModel(model);
*/
//在模型/视图架构中使用信号和槽意味着对模型的更改可以传播到所有附加的视图,
//确保我们始终可以访问相同的数据,而不管使用的视图如何。
//上图显示了同一个模型的两个不同视图,
//每个视图都包含许多选定的项目。
//尽管模型中的数据在整个视图中显示一致,但每个视图都维护自己的内部选择模型。
//这在某些情况下很有用,但对于许多应用程序,共享选择模型是可取的。
//Handling selections of items
//处理视图中项目选择的机制由 QItemSelectionModel 类提供。
//默认情况下,所有标准视图都会构建自己的选择模型
//并以正常方式与它们交互。
//视图使用的选择模型可以通过 selectionModel() 函数获得
//替换选择模型可以通过 setSelectionModel() 指定。
//当我们希望在同一模型数据上提供多个一致的视图时,控制视图使用的选择模型的能力非常有用。
//通常,除非您对模型或视图进行子类化,否则您不需要直接操作选择的内容。
//但是,如果需要,可以访问选择模型的界面,这在处理项目视图中的选择中进行了探讨。
//Sharing selections among views
//尽管默认情况下视图类提供自己的选择模型很方便
//但是当我们在同一个模型上使用多个视图时
//通常希望模型的数据和用户的选择在所有视图中一致显示。
//由于视图类允许替换其内部选择模型,因此我们可以使用以下行实现视图之间的统一选择:
//secondTableView->setSelectionModel(firstTableview->selectionModel());

//第二个视图给出了第一个视图的选择模型。
//两个视图现在在相同的选择模型上运行,保持数据和所选项目同步。
//在上面显示的示例中,使用相同类型的两个视图来显示相同模型的数据。
//但是,如果使用两种不同类型的视图,则所选项目在每个视图中的表示方式可能非常不同;
//例如,表视图中的连续选择可以表示为树视图中突出显示的一组碎片化项目。

//Delegate Classes
//Concepts
//与模型-视图-控制器模式不同,模型/视图设计不包括用于管理与用户交互的完全独立的组件。
//通常,视图负责将模型数据呈现给用户,以及处理用户输入
//为了在获取此输入的方式上提供一些灵活性,交互由委托执行。
//这些组件提供输入功能,还负责渲染某些视图中的单个项目。
//控制委托的标准接口在 QAbstractItemDelegate 类中定义。
//委托人应该能够通过实现paint() 和sizeHint() 函数来自己呈现他们的内容。
//然而,简单的基于小部件的委托可以子类化 QStyledItemDelegate 而不是 QAbstractItemDelegate
//并利用这些函数的默认实现。
//代理的编辑器可以通过使用小部件来管理编辑过程或直接处理事件来实现。
//本节稍后将介绍第一种方法,它也在 Spin Box Delegate 示例中显示。
//Pixelator 示例展示了如何创建一个自定义委托来为表格视图执行专门的渲染。

//Using an existing delegate
//Qt 提供的标准视图使用 QStyledItemDelegate 的实例来提供编辑功能。
//委托接口的这个默认实现为每个标准视图以通常的样式呈现项目:
//QListView、QTableView 和 QTreeView。
//所有标准角色都由标准视图使用的默认委托处理。
//QStyledItemDelegate 文档中描述了这些解释的方式。
//视图使用的委托由 itemDelegate() 函数返回。
//setItemDelegate() 函数允许您为标准视图安装自定义委托,
//并且在为自定义视图设置委托时需要使用此函数。

//A simple delegate
//此处实现的委托使用 QSpinBox 来提供编辑功能,主要用于显示整数的模型。
//尽管我们为此目的设置了一个自定义的基于整数的表模型
//但我们可以轻松地使用 QStandardItemModel 代替
//因为自定义委托控制数据输入
//我们构造一个表视图来显示模型的内容,这将使用自定义委托进行编辑。
//我们从 QStyledItemDelegate 子类化委托,因为我们不想编写自定义显示函数。
// 但是,我们仍然必须提供管理编辑器小部件的功能:
/*
    class SpinBoxDelegate : public QStyledItemDelegate
    {
        Q_OBJECT
    public:
        SpinBoxDelegate(QObject *parent = nullptr);
        QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option,
                              const QModelIndex &index) const override;
        void setEditorData(QWidget *editor, const QModelIndex &index) const override;
        void setModelData(QWidget *editor, QAbstractItemModel *model,
                          const QModelIndex &index) const override;
        void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,
                                  const QModelIndex &index) const override;
    }
*/
//请注意,构造委托时没有设置编辑器小部件。 我们只在需要时构建一个编辑器小部件。
//Providing an editor
//在这个例子中,当 table view 需要提供一个编辑器时
//它会要求委托提供一个适合被修改项目的编辑器小部件。
//createEditor() 函数提供了委托能够设置合适的小部件所需的一切:
/*
    QWidget *SpinBoxDelegate::createEditor(QWidget *parent,
                                           const QStyleOptionViewItem &option,
                                           const QModelIndex &index) const
    {
        QSpinBox *editor = new QSpinBox(parent);
        editor->setFrame(false);
        editor->setMinimum(0);
        editor->setMaximum(100);

        return editor;
    }
    //请注意,我们不需要保留指向编辑器小部件的指针,因为视图负责在不再需要时销毁它。
    //我们在编辑器上安装委托的默认事件过滤器,
    //以确保它提供用户期望的标准编辑快捷方式。
    //可以向编辑器添加其他快捷方式以允许更复杂的行为;
    //这些将在编辑提示部分讨论。
    //该视图通过调用我们稍后为这些目的定义的函数来确保正确设置编辑器的数据和几何。
    //我们可以根据视图提供的模型索引创建不同的编辑器。
    //例如,如果我们有一列整数和一列字符串,我们可以根据正在编辑的列返回 QSpinBox 或 QLineEdit。
    //委托必须提供一个函数来将模型数据复制到编辑器中。
    //在这个例子中,我们读取了显示角色中存储的数据,并相应地设置了旋转框中的值。
    /*
        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);
        }
    */
//在这个例子中,我们知道编辑器小部件是一个旋转框
//但我们可以为模型中的不同类型的数据提供不同的编辑器,
//在这种情况下,我们需要在访问其成员函数之前将小部件转换为适当的类型 .
//Submitting data to the model
//当用户完成编辑旋转框中的值时
//视图要求委托通过调用 setModelData() 函数将编辑后的值存储在模型中。
/*
    void SpinBoxDelegate::setModelData(QWidget *editor,QAbstractItemModel *model,
                                       const QModelIndex &index) const
    {
        QSpinBox *spinBox = static_cast(editor);
        spinBox->interpreText();
        int value = spinBox->value();

        model->setData(index,value,Qt::EditRole);
    }
*
//由于视图管理委托的编辑器小部件
//我们只需要使用提供的编辑器的内容更新模型。
//在这种情况下,我们确保旋转框是最新的,并使用指定的索引用它包含的值更新模型。
//当视图完成编辑时,标准 QStyledItemDelegate 类通过发出 closeEditor() 信号通知视图。
//该视图确保编辑器小部件已关闭并销毁。
//在这个例子中,我们只提供简单的编辑工具,所以我们永远不需要发出这个信号。
//所有对数据的操作都是通过QAbstractItemModel提供的接口完成的。
//这使得委托基本上独立于它操作的数据类型,
//但必须做出一些假设才能使用某些类型的编辑器小部件。
//在这个例子中,我们假设模型总是包含整数值,
//但我们仍然可以将此委托用于不同类型的模型,
//因为 QVariant 为意外数据提供了合理的默认值。
//更新编辑器的几何图形
//管理编辑器的几何图形是委托的责任。
//必须在创建编辑器时以及更改视图中项目的大小或位置时设置几何图形。
//幸运的是,视图在视图选项对象中提供了所有必要的几何信息。
/*
    void SpinBoxDelegate::updateEditorGeometry(QWidget *editor,
                                               const QStyleOptionViewItem &option,
                                               const QModelIndex &) const
    {
        editor->setGeometry(option.rect);
    }
*/
//在这种情况下,我们只使用项目矩形中的视图选项提供的几何信息。
//呈现具有多个元素的项目的委托不会直接使用项目矩形。
//它将相对于项目中的其他元素定位编辑器。


//Editing hints
//编辑后,代表应向其他组件提供有关编辑过程结果的提示,并提供有助于后续编辑操作的提示。
//这是通过发出带有合适提示的 closeEditor() 信号来实现的。
//这是由默认的 QStyledItemDelegate 事件过滤器处理的,我们在构造它时安装在旋转框上。
//可以调整旋转框的行为,使其更加用户友好
//在 QStyledItemDelegate 提供的默认事件过滤器中,
//如果用户点击 Return 以确认他们在旋转框中的选择
//则委托将值提交给模型并关闭旋转框。
//我们可以通过在旋转框上安装我们自己的事件过滤器来改变这种行为
//并提供适合我们需要的编辑提示
//例如,我们可能会发出 closeEditor() 和 EditNextItem 提示,以自动开始编辑视图中的下一个项目。
//另一种不需要使用事件过滤器的方法是提供我们自己的编辑器小部件
//为了方便起见,也许子类化 QSpinBox
//这种替代方法将使我们能够以编写额外代码为代价
//更好地控制编辑器小部件的行为方式。
//如果您需要自定义标准 Qt 编辑器小部件的行为,通常在委托中安装事件过滤器会更容易。
//Delegates不必发出这些提示,但那些不发出这些提示的将更少地集成到应用程序中
//并且与发出提示以支持常见编辑操作的那些相比,其可用性会更低。

//处理项目视图中的选择
//Concepts
//项目视图类中使用的选择模型提供了基于模型/视图架构设施的选择的一般描述。
//尽管用于操作选择的标准类对于提供的项目视图来说已经足够了
//但是选择模型允许您创建专门的选择模型来满足您自己的项目模型和视图的要求。
//有关在视图中选择的项目的信息存储在 QItemSelectionModel 类的实例中。
//这在单个模型中维护项目的模型索引,并且独立于任何视图。
//由于模型上可以有多个视图,因此可以在视图之间共享选择,从而允许应用程序以一致的方式显示多个视图。
//选择由选择范围组成。
//通过仅记录每个选定项目范围的开始和结束模型索引,
//它们可以有效地维护有关大量项目选择的信息。
//项目的非连续选择是通过使用多个选择范围来描述选择来构建的。
//选择应用于选择模型持有的模型索引集合。
//最近应用的项目选择称为当前选择。
//即使在通过使用某些类型的选择命令应用后,也可以修改此选择的效果。
//本节稍后将讨论这些内容。

//Current item and selected items
//在一个视图中,总是有一个当前项和一个选定项——两个独立的状态。
//一个项目可以是当前项目并同时被选中。
//视图负责确保始终存在当前项,例如键盘导航需要当前项。
//下表突出显示了当前项目和所选项目之间的差异。
//Current Item                  Selected Items
//只能有一个当前项目。            可以有多个选定的项目
//当前项目将通过按键导航或鼠标按钮点击进行更改。 项目的选定状态是设置还是取消设置,这取决于用户与项目交互时的几种预定义模式——例如单选、多选等。
//如果按下编辑键 F2 或双击该项目(前提是启用了编辑),则将编辑当前项目。 当前项目可以与锚点一起使用来指定应该选择或取消选择(或两者的组合)的范围。
//当前项目由焦点矩形指示。 当前项目由焦点矩形指示。
//在操作选择时,将 QItemSelectionModel 视为项模型中所有项的选择状态的记录通常会有所帮助。
//一旦设置了选择模型,就可以选择、取消选择项目集合,
//或者可以切换它们的选择状态,而无需知道哪些项目已被选中
//可以随时检索所有选定项的索引,并且可以通过信号和槽机制将选择模型的更改通知其他组件。

//Using a selection model
//标准视图类提供可用于大多数应用程序的默认选择模型。
//可以使用视图的 selectionModel() 函数获取属于一个视图的选择模型,
//并通过 setSelectionModel() 在多个视图之间共享
//因此通常不需要构建新的选择模型。
//选择是通过指定模型和 QItemSelection 的一对模型索引来创建的。
//这使用索引来引用给定模型中的项目,并将它们解释为选定项目块中左上角和右下角的项目。
//要将选择应用于模型中的项目,需要将选择提交给选择模型;
//这可以通过多种方式实现,每种方式对选择模型中已有的选择都有不同的影响。

//Selecting items
//为了演示选择的一些主要特性
//我们构建了一个总共有 32 个项目的自定义表模型的实例,并打开一个表视图来查看其数据:
/*
    TableModel *model = new TableModel(8,4,&app);

    QTableView *table = new QTableView(0);
    table->setModel(model);

    QItemSelectionModel *selectionModel = table->selectionModel();
*/
//检索表视图的默认选择模型以供以后使用。
//我们不会修改模型中的任何项目,而是选择视图将显示在表格左上角的一些项目。
//为此,我们需要检索与要选择的区域中左上角和右下角项对应的模型索引:
/*
    QModelIndex topLeft;
    QModelIndex bottomRight;

    topLeft = model->index(0,0,QModelIndex());
    bottomRight = model->index(5,2,QModelIndex());
*/
//要在模型中选择这些项目,并在表格视图中看到相应的变化,我们需要构造一个选择对象,然后将其应用到选择模型中:
/*
    QItemSelection selection(topLeft,bottomRight);
    selectionModel->select(selection,QItemSelectionModel::Select);
*/
//使用由选择标志组合定义的命令将选择应用于选择模型。
//在这种情况下,所使用的标志会导致选择对象中记录的项目被包含在选择模型中,而不管它们之前的状态如何。
//结果选择显示在视图中。
//可以使用由选择标志定义的各种操作来修改项目的选择。
//这些操作产生的选择可能具有复杂的结构,但它由选择模型有效地表示。
//当我们检查如何更新选择时,描述了使用不同的选择标志来操作所选项目。

//Reading the selection state
//可以使用 selectedIndexes() 函数读取存储在选择模型中的模型索引。
//这将返回一个未排序的模型索引列表,只要我们知道它们用于哪个模型,我们就可以对其进行迭代:
/*
    const QModelIndexList indexes = selectionIndex->selectedIndexes()
    
    for(const QModelIndex &index : indexes)
    {
        QString text = QString("(%1,%2)").arg(index.row()).arg(index.column());
        model->setData(index,text);
    }
*/

//上面的代码使用基于范围的 for 循环来迭代和修改与选择模型返回的索引对应的项目。
//选择模型发出信号来指示选择的变化。
//这些通知其他组件关于整个选择和项目模型中当前聚焦项目的更改。
//我们可以将 selectionChanged() 信号连接到一个插槽,并检查模型中在选择更改时选中或取消选中的项目。
//该槽由两个 QItemSelection 对象调用:
//一个包含与新选择的项目对应的索引列表;
//另一个包含对应于新取消选择的项目的索引。
//在下面的代码中,我们提供了一个槽,它接收 selectionChanged() 信号,用字符串填充所选项目,并清除取消选择项目的内容。
/*
    void MainWindow::updateSelection(const QItemSelection &selected,
                                     const QItemSelection &deselected)
    {
        QModelIndexList items = selected.indexes();
        for(const QModelIndex &index : qAsConst(items))
        {
            QString text = QString("%1,%2").arg(index.row()).arg(index.column());
            model->setData(index,text);
        }

        items = deselected.indexes();

        for(const QModelIndex &index : qAsConst(items))
        {
            model->setData(index,QString());
        }
    }
*/

//我们可以通过将 currentChanged() 信号连接到使用两个模型索引调用的槽来跟踪当前聚焦的项目。
//这些对应于先前关注的项目和当前关注的项目。
/*
    void MainWindow::changeCurrent(const QModelIndex ¤t,
                                   const QModelIndex &previous)
    {
        statusBar()->showMessage(tr("Moved from(%1,%2) to (%4,%5)")
                                .arg(previous.row()).arg(previous.column())
                                .arg(current.row()).arg(current.column()));
    }
*/
//对于这些信号,用户所做的监控选择很简单,
//但我们也可以直接更新选择模型。

//Updating a selection
//选择命令由选择标志的组合提供
//,由 QItemSelectionModel::SelectionFlag 定义。
//每个选择标志告诉选择模型在调用任一 select() 函数时如何更新其所选项目的内部记录。
//最常用的标志是 Select 标志,它指示选择模型将指定的项目记录为被选中。
//Toggle 标志使选择模型反转指定项目的状态,选择给定的任何取消选择的项目,
//并取消选择任何当前选定的项目。
//取消选择标志取消选择所有指定的项目。

//通过创建项目选择并将它们应用到选择模型来更新选择模型中的单个项目。
//在下面的代码中,我们将第二个项目选择应用于上面显示的表模型,
//使用 Toggle 命令反转给定项目的选择状态。
/*
    QItemSelection toggleSelection;

    topLeft = model->index(2,1,QModelIndex());
    bottomRight = model->index(7,3,QModelIndex());
    toggleSelection.select(topLeft,bottomRight);

    selectionModel->select(toggleSelection,QItemSelectionModel::Toggle);
*/

//此操作的结果显示在表格视图中,提供了一种可视化我们取得的成果的便捷方式:
//默认情况下,选择命令仅对模型索引指定的单个项目进行操作。
//但是,用于描述选择命令的标志可以与其他标志组合以更改整个行和列。
//。 例如,如果您只使用一个索引调用 select(),但使用一个由 Select 和 Rows 
//组合而成的命令,则包含所引用项目的整行都会被选中。
/*
    QItemSelection columnSelection;

    topLeft = model->index(0,1,QModelIndex());
    bottomRight = model->index(0,2,QModelIndex());

    columnSelection.selection(topLeft,bottomRight);

    selectionModel->select(columnSelection,
        QItemSelectionModel::Select | QItemSelectionModel::Columns);

    QItemSelection rowSelection;

    topLeft = model->index(0,0,QModelIndex());
    bottomRight = model->index(1,0,QModelIndex());

    rowSelection.elect(topLeft,bottomRight);

    selectionModel->selection(rowSelection,
        QItemSelectionModel::Select | QItemSelectionModel::Rows);
*/
//尽管只为选择模型提供了四个索引,但使用列和行选择标志意味着选择了两列和两行。
//下图显示了这两个选择的结果:
//在示例模型上执行的命令都涉及累积模型中的项目选择。
//也可以清除选择,或用新选择替换当前选择。
//要将当前选择替换为新选择,请将其他选择标志与当前标志组合。
//使用此标志的命令指示选择模型将其当前的模型索引集合替换为在调用 select() 中指定的索引集合。
//要在开始添加新选择之前清除所有选择,请将其他选择标志与清除标志结合使用。
//这具有重置选择模型的模型索引集合的效果。

//Selecting all items in a model
//要选择模型中的所有项目,必须为模型的每个级别创建一个选择,以涵盖该级别中的所有项目。
//我们通过检索与具有给定父索引的左上角和右下角项对应的索引来做到这一点:
/*
    QModelIndex topLeft = model->index(0,0,parent);
    QModelIndex bottomRight = model->index(model->rowCount(parent) - 1,
        model->columnCount(parent) - 1,parent);
*/

//使用这些索引和模型构建选择。 然后在选择模型中选择相应的项目:
/*
    QItemSelection selection(topLeft,bottomRight);
    selectionModel->select(selection,QItemSelectionModel::Select);
*/

//这需要对模型中的所有级别执行。 对于顶级项目,我们会以通常的方式定义父索引:
//QModelIndex parent = QModelIndex();

//对于分层模型,hasChildren() 函数用于确定任何给定项是否是另一级项的父项。

//Creating New Models
//模型/视图组件之间的功能分离允许创建可以利用现有视图的模型。
//这种方法让我们可以使用标准图形用户界面组件
//(例如 QListView、QTableView 和 QTreeView)呈现来自各种来源的数据。
//QAbstractItemModel 类提供了一个足够灵活的接口
//以支持以分层结构排列信息的数据源
//允许以某种方式插入、删除、修改或排序数据。 它还提供对拖放操作的支持。
//QAbstractListModel 和 QAbstractTableModel 类为更简单的非分层数据结构的接口提供支持
//并且更容易用作简单列表和表模型的起点。
//在本节中,我们创建一个简单的只读模型来探索模型/视图架构的基本原理。
//在本节的后面,我们调整这个简单的模型,以便用户可以修改项目。
//有关更复杂模型的示例,请参阅简单树模型示例。
//QAbstractItemModel 子类的要求在模型子类化参考文档中进行了更详细的描述。
//Designing a model
//在为现有数据结构创建新模型时,重要的是要考虑应该使用哪种类型的模型来提供数据接口。
//如果数据结构可以表示为一个列表或项目表,您可以将 QAbstractListModel 或 
//QAbstractTableModel 子类化,因为这些类为许多函数提供了合适的默认实现。
//但是,如果底层数据结构只能用层次树结构表示,
//就需要对QAbstractItemModel进行子类化。
//简单树模型示例中采用了这种方法。
//在本节中,我们实现了一个基于字符串列表的简单模型,因此 QAbstractListModel 提供了一个理想的构建基类。
//无论底层数据结构采用何种形式,在专用模型中补充标准 QAbstractItemModel API 
//通常是一个好主意,该 API 允许更自然地访问底层数据结构。
//这使得用数据填充模型变得更容易,
//但仍然允许其他通用模型/视图组件使用标准 API 与其交互。
//下面描述的模型为此目的提供了一个自定义构造函数。

//A read-only example model
//这里实现的模型是一个基于标准 QStringListModel 类的简单、非分层、只读的数据模型。
//它有一个 QStringList 作为其内部数据源,并且只实现了制作功能模型所需的内容。
//为了使实现更容易,我们将 QAbstractListModel 子类化,因为它为列表模型定义了合理的默认行为,
//并且它公开了一个比QAbstractItemModel 类更简单的接口。

/*
    class StringListModel : public QAbstractListMode
    {
        Q_OBJECT
    public:
        StringListModel(const QStringList &string,QObject *parent = nullptr)
            :QAbstractListMode(parent),stringList(strings){}
        int rowCount(const QModelIndex &parent = QModelIndex()) const override;
        QVariant data(const QModelIndex,int role) const override;
        QVariant headerData(int section, Qt::Orientation orientation,
                            int role = Qt::DisplayRole) const override;
    private:
        QStringList stringList;
    };
*/
//除了模型的构造函数,我们只需要实现两个函数:
//rowCount() 返回模型中的行数,data() 返回与指定模型索引对应的数据项。
//表现良好的模型还实现了 headerData() 来为树和表视图提供一些显示在其标题中的内容。
//请注意,这是一个非分层模型
//因此我们不必担心父子关系。 如果我们的模型是分层的
//我们还必须实现 index() 和 parent() 函数。
//字符串列表内部存储在 stringList 私有成员变量中。

//Dimensions of the model
//我们希望模型中的行数与字符串列表中的字符串数相同
//我们考虑到这一点来实现 rowCount() 函数:
/*
    int StringListModel::rowCount(const QModelIndex &parent) const
    {
        return stringList.count();
    }
*/
//由于模型是非分层的,我们可以放心地忽略与父项对应的模型索引。
//默认情况下,从 QAbstractListModel 派生的模型只包含一列,因此我们不需要重新实现 columnCount() 函数。
//Model headers and data
//对于视图中的项目,我们希望返回字符串列表中的字符串
//data() 函数负责返回对应于 index 参数的数据项:
/*
    QVariant StringListModel:data(const QModelIndex &index,int role) const
    {
        if(!index.isValid())
            return QVariant();

        if(index.row() >= stringlist.size())
            return QVariant();

        if(role == Qt::DisplayRole)
            return stringList::at(index.row());
        else
            return QVariant();
    }
*/
//如果提供的模型索引有效,行号在字符串列表中的项目范围内,并且请求的角色是我们支持的角色,我们只会返回一个有效的 QVariant。
//某些视图,例如 QTreeView 和 QTableView,
//能够显示标题以及项目数据。
//如果我们的模型显示在带有标题的视图中,我们希望标题显示行号和列号。
//我们可以通过子类化 headerData() 函数来提供有关标题的信息:
/*
    QVariant StringListModel::headerData(int section,Qt::Orientation orientation,
                                         int role) const
    {
        if(role != Qt::DisplayRole)
            return QVariant();

        if(orientation == Qt::Horizontal)
            return QStringLiteral("Column %1").arg(section);
        else
            return QStringLiteral("Row %1").arg(section);
    }
*/

//同样,只有当角色是我们支持的角色时,
//我们才返回有效的 QVariant。
//在决定要返回的确切数据时,还会考虑标头的方向。
//并非所有视图都显示带有项目数据的标题,而那些显示标题的视图可能会被配置为隐藏它们。
//尽管如此,建议您实现 headerData() 函数以提供有关模型提供的数据的相关信息。
//一个项目可以有多个角色,根据指定的角色给出不同的数据
//我们模型中的项目只有一个角色 DisplayRole,因此我们返回项目的数据,而不管指定的角色如何。
//但是,我们可以在其他角色中重用我们为 DisplayRole 提供的数据,
//例如视图可用于在工具提示中显示有关项目的信息的 ToolTipRole。


//可编辑的模型
//只读模型显示了如何向用户呈现简单的选择
//但对于许多应用程序,可编辑列表模型更有用。
//我们可以修改只读模型,通过更改我们为只读实现的 data() 函数,
//并通过实现两个额外的函数:flags() 和 setData() 来使项目可编辑。
//以下函数声明被添加到类定义中:
/*
    Qt::ItemFlags flags(const QModelIndex &index) const override;
    bool setData(const QModelIndex &index, const QVariant &value,
                 int role = Qt::EditRole) override;
*/

//Making the model editable
//委托在创建编辑器之前检查项目是否可编辑
//模型必须让委托知道其项目是可编辑的。
//我们通过为模型中的每个项目返回正确的标志来做到这一点;
//在这种情况下,我们启用所有项目并使它们既可选择又可编辑:
/*
Qt::ItemFlags StringListModel::flags(const QModelIndex &index) const
{
    if(!index.isValid())
        return Qt::ItemIsEnabled;

    return QAbstractItemModel::flags(index) | Qt::ItemIsEditable;
}
//请注意,我们不必知道委托如何执行实际的编辑过程
//我们只需要为委托提供一种方法来设置模型中的数据。
//这是通过 setData() 函数实现的:
/*
    bool StringListModel::setData(const QModelIndex &index,
                                  const QVariant &value, int role)
    {
        if(index.isValid() && role == Qt::EditRole)
        {
            stringList.replace(index.row(),value.toString());
            emit dataChanged(index,index,{role});
            return true;
        }
        return false;
    }
*/
//在此模型中,与模型索引对应的字符串列表中的项目将替换为提供的值。
//但是,在修改字符串列表之前,我们必须确保索引有效,项目类型正确,并且支持角色。
//按照惯例,我们坚持认为该角色是 EditRole,因为这是标准项目委托使用的角色。
//但是,对于布尔值,您可以使用 Qt::CheckStateRole 并设置 Qt::ItemIsUserCheckable 标志;
//然后使用一个复选框来编辑该值。
//此模型中的基础数据对于所有角色都是相同的,因此此细节只是使模型与标准组件的集成更容易。
//在此模型中,与模型索引对应的字符串列表中的项目将替换为提供的值。
//但是,在修改字符串列表之前,我们必须确保索引有效,项目类型正确
//并且支持角色。
//按照惯例,我们坚持认为该角色是 EditRole,因为这是标准项目委托使用的角色。
//但是,对于布尔值,您可以使用 Qt::CheckStateRole 并设置 Qt::ItemIsUserCheckable 标志;
//然后使用一个复选框来编辑该值。
//此模型中的基础数据对于所有角色都是相同的,因此此细节只是使模型与标准组件的集成更容易。
//设置数据后,模型必须让视图知道某些数据已更改。
//这是通过发出 dataChanged() 信号来完成的。
//由于只有一项数据发生了变化,因此信号中指定的项目范围仅限于一个模型索引。
/*
    QVariant StringListModel::data(const QModelIndex &index, int role) const
    {
        if(!index.isValid())
            return QVariant();

        if(index.row() >= stringList.size())
            return QVariant();

        if(role == Qt::DisplayRole || role == Qt::EditRole)
            return stringList.at(index.row());
        else
            return QVariant();
    }
*/
//Inserting and removing rows
//可以更改模型中的行数和列数。
//。 在字符串列表模型中,只有改变行数才有意义,所以我们只重新实现插入和删除行的函数。
//这些在类定义中声明:
/*
    bool insertRows(int position, int rows, const QModelIndex &index = QModelIndex()) override;
    bool removeRows(int position, int rows, const QModelIndex &index = QModelIndex()) override;
*/

//由于此模型中的行对应于列表中的字符串,
//因此 insertRows() 函数在指定位置之前将多个空字符串插入到字符串列表中。
//插入的字符串数等于指定的行数。
//父索引通常用于确定应在模型中的何处添加行。
//在这种情况下,我们只有一个顶级字符串列表,因此我们只需将空字符串插入该列表中。
/*
    bool StringListModel::insertRows(int position, int row,const QModelIndex &parent)
    {
        beginInsertRows(QModelIndex(),position,position+rows-1);

        for(int row = 0; row < rows; ++row)
        {
            stringList.insert(position,"");
        }
        endInsertRows();
        return true;
    }
*/
//该模型首先调用 beginInsertRows() 函数来通知其他组件行数即将发生变化。
//该函数指定要插入的第一行和最后一行的行号,以及其父项的模型索引。
//更改字符串列表后,调用endInsertRows()完成操作,并通知其他组件模型的维度发生了变化,返回true表示成功。
//从模型中删除行的函数也很容易编写。
//要从模型中删除的行由给定的位置和行数指定
//我们忽略父索引以简化我们的实现,只是从字符串列表中删除相应的项目。
/*
    bool StringListModel::removeRows(int position,int rows,const QModelIndex &parent)
    {
        beginRemoveRows(QModelIndex(),position,position+rows - 1);

        for(int row = 0; row < rows; ++row)
        {
            stringList.removeAt(position);
        }
        endRemoveRows();
        return true;
    }
*/
//beginRemoveRows() 函数始终在删除任何基础数据之前调用,
//并指定要删除的第一行和最后一行。
//这允许其他组件在数据变得不可用之前访问数据。
//删除行后,模型发出 endRemoveRows() 以完成操作并让其他组件知道模型的维度已更改。
    
//Next steps
//我们可以显示此模型或任何其他模型提供的数据,使用 QListView 类以垂直列表的形式呈现模型的项目。
//对于字符串列表模型,此视图还提供了一个默认编辑器
//以便可以操作项目。 我们检查了视图类中标准视图类提供的可能性

//Item View Convenience Classes
//基于项目的小部件具有反映其用途的名称:
//QListWidget 提供项目列表,QTreeWidget 显示多级树结构
//QTableWidget 提供单元格项目表。
//每个类都继承了 QAbstractItemView 类的行为
//该类实现了项目选择和标题管理的常见行为。

//List widgets
//单级项目列表通常使用一个 QListWidget 和多个
//QListWidgetItem 来显示。
//列表小部件的构造方式与任何其他小部件相同:

//QListWidget *listWidget = new QListWidget(this);

//列表项可以在构造时直接添加到列表小部件中:
/*
    new QListWidgetItem(tr("Sycamore"),listWidget);
    new QListWidgetItem(tr("Chestnut"),listWidget);
    new QListWidgetItem(tr("Mahogany"),listWidget);
*/

//它们也可以在没有父列表小部件的情况下构建,并在稍后添加到列表中:
/*
    QListWidgetItem *newItem = new QListWidgetItem;
    newItem->setText(itemText);
    listWidget->insertItem(row,newItem);
*/
//列表中的每一项都可以显示一个文本标签和一个图标。
//可以更改用于呈现文本的颜色和字体,以便为项目提供自定义外观。
//工具提示、状态提示和“这是什么?” 帮助都可以轻松配置
//以确保列表正确集成到应用程序中。
/*
    newItem->setToolTip(toolTipText);
    newItem->setStatusTip(toolTipText);
    newItem->setWhatsThis(whatsThisText);
*/
//默认情况下,列表中的项目按其创建顺序显示。
//可以根据 Qt::SortOrder 中给出的标准对项目列表进行排序,
//以生成按字母顺序向前或向后排序的项目列表:
/*
    listWidget->sortItems(Qt::AscendingOrder);
    listWidget->sortItems(Qt::DescendingOrder);
*/

//Tree Widgets
//树或项目的分层列表由 QTreeWidget 和 QTreeWidgetItem 类提供。
//树小部件中的每个项目都可以有自己的子项目,并且可以显示多列信息。
//树小部件的创建就像任何其他小部件一样:

//QTreeWidget *treeWidget = new QTreeWidget(this);

//在将项目添加到树小部件之前,必须设置列数。
//例如,我们可以定义两列,并创建一个标题以在每列的顶部提供标签:
/*
    treeWidget->setColumnCount(2);
    QStringList handers;
    headers << tr("Subject")<setHeaderLabels(headers);
*/
//为每个部分设置标签的最简单方法是提供一个字符串列表。
//对于更复杂的标题,您可以构建一个树项,
//根据需要对其进行装饰,并将其用作树小部件的标题。

//树小部件中的顶级项目是使用树小部件作为其父小部件构建的。
//它们可以按任意顺序插入,或者您可以通过在构造每个项目时指定前一个项目来确保它们按特定顺序列出:
/*
    QTreeWidgetItem *cities = new QTreeWidgetItem(treeWidget);
    cities->setText(0,tr("Cities"));
    QTreeWidgetItem *osloItem = new QTreeWidgetItem(cities);
    osloItem->setText(0,tr("Oslo"));
    osloItem->setText(1,tr("Yes"));

    QTreeWidgetItem *planets = new QTreeWidgetItem(treeWidget,cities);
*/

//树小部件处理顶级项目与树内部更深层次的其他项目略有不同。
//可以通过调用树小部件的 takeTopLevelItem() 函数从树的顶层删除项目,
//但通过调用其父项的 takeChild() 函数删除较低级别的项目。
//使用 insertTopLevelItem() 函数将项目插入到树的顶层。
//在树的较低级别,使用父项的 insertChild() 函数。
//在树的顶层和较低层之间移动项目很容易。
//我们只需要检查项目是否是顶级项目,这些信息由每个项目的 parent() 函数提供。
//例如,我们可以删除树小部件中的当前项目,而不管其位置:
/*
    QTreeWidgetItem *parent = currentItem->parent();

    int index;

    if(parent)
    {
        index = parent->indexOfChild(treeWidget->currentItem());
        delete parent->takeChild(index);
    }
    else
    {
        index = treeWidget->indexOfTopLevelItem(treeWidget->currentItem());
        delete treeWidget->takeTopLevelItem(index);
    }
*/
//将项目插入树小部件中的其他位置遵循相同的模式:
/*
    QTreeWidgetItem *parent = currentItem->parent();
    QTreeWidgetItem *newItem;
    if(parent)
        newItem = new QTreeWidgetItem(parent,treeWidget->currentItem());
    else
        newItem = new QTreeWidgetItem(treeWidget,treeWidget->currentItem());
*/

//Table widgets
//类似于电子表格应用程序中的项目表是使用 QTableWidget 和 QTableWidgetItem 构建的。
//这些提供了一个滚动表小部件,其中包含要在其中使用的标题和项目。
//可以使用一定数量的行和列创建表格,或者根据需要将这些表格添加到未调整大小的表格中。
/*
    QTableWidget *tableWidget;
    tableWidget = new QTableWidget(12,3,this);
*/
//在将项目添加到所需位置的表之前,先在表外构建项目:
/*
    QTableWidgetItem *newItem = new QTableWidgetItem(tr("%1").arg(pow(row,column+1)));
    tableWidget->setItem(row,column,newItem);    
*/
//通过在表格外构造项目并将它们用作标题,可以将水平和垂直标题添加到表格中:
/*
    QTableWidgetItem *valueHeaderItem = new QTableWidgetItem(tr("Value"));
    tableWidget->setHorizontalHeaderItem(0,valuesHeaderItem);
*/
//请注意,表中的行和列从零开始。
//Common features
//每个便利类都有许多基于项目的特性
//这些特性可通过每个类中的相同接口使用。
//我们在以下部分中展示了这些,并提供了一些不同小部件的示例。
//查看每个小部件的模型/视图类列表,了解有关所用每个函数的使用的更多详细信息。

//Hidden items
//能够在项目视图小部件中隐藏项目而不是删除它们有时很有用。
//以上所有小部件的项目都可以隐藏,稍后再次显示。
//您可以通过调用 isItemHidden() 函数来确定项目是否隐藏,
//并且可以使用 setItemHidden() 隐藏项目。
//由于此操作是基于项目的,因此所有三个便利类都可以使用相同的功能。

//Selections
//选择项目的方式由小部件的选择模式(QAbstractItemView::SelectionMode) 控制。
//此属性控制用户是否可以选择一个或多个项目,以及在多项目选择中
//选择是否必须是连续范围的项目。 选择模式的工作方式与上述所有小部件相同。

//单项选择:当用户需要从小部件中选择单项时,
//默认的 SingleSelection 模式最适合。
//在此模式下,当前项和选定项相同。

//多项目选择:在这种模式下,用户可以在不改变现有选择的情况下切换小部件中任何项目的选择状态
//就像可以独立切换非独占复选框的方式一样。

//扩展选择:通常需要选择许多相邻项目的小部件,例如电子表格中的那些,需要扩展选择模式。
//在此模式下,可以使用鼠标和键盘选择小部件中连续范围的项目。
//如果使用修饰键,也可以创建复杂的选择,包括许多不与小部件中其他选定项目相邻的项目。
//如果用户选择一个项目而不使用修饰键,则现有选择将被清除。

//使用 selectedItems() 函数读取小部件中的选定项目,提供可以迭代的相关项目列表。
//例如,我们可以使用以下代码查找所选项目列表中所有数值的总和:
/*
    const QList selected = tableWidget->selectedItems();
    int number = 0;
    double total = 0;

    for(QTableWidgetItem* item : selected)
    {
        bool ok;
        double value = new item->text().toDouble(&ok);

        if(ok && !item->text().isEmpty())
        {
            total += value;
            number++;
        }   
    }
*/


//请注意,对于单选模式,当前项目将在选择中。
//在多选和扩展选择模式下,当前项目可能不在选择范围内,这取决于用户形成选择的方式。

//Searching
//能够在项目视图小部件中查找项目通常很有用,
//无论是作为开发人员还是作为向用户呈现的服务。
//所有三个项目视图便利类都提供了一个通用的 findItems() 函数,以使其尽可能一致和简单。

//根据从 Qt::MatchFlags 中选择的值指定的标准,
//通过它们包含的文本搜索项目。 我们可以使用 findItems() 函数获取匹配项的列表:
/*
    const QListfound = treeWidget->findItems(itemText,Qt::MathWildcard);

    for(QTreeWidgetItem *item : found)
    {
        item->setSelected(true);
        //show the item->text(0) for each item
    }
*/
//如果树小部件中包含搜索字符串中给出的文本,则上面的代码会导致选择树小部件中的项目。
//此模式也可用于列表和表格小部件。


//在项目视图中使用拖放

//模型/视图框架完全支持 Qt 的拖放基础设施。
//表、表格和树中的项目可以在视图内拖动,数据可以作为 MIME 编码数据导入和导出。
//标准视图自动支持内部拖放,在其中移动项目以更改它们的显示顺序。
//默认情况下,不为这些视图启用拖放,因为它们被配置为最简单、最常见的用途。
//要允许拖动项目,需要启用视图的某些属性,并且项目本身也必须允许拖动发生。

//仅允许从视图导出项目且不允许将数据放入其中的模型的要求低于完全启用的拖放模型的要求。

//Using convenience views
//与 QListWidget、QTableWidget 和 QTreeWidget 一起使用的每种类型的项目默认配置为使用不同的标志集。
//例如,每个 QListWidgetItem 或 QTreeWidgetItem 最初都是启用的
//可勾选的、可选择的,并且可以作为拖放操作的来源;
//每个 QTableWidgetItem 也可以编辑并用作拖放操作的目标。
//尽管所有标准项都为拖放设置了一个或两个标志,
//但您通常需要在视图本身中设置各种属性以利用内置的拖放支持:

//要启用项目拖动,请将视图的 dragEnabled 属性设置为 true。
//要允许用户在视图中放置内部或外部项目,请将视图的 viewport() 的 acceptDrops 属性设置为 true。
//要向用户显示当前被拖动的项目放置在何处,请设置视图的 showDropIndicator 属性
//这为用户提供了关于视图内项目放置的持续更新信息。

//例如,我们可以使用以下代码行在列表小部件中启用拖放:
/*
    QListWidget *listWidget = new QListWidget(this);
    listWidget->setSelectionMode(QAbstractItemView::SingleSelection);
    listWidget->setDragEnable(true);
    listWidget->setDropIndicatorShown(true);
*/

//结果是一个列表小部件,它允许在视图内复制项目,
//甚至允许用户在包含相同类型数据的视图之间拖动项目。
//在这两种情况下,项目都被复制而不是移动。
//为了让用户能够在视图内移动项目,我们必须设置列表小部件的 dragDropMode:
//listWidget->setDragDropMode(QAbstractItemView::InternalMove);

//Using model/view classes
//设置拖放视图遵循与便利视图相同的模式。
//例如,QListView 可以像 QListWidget 一样设置:
/*
    QListView *listView = new QListView(this);
    listView->setSelectionMode(QAbstractItemView::ExtendedSelection);
    listView->setDrapEnabled(true);
    listView->setAcceptDrops(true);
    listView->setDropIndicatorShown(true);
*/

//由于对视图显示的数据的访问是由模型控制的,
//因此所使用的模型还必须提供对拖放操作的支持。
//模型支持的操作可以通过重新实现 QAbstractItemModel::supportedDropActions() 函数来指定。
//例如,使用以下代码启用复制和移动操作:
/*
    Qt::DropActions DragDropListModel::supportedDropActions() const
    {
        return Qt::copyAction | Qt::MoveAction;
    }
*/
//尽管可以给出来自 Qt::DropActions 的值的任意组合,但需要编写模型来支持它们。
//例如,为了允许 Qt::MoveAction 与列表模型正确使用
//模型必须直接或通过从其基类继承实现来提供 QAbstractItemModel::removeRows() 的实现。

//启用项目的拖放
//模型通过重新实现 QAbstractItemModel::flags() 函数以提供合
//适的标志来向视图指示哪些项目可以拖动,哪些将接受放置。
//例如,一个基于 QAbstractListModel 提供简单列表的模型可以
///通过确保返回的标志包含 Qt::ItemIsDragEnabled 和 Qt::ItemIsDropEnabled 值来为每个项目启用拖放:
/*
    Qt::ItemFlags DragDropListModel::flags(const QModelIndex &index) const
    {
        Qt::ItemFlags defaultFlags = QStringListModel::flags(index);

        if(index.isValid())
            return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
        else
            return Qt::ItemIsDropEnabled | defaultFlags;
    }
*/
//请注意,可以将项目放入模型的顶层,但仅对有效项目启用拖动。
//在上面的代码中,由于模型是从 QStringListModel 派生出来的,我们通过调用它的 flags() 函数的实现来获得一组默认的标志。

//Encoding exported data
//在拖放操作中从模型导出数据项时,它们被编码为与一种或多种 MIME 类型相对应的适当格式。
//模型通过重新实现 QAbstractItemModel::mimeTypes() 函数并返回标准 MIME 类型列表来声明它们可用于提供项目的 MIME 类型。
//例如,仅提供纯文本的模型将提供以下实现:
/*
    QStringList DragDropListModel::mimeTypes() const
    {
        QStringList types;
        types<<"application/vnd.text.list";
        return types;
    }
*/
//该模型还必须提供代码以对广告格式的数据进行编码
//这是通过重新实现 QAbstractItemModel::mimeData() 函数以提供 QMimeData 对象来实现的,
//就像在任何其他拖放操作中一样。
//以下代码显示了与给定索引列表相对应的每个数据项如何编码
//为纯文本并存储在 QMimeData 对象中。
/*
QMimeData *DragDropListModel::mimeData(const QModelIndexList &indexes) const
{
    QMimeData *mimeData = new QMimeData;
    QByteArray encodedData;

    QDataStream stream(&encodedData,QIODevice::WriteOnly);

    for(const QModelIndex &index : indexes)
    {
        if(index.isValid())
        {
            QString text = data(index,Qt::DisplayRole).toString();
            stream<setData("application/vnd.text.list",encodedData);
    return mimeData;
}
*/
//由于向函数提供了模型索引列表,因此这种方法足够通用,可用于分层模型和非分层模型。
//请注意,自定义数据类型必须声明为元对象,并且必须为它们实现流运算符。
//有关详细信息,请参阅 QMetaObject 类说明。

//Inserting dropped data into a model
//任何给定模型处理丢弃数据的方式取决于其类型(列表、表格或树)及其内容可能呈现给用户的方式。
//通常,用于容纳丢弃数据的方法应该是最适合模型底层数据存储的方法。
//不同类型的模型倾向于以不同的方式处理丢弃的数据。
//列表和表模型仅提供存储数据项的平面结构。
//因此,当数据被放置在视图中的现有项目上时
//它们可能会插入新的行(和列)
//或者它们可能会使用提供的某些数据覆盖模型中项目的内容。
//树模型通常能够将包含新数据的子项添加到其底层数据存储中
//因此就用户而言,其行为更可预测。

//删除的数据由模型重新实现的
//QAbstractItemModel::dropMimeData() 处理。
//例如,处理简单字符串列表的模型可以提供一种实现,
//该实现可以分别处理放到现有项目上的数据和放到模型顶层(即,放到无效项目上)的数据。
//模型可以通过重新实现 QAbstractItemModel::canDropMimeData() 来禁止删除某些项目,
//或者根据删除的数据。
//模型首先必须确保操作应该被执行,
//所提供的数据是可以使用的格式,并且它在模型中的目标是有效的:
/*
    bool DragDropListMode::canDropMimeData(const QMimeData *data,
            Qt::DropAction action,int row,int column, const QModelIndex &parent) const
    {
        Q_UNUSED(action);
        Q_UNUSED(row);
        Q_UNUSED(parent);

        if(!data->hasFormat("application/vnd.text.list"))
            return false;

        if(column > 0)
            return false;

        return true;
    }

    bool DragDropListModel::DropMimeData(const QMimeData,
        Qt::DropAction action,int row,int column,const QModelIndex &parent)
    {
        if(!canDropMimeData(data,action,row,column,parent))
            return false;

        if(action == Qt::IgnoreAction)
            return true;

        //如果提供的数据不是纯文本,
        //或者如果提供的列号无效,
        //,简单的一列字符串列表模型可以指示失败。
        //要插入到模型中的数据会根据它是否被放到现有项目上而有不同的处理方式。
        //在这个简单的示例中,我们希望允许在现有项目之间
        //列表中的第一个项目之前和最后一个项目之后放置
        //当drop发生时,父item对应的model index要么有效,
        //表示drop发生在某item上
        //要么无效,表示drop发生在model顶层对应的view的某处 .

        int beginRow;

        if(row != -1)
            beginRow = row;

        //我们首先检查提供的行号
        //看看我们是否可以使用它向模型中插入项目
        //而不管父索引是否有效。

        else if(parent.isValid())
            beginRow = parent.row();
        //如果父模型索引有效,则丢弃发生在项目上。
        //在这个简单的列表模型中,我们找出项目的行号并使用该值将删除的项目插入模型的顶层。
        else 
            beginRow = rowCount(QModelIndex());

        //当视图中的其他地方发生丢弃并且行号不可用时
        //我们将项目附加到模型的顶层。
        //在分层模型中,当一个项目发生下降时,最好将新项目作为该项目的子项插入模型中。
        //在这里显示的简单示例中,模型只有一个级别,因此这种方法并不合适。

        //Decoding imported data
        //dropMimeData() 的每个实现还必须解码数据并将其插入到模型的底层数据结构中。
        //对于简单的字符串列表模型,编码项可以被解码并流式传输到 QStringList 中:
            QByteArray encodedData = data->data("application/vnd.text.list");
            QDataStream stream(&encodedData,QIODevice::ReadOnly);
            QStringList newItems;
            int rows = 0;

            while(!stream.atEnd())
            {
                QString text;
                stream>>text;
                newItems<
//请注意,模型通常需要提供 QAbstractItemModel::insertRows() 和 QAbstractItemModel::setData() 函数的实现。

//因为 C++ 禁止联合包含具有非默认构造函数或析构函数的类型,
//所以大多数有趣的 Qt 类不能在联合中使用。
//如果没有 QVariant,这对于 QObject::property() 和数据库工作等来说将是一个问题。
//QVariant 对象一次保存一个 typeId() 的单个值。
//(某些类型是多值的,例如字符串列表。)您可以找出变体包含的类型 T,
//使用 convert() 将其转换为其他类型,
//使用 toT() 函数之一获取其值 (例如,toSize()),
//并检查是否可以使用 canConvert() 将类型转换为特定类型。
//名为 toT() 的方法(例如,toInt()、toString())是常量
//如果您要求存储类型,它们将返回存储对象的副本。
//如果您要求可以从存储的类型生成的类型, toT() 会复制并转换对象本身并保持不变。
//如果您要求的类型无法从存储的类型中生成,则结果取决于类型
//下面是一些示例代码来演示 QVariant 的使用:
/*
    QDataStream out(...);
    QVariant v(123); //The variant now contains an int 
    int x = v.toInt();  //x = 123;
    out<> v;            //Reads an Int variant
    int z = v.toInt(); //z =  //z = 123
    qDebug("Type is %s", v.typeName());
    v = v.toInt() + 100;    //The variant now holds the value 223
    v = QVariant(QStringList()); //The variant now holds a QStringList
*/
//您甚至可以将 QList 和 QMap 值存储在一个变体中,
//因此您可以轻松构建任意类型的任意复杂的数据结构。
//这是非常强大和通用的,但可能比在标准数据结构中存储特定类型的内存和速度效率低。
//QVariant 还支持空值的概念。 如果变体不包含初始化值或包含空指针,则变体为空。
/*
    QVariant x; //x.isNull() == true
    QVariant y = QVariant::fromValue(nullptr); //y.isNull() == true
*/
//Variant 可以扩展以支持 QMetaType::Type 枚举中提到的类型之外的其他类型。 
//有关详细信息,请参阅创建自定义 Qt 类型。


QVariant CustomTableModel::data(const QModelIndex &index, int role) const
{
    //enum Qt::ItemDataRole
    //模型中的每个项目都有一组与之关联的数据元素
    //每个元素都有自己的角色
    //视图使用角色来向模型指示它需要哪种类型的数据。
    //自定义模型应该返回这些类型的数据
    if (role == Qt::DisplayRole) {  //要以文本形式呈现的关键数据。 (QString)
        return m_data[index.row()]->at(index.column());
    } else if (role == Qt::EditRole) { //适合在编辑器中编辑的形式中的数据。 (QString)
        return m_data[index.row()]->at(index.column());
    } else if (role == Qt::BackgroundRole) { //用于使用默认委托呈现的项目的背景画笔。 (QBrush)
        for (const QRect &rect : m_mapping) {
            if (rect.contains(index.column(), index.row()))
                return QColor(m_mapping.key(rect));
        }

        // cell not mapped return white color
        return QColor(Qt::white);
    }
    return QVariant();
}

bool CustomTableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    适合在编辑器中编辑的形式中的数据。 (QString)
    if (index.isValid() && role == Qt::EditRole) {
        m_data[index.row()]->replace(index.column(), value.toDouble());
        emit dataChanged(index, index);
        return true;
    }
    return false;
}

Qt::ItemFlags CustomTableModel::flags(const QModelIndex &index) const
{
    return QAbstractItemModel::flags(index) | Qt::ItemIsEditable;
}

void CustomTableModel::addMapping(QString color, QRect area)
{
    m_mapping.insert(color, area);
}

tablewidget.h

#ifndef TABLEWIDGET_H
#define TABLEWIDGET_H

//小部件是用户界面的原子:
//它从窗口系统接收鼠标、
//键盘和其他事件,并在屏幕上绘制自己的表示。
//每个小部件都是矩形的,它们按 Z 顺序排序。
//小部件被其父部件和它前面的小部件剪裁。
//未嵌入父窗口小部件的窗口小部件称为窗口。
//通常,窗口有一个框架和一个标题栏,
//尽管也可以使用合适的窗口标志创建没有这种装饰的窗口。
//在 Qt 中,QMainWindow 和 QDialog 的各种子类是最常见的窗口类型。
//每个小部件的构造函数都接受一两个标准参数:
//QWidget *parent = nullptr 是新小部件的父级。
//如果它是 nullptr(默认值),新的小部件将是一个窗口。
//如果不是,它将是父级的子级
//并受父级几何图形的约束(除非您将 Qt::Window 指定为窗口标志)。
//Qt::WindowFlags f = { } (如果可用)设置窗口标志;
//默认值适用于几乎所有小部件
//但要获得例如没有窗口系统框架的窗口,您必须使用特殊标志。
//QWidget 有很多成员函数,但其中一些没有直接的功能;
//例如, QWidget 有一个 font 属性,但从不使用它本身。
//有许多提供真正功能的子类,
//例如 QLabel、QPushButton、QListWidget 和 QTabWidget。

#include 
#include "customtablemodel.h"

class TableWidget : public QWidget
{
    Q_OBJECT

public:
    TableWidget(QWidget *parent = 0);

private:
    CustomTableModel *m_model;
};

#endif // TABLEWIDGET_H

tablewidget.cpp

#include "tablewidget.h"
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

QT_USE_NAMESPACE

//QTableView 实现了一个表格视图
//用于显示模型中的项目
//此类用于提供以前由 QTable 类提供的标准表
//但使用 Qt 的模型/视图架构提供的更灵活的方法。
//QTableView 类是模型/视图类之一,是 Qt 模型/视图框架的一部分。
//QTableView 实现了 QAbstractItemView 类定义的接口
//以允许它显示从 QAbstractItemModel 类派生的模型提供的数据。
//Navigation
//您可以通过用鼠标单击单元格或使用箭头键来导航表格中的单元格
//因为 QTableView 默认启用 tabKeyNavigation,
//所以你也可以点击 Tab 和 Backtab 来从一个单元格移动到另一个单元格。

//Visual Appearance
//该表有一个可以使用 verticalHeader() 函数获得的垂直标题
//以及一个可以通过 horizontalHeader() 函数获得的水平标题。
//可以使用 rowHeight() 找到表格中每一行的高度;
//类似地,可以使用 columnWidth() 找到列的宽度。
//由于这两个都是普通小部件,因此您可以使用其 hide() 
//函数隐藏它们中的任何一个。

//可以使用 hideRow()、hideColumn()、
//showRow() 和 showColumn() 隐藏和显示行和列。
//可以使用 selectRow() 和 selectColumn() 选择它们。
//该表将根据 showGrid 属性显示网格。
    
//表视图中显示的项目,就像其他项目视图中的项目一样,
//使用标准委托进行渲染和编辑。
//但是,对于某些任务,有时能够在表格中插入小部件是有用的。
//使用 setIndexWidget() 函数为特定索引设置小部件,
//然后使用 indexWidget() 检索。
//默认情况下,表格中的单元格不会扩展以填充可用空间
//您可以通过拉伸最后一个标题部分来使单元格填充可用空间。
//使用horizontalHeader() 或verticalHeader() 
//访问相关标题并设置标题的stretchLastSection 属性。
//要根据每列或每行的空间需求分配可用空间,
//请调用视图的 resizeColumnsToContents() 或 resizeRowsToContents() 函数。

//Coordinate Systems
//对于某些特殊形式的表格,能够在行和列索引以及小部件坐标之间进行转换非常有用。
//rowAt() 函数提供指定行视图内的 y 坐标;
//行索引可用于通过 rowViewportPosition() 获取相应的 y 坐标。
//columnAt() 和 columnViewportPosition() 
//函数提供 x 坐标和列索引之间的等效转换操作。


TableWidget::TableWidget(QWidget *parent)
    : QWidget(parent)
{
    // create simple model for storing data
    // user's table data model
    //! [1]
    m_model = new CustomTableModel;
    //! [1]

    //! [2]
    // create table view and add model to it
    QTableView *tableView = new QTableView;
    //[virtual] void QAbstractItemView::setModel(QAbstractItemModel *model)
    //设置要呈现的视图模型
    //此函数将创建并设置一个新的选择模型,替换之前使用 setSelectionModel() 设置的任何模型
    //但是,旧的选择模型不会被删除,因为它可能在多个视图之间共享。
    //如果不再需要旧的选择模型,我们建议您删除它。
    //这是通过以下代码完成的:
    /*
        QItemSelectionModel *m = view->selectionModel();
        view->setModel(new model);
        delete m;
    */
    //如果旧模型和旧选择模型都没有父对象
    //或者它们的父对象是长期存在的对象,
    //则最好调用它们的 deleteLater() 函数来显式删除它们。
    //除非它是模型的父对象,否则视图不会获得模型的所有权,
    //因为模型可能在许多不同的视图之间共享。    
    tableView->setModel(m_model);
    //minimumWidth : int
    //此属性保存小部件的最小宽度(以像素为单位)
    //此属性对应于 minimumSize 属性所保持的宽度。
    //默认情况下,此属性的值为 0。
    tableView->setMinimumWidth(300);
    //QHeaderView *QTableView::horizontalHeader() const
    //返回表格视图的水平标题。
    //[since 5.0] void QHeaderView::setSectionResizeMode(QHeaderView::ResizeMode mode)
    //设置如何将标题调整为给定模式所描述的限制。
    //调整大小模式指定标题部分的行为。
    //可以使用 setSectionResizeMode() 在整个标题视图或单个部分上设置它。
    //QHeaderView::Stretch
    //QHeaderView 将自动调整该部分的大小以填充可用空间
    //用户或以编程方式无法更改大小。
    tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
    tableView->verticalHeader()->setSectionResizeMode(QHeaderView::Stretch);
    m_model->setParent(tableView);
    //! [2]

    //! [3]
    QChart *chart = new QChart;
    //此属性保存图表的动画选项。
    //根据此设置启用或禁用动画。
    //图表中启用了所有动画类型。
    chart->setAnimationOptions(QChart::AllAnimations);
    //! [3]

    // series 1
    //! [4]
    //此类将数据绘制为一系列按类别分组的垂直条形,
    //每个条形集中的每个类别一个条形添加到该系列中。
    QBarSeries *series = new QBarSeries;

    int first = 3;
    int count = 5;
    //模型映射器允许使用从 QAbstractItemModel 类派生的数据模型作为图表的数据源。
    //垂直模型映射器用于在数据模型和 QAbstractBarSeries 之间创建连接,
    //以便数据模型中的每一列定义一个条形集
    //而每一行映射到条形系列中的一个类别
    //模型和条形系列属性均可用于操作数据
    //模型映射器使条形系列和数据模型保持同步。
    QVBarModelMapper *mapper = new QVBarModelMapper(this);
    //此属性保存用作第一个条形集的数据源的模型列。
    mapper->setFirstBarSetColumn(1);
    //此属性保存用作最后一个条形集的数据源的模型列。
    mapper->setLastBarSetColumn(4);
    //此属性保存模型的行,该行包含条形系列中条形集的第一个值。
    mapper->setFirstRow(first);
    //此属性保存映射为条形系列数据的模型行数。
    mapper->setRowCount(count);
    //此属性保存映射器使用的条形系列。
    //系列中的所有数据在设置到映射器时都会被丢弃
    //指定新系列时,旧系列将断开连接(但保留其数据)。
    mapper->setSeries(series);
    //此属性保存映射器使用的数据模型。
    mapper->setModel(m_model);
    chart->addSeries(series);
    //! [4]

    //! [5]
    // for storing color hex from the series
    QString seriesColorHex = "#000000";

    // get the color of the series and use it for showing the mapped area
    QList<QBarSet *> barsets = series->barSets();
    for (int i = 0; i < barsets.count(); i++) {
        //[static] QString QString::number(long n, int base = 10)
        //根据指定的基数返回与数字 n 等效的字符串。
        //基数默认为 10,必须在 2 到 36 之间。对于 10 以外的基数,n 被视为无符号整数。
        //格式始终使用 QLocale::C,即英语/美国。
        //要获得数字的本地化字符串表示,请使用 QLocale::toString() 和适当的语言环境。
        /*
            long a = 63;
            QString s = QString::number(a,16); //s == "3f"
            QString t = QString::number(a,16).toUpper(); //t == "3F"
        */
        //QString QString::right(qsizetype n) const
        //返回包含字符串最右边的 n 个字符的子字符串。
        //如果您知道 n 不能越界,请在新代码中改用 last(),因为它更快。        
        seriesColorHex = "#" + QString::number(barsets.at(i)->brush().color().rgb(), 16).right(6).toUpper();
        m_model->addMapping(seriesColorHex, QRect(1 + i, first, 1, barsets.at(i)->count()));
    }
    //! [5]

    //! [6]
    QStringList categories;
    categories << "April" << "May" << "June" << "July" << "August";
    //QBarCategoryAxis 可以设置为显示带有刻度线、网格线和阴影的轴线。
    //类别绘制在刻度之间。 它也可以与线系列一起使用,如 Line 和 BarChart 示例所示。
    //以下代码说明了如何使用 QBarCategoryAxis:
    /*
        QCharView *chartView = new QChartView;
        QBarSeries *series = new QBarSeries;

        //...
        chartView->chart()->addSeries(series);
        chartView->chart()->createDefaultAxes();

        QBarCategoryAxis *axisX = new QBarCategoryAxis;
        QStringList categories;
        categories<<"Jan"<<"Feb"<<"Mar"<<"Apr"<<"May"<<"Jun";
        axisX->append(categories);
        axisX->setRange("Feb","May");
        chartView->chart()->setAxisX(axisX,series);
    */
    QBarCategoryAxis *axisX = new QBarCategoryAxis();
    //void QBarCategoryAxis::append(const QStringList &categories)
    //将类别附加到轴。
    //轴上的最大值将更改为匹配类别中的最后一个类别。
    //如果之前未定义类别,则轴上的最小值也将更改以匹配类别中的第一个类别。
    //类别必须是有效的 QString 并且不能重复。 不会附加重复的类别。
    axisX->append(categories);
    chart->addAxis(axisX, Qt::AlignBottom);
    series->attachAxis(axisX);
    QValueAxis *axisY = new QValueAxis();
    chart->addAxis(axisY, Qt::AlignLeft);
    series->attachAxis(axisY);
    //! [6]

    //! [7]
    QChartView *chartView = new QChartView(chart);
    chartView->setRenderHint(QPainter::Antialiasing);
    chartView->setMinimumSize(640, 480);
    //! [7]

    //QGridLayout 获取可用的空间(通过其父布局或 parentWidget())
    //将其划分为行和列,并将其管理的每个小部件放入正确的单元格中。
    //列和行的行为相同; 我们将讨论列,但行也有等价的函数。
    //每列都有一个最小宽度和一个拉伸因子。
    //最小宽度是使用 setColumnMinimumWidth() 设置的最大宽度和该列中每个小部件的最小宽度。
    //拉伸因子使用 setColumnStretch() 设置,并确定列将获得多少可用空间超过其必要的最小值。
    //通常,使用 addWidget() 将每个托管小部件或布局放入其自己的单元格中。
    //小部件也可以使用 addItem() 和 addWidget() 的行和列跨越重载来占据多个单元格
    //如果这样做, QGridLayout 将猜测如何在列/行上分配大小(基于拉伸因子)。
    //要从布局中删除小部件,请调用 removeWidget()。
    //在小部件上调用 QWidget::hide() 也会有效地从布局中删除小部件,直到调用 QWidget::show()。
    //下图显示了一个带有五列三行网格的对话框片段(该网格以洋红色显示):
    //此对话框片段中的第 0、2 和 4 列由 QLabel、QLineEdit 和 QListBox 组成。
    //第 1 列和第 3 列是使用 setColumnMinimumWidth() 制作的占位符。
    //第 0 行由三个 QLabel 对象
    //三个 QLineEdit 对象的第 1 行和三个 QListBox 对象的第 2 行组成。
    //我们使用占位符列(1 和 3)来获得列之间的适当空间量。
    //请注意,列和行的宽度或高度不同
    //如果您希望两列具有相同的宽度
    //您必须自己将它们的最小宽度和拉伸系数设置为相同。
    //您可以使用 setColumnMinimumWidth() 和 setColumnStretch() 执行此操作。
    //如果 QGridLayout 不是顶级布局(即不管理小部件的所有区域和子项)
    //则必须在创建时将其添加到其父布局中,但在对其进行任何操作之前
    //添加布局的正常方法是在父布局上调用 addLayout() 。
    //添加布局后,您可以开始使用 addWidget()、addItem() 和 addLayout() 将小部件和其他布局放入网格布局的单元格中。
    //QGridLayout 还包括两个边距宽度:
    //contents margin 和spacing()
    //contents margin 是沿 QGridLayout 的四个边中的每一个保留的空间的宽度。
    //spacing()是相邻框之间自动分配的间距的宽度。
    //默认内容边距值由样式提供。
    //Qt 样式指定的默认值对于子小部件是 9,对于窗口是 11
    //间距默认与顶级布局的边距宽度相同,或与父布局相同。

    //! [8]
    // create main layout
    QGridLayout *mainLayout = new QGridLayout;
    void QGridLayout::addWidget(QWidget *widget, int row, int column, Qt::Alignment alignment = Qt::Alignment())
    //将给定的小部件添加到行、列的单元格网格。 默认情况下,左上角的位置是 (0, 0)。
    //对齐方式由对齐方式指定。 默认对齐为 0,这意味着小部件填充整个单元格。
    mainLayout->addWidget(tableView, 1, 0);
    mainLayout->addWidget(chartView, 1, 1);
    //void QGridLayout::setColumnStretch(int column, int stretch)
    //设置要拉伸的列列的拉伸系数。 第一列是数字 0。
    //拉伸系数与此网格中的其他列有关。 具有较高拉伸系数的列占用更多可用空间。
    //默认拉伸因子为 0。如果拉伸因子为 0 并且此表中的其他列根本无法增长,则该列可能仍会增长。
    //另一种方法是使用带有 QSpacerItem 的 addItem() 添加间距。
    mainLayout->setColumnStretch(1, 1);
    mainLayout->setColumnStretch(0, 0);
    setLayout(mainLayout);
    //! [8]
}

Qt例子学习笔记 - Examples/Qt-6.2.0/charts/barmodelmapper_第1张图片

你可能感兴趣的:(Qt,学习,Qt,qt,1024程序员节)