天天看點

Qt模型視圖架構:自定義模型

來自官方文檔,有改動。

模型/視圖元件之間的功能分離允許建立可以利用現有視圖的模型。

QAbstractItemModel 類提供了一個足夠靈活的接口,以支援以分層結構排列資訊的資料源,允許以某種方式插入、删除、修改或排序資料。它還提供對拖放操作的支援。

QAbstractListModel 和 QAbstractTableModel 類為更簡單的非分層資料結構的接口提供支援,并且更容易用作簡單清單和表模型的起點。

設計模型

在為現有資料結建構立新模型時,重要的是要考慮應該使用哪種類型的模型來提供資料接口。

如果資料結構可以表示為一個清單或項目表格,可以将 QAbstractListModel 或 QAbstractTableModel 子類化,因為這些類為許多函數提供了合适的預設實作。

但是,如果底層資料結構隻能用層次樹結構表示,就需要對QAbstractItemModel進行子類化。

在本節中,我們實作了一個基于字元串清單的簡單模型,QAbstractListModel 提供是一個理想的建構基類。

隻讀模型示例

這裡實作的模型是一個基于标準 QStringListModel 類的簡單、非分層、隻讀的資料模型。它有一個 QStringList 作為其内部資料源。

在實作模型時,重要的是要記住 QAbstractItemModel 本身不存儲任何資料,它僅提供視圖用來通路資料的接口。

類聲明如下:

class StringListModel : public QAbstractListModel
 {
     Q_OBJECT

 public:
     StringListModel(const QStringList &strings, QObject *parent = nullptr)
         : QAbstractListModel(parent), stringList(strings) {}

     int rowCount(const QModelIndex &parent = QModelIndex()) const override;
     QVariant data(const QModelIndex &index, 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 私有成員變量中。

模型尺寸

行數和列數:

int StringListModel::rowCount(const QModelIndex &parent) const
 {
     return stringList.count();
 }

 int StringListModel::columnCount(const QModelIndex &parent) const
 {
     return 2;
 }
           

模型頭和資料

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();
 }
           

某些視圖,例如 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);
 }
           

可編輯的模型

隻讀模型顯示了如何向使用者呈現簡單的選擇,但對于許多應用程式,可編輯清單模型更有用。 我們可以修改隻讀模型,通過更改我們為隻讀實作的 data() 函數,并通過實作兩個額外的函數:flags() 和 setData() 來使項目可編輯。 以下函數添加到類中:

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

​
 Qt::ItemFlags StringListModel::flags(const QModelIndex &index) const
 {
     if (!index.isValid())
         return Qt::ItemIsEnabled;

     return QAbstractItemModel::flags(index) | Qt::ItemIsEditable;
 }

​​
 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;
 }
           

設定資料後,模型必須讓視圖知道某些資料已更改。這是通過發出 dataChanged() 信号來完成的。由于隻有一項資料發生了變化,是以信号中指定的項目範圍僅限于一個模型索引。

還需要更改 data() 函數:

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();
 }
           

彙總:

#ifndef STRINGLISTMODEL_H
#define STRINGLISTMODEL_H

#include <QAbstractListModel>

class StringListModel : public QAbstractListModel
{
    Q_OBJECT

public:
    StringListModel(const QStringList &strings, QObject *parent = nullptr)
        : QAbstractListModel(parent), stringList(strings){}

    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    int columnCount(const QModelIndex &parent = QModelIndex()) const override;

    QVariant data(const QModelIndex &index, int role) const override;
    QVariant headerData(int section, Qt::Orientation orientation,
                        int role = Qt::DisplayRole) const override;
    Qt::ItemFlags flags(const QModelIndex &index) const override;
    bool setData(const QModelIndex &index, const QVariant &value,
                     int role = Qt::EditRole) override;
    bool insertRows(int position, int rows, const QModelIndex &index = QModelIndex()) override;
    bool removeRows(int position, int rows, const QModelIndex &index = QModelIndex()) override;

private:
    QStringList stringList;
};

#endif // STRINGLISTMODEL_H
           
#include "stringlistmodel.h"

int StringListModel::rowCount(const QModelIndex &parent) const
{
    return stringList.count();
}

int StringListModel::columnCount(const QModelIndex &parent) const
{
    return 2;
}

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();
}

QVariant StringListModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (role != Qt::DisplayRole)
        return QVariant();

    if (orientation == Qt::Horizontal)
        return tr("第 %1 列").arg(section);
    else
        return tr("第 %1 行").arg(section);
}

Qt::ItemFlags StringListModel::flags(const QModelIndex &index) const
{
    if (!index.isValid())
        return Qt::ItemIsEnabled;

    return QAbstractItemModel::flags(index) | Qt::ItemIsEditable;
}

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;
}

bool StringListModel::insertRows(int position, int rows, const QModelIndex &parent)
{
    beginInsertRows(QModelIndex(), position, position+rows-1);

    for (int row = 0; row < rows; ++row) {
        stringList.insert(position, "");
    }

    endInsertRows();
    return 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;
}
           

測試:

QStringList numbers;
    numbers << "One" << "Two" << "Three" << "Four" << "Five";

    StringListModel * model = new StringListModel(numbers);
    QTableView view;
    view.setModel(model);
    view.show();
           
Qt模型視圖架構:自定義模型
Qt模型視圖架構:自定義模型

改一下data(),修改項目出現位置:

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)
    {
        if(index.column() == 0)
            if(index.row() % 2 == 0)
                return stringList.at(index.row());
            else
                return QVariant();
        else
            if(index.row() % 2 == 0)
                return QVariant();
            else
                return stringList.at(index.row());
    }
    else
        return QVariant();
}
           
Qt模型視圖架構:自定義模型