【问题标题】:Connect QAbstractTableModel with QML TableView将 QAbstractTableModel 与 QML TableView 连接起来
【发布时间】:2019-06-03 21:52:02
【问题描述】:

为了创建一个非常通用的模型,我对QAbstractItemModel 进行了子类化,文件如下:

cvartablemodel.h

#ifndef CVARTABLEMODEL_H
#define CVARTABLEMODEL_H

#include <QObject>
#include <QAbstractTableModel>
#include <QList>
#include <QVariant>

/**
 * @brief   Provides a QAbstractTableModel override class that implements the
 *          API for MDE variables storing, reading and writing.
 */
class CVarTableModel : public QAbstractTableModel
{

public:

    /**
     * @brief   An enumeration class providing the columns and the amount of
     *          columns as well (use ZCOUNT as always last member).
     */
    enum class Columns
    {
        Name = 0,
        Unit,
        Value,

        ZCOUNT,
    };
    Q_ENUM(Columns)

    CVarTableModel(QObject* parent = nullptr);
    ~CVarTableModel() override;

    // Basic overrides
    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;

    // Since its a well behaved model...
    QVariant headerData(int section,
                        Qt::Orientation orientation,
                        int role) const override;

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

    // Only rows are modificable for now
    bool insertRows(int position,
                    int rows,
                    const QModelIndex &index = QModelIndex()) override;
    bool removeRows(int position,
                    int rows,
                    const QModelIndex &index = QModelIndex()) override;

private:

    /**
     * @brief   The local, intermediate storage object.
     */
    QList<QList<QVariant>> m_data;
};

#endif // CVARTABLEMODEL_H

cvartablemodel.cpp

#include <QDebug>

#include "cvartablemodel.h"

/**
 * @brief   The default constructor, nothing interesting in here.
 * @param   parent: the parent object.
 */
CVarTableModel::CVarTableModel(QObject* parent) : QAbstractTableModel(parent)
{

}

/**
 * @brief   Clear the storage and remove connections (if any) at exit
 */
CVarTableModel::~CVarTableModel()
{
    foreach (auto row, m_data)
        row.clear();

    m_data.clear();
}

/**
 * @brief   Returns the fixed (for now) amount of columns
 * @param   parent: unused
 * @return  The amount of available columns
 */
int CVarTableModel::columnCount(const QModelIndex& parent) const
{
    if (parent.isValid())
        return 0; // https://doc.qt.io/qt-5/qabstractitemmodel.html#columnCount

    return static_cast<int>(Columns::ZCOUNT);
}

/**
 * @brief   Row count is equal to the stored number of variables.
 * @param   parent: unused.
 * @return  The amount of stored variables entries.
 */
int CVarTableModel::rowCount(const QModelIndex& parent) const
{
    if (parent.isValid())
        return 0; // https://doc.qt.io/qt-5/qabstractitemmodel.html#rowCount

    return m_data.length();
}

/**
 * @brief   Reads the cell specified by the \ref index.
 * @param   index: Stores row/ col data.
 * @param   role: the display role.
 * @return  In case of valid \p index, a valid cell value. Otherwise empty
 *          QVariant object.
 */
QVariant CVarTableModel::data(const QModelIndex& index, int role) const
{
    if (role != Qt::DisplayRole)
        return QVariant();

    if (!index.isValid())
        return QVariant();

    // check the row
    if ((index.row() >= m_data.length()) || (index.row() < 0))
        return QVariant();

    // check the column
    if ((index.row() >= m_data[index.row()].length()) || (index.column() < 0))
        return QVariant();

    return m_data[index.row()][index.column()];
}

/**
 * @brief   Obtains the header (columns) names.
 * @param   section: column number.
 * @param   orientation: Accepts only horizontal.
 * @param   role: Accepts only display.
 * @return  The column header text in case all params are valid.
 *          Otherwise empty QVariant.
 */
QVariant CVarTableModel::headerData(int section,
                                    Qt::Orientation orientation,
                                    int role) const
{
    if (role != Qt::DisplayRole)
        return QVariant();

    if (orientation != Qt::Horizontal)
        return QVariant();

    if (section >= static_cast<int>(Columns::ZCOUNT))
        return QVariant();

    return QVariant::fromValue(static_cast<Columns>(section));
}

/**
 * @brief   Returns the \p index flags. Only values column is editable for now.
 * @param   index: model index item.
 * @return  flags enum val.
 */
Qt::ItemFlags CVarTableModel::flags(const QModelIndex& index) const
{
    Qt::ItemFlags flags = Qt::ItemIsEnabled;

    if (index.isValid())
    {
        if (static_cast<Columns>(index.column()) == Columns::Value)
            flags |= Qt::ItemIsEditable;
    }

    return flags;
}

/**
 * @brief   Cell data writing override.
 * @param   index: The model index with row/ col.
 * @param   value: Value to be set in the cell.
 * @param   role: Only EditRole accepted.
 * @return  Non zero on succesfull data editing.
 */
bool CVarTableModel::setData(const QModelIndex& index,
                             const QVariant& value,
                             int role)
{
    if (!index.isValid() || (role != Qt::EditRole))
        return false;

    // check the row
    if ((index.row() >= m_data.length()) || (index.row() < 0))
        return false;

    // check the column
    if ((index.row() >= m_data[index.row()].length()) || (index.column() < 0))
        return false;

    m_data[index.row()][index.column()] = value;
    emit dataChanged(index, index, {role});

    return true;
}

/**
 * @brief   Inserts the \p rows amount of rows. They will start at \p position.
 * @param   position: position at which the insertion starts (the new 1st item
 *          will have this index in the end).
 * @param   rows: amount of rows to insert.
 * @param   index: unused.
 * @return  Non zero in case of succesfull row insertion.
 */
bool CVarTableModel::insertRows(int position,
                                int rows,
                                const QModelIndex& index)
{
    Q_UNUSED(index);

    if ((position >= rowCount(QModelIndex())) || (position < 0))
        return false;

    beginInsertRows(QModelIndex(), position, position + rows - 1);
    for (int row = 0; row < rows; row++)
    {
        QList<QVariant> emptyRow;
        for (int i = 0; i < columnCount(QModelIndex()); i++)
            emptyRow.append(QVariant());

        m_data.insert(position, emptyRow);
    }
    endInsertRows();

    return true;
}

/**
 * @brief   Removes \p rows amount of rows starting at \p position
 * @param   position: removing starts at this position.
 * @param   rows: the amount of rows that will be removed.
 * @param   index: unused.
 * @return  Non zero on succesfull rows removal.
 */
bool CVarTableModel::removeRows(int position,
                                int rows,
                                const QModelIndex& index)
{
    Q_UNUSED(index);

    if ((position >= rowCount(QModelIndex())) || (position < 0))
        return false;

    if (rows > rowCount(QModelIndex()))
        return false;

    beginRemoveRows(QModelIndex(), position, position + rows - 1);
    for (int row = 0; row < rows; row++)
        m_data.removeAt(position);

    endRemoveRows();
    return true;
}

我需要将此对象的一个​​实例与 QML TableView 组件连接起来,但我真的不知道怎么做。

我已经为它创建了实例和一个 getter:

    /**
     * @brief   The API + intermediate storage model for the bad nodes
     */
    CVarTableModel m_varTabModel;

/**
 * @brief   A pointer getter for the whole variable table model.
 * @return  pointer to the model.
 */
QObject* CVessel::varTabModel()
{
    return static_cast<QObject*>(&m_varTabModel);
}

所以最初这需要是一个没有行的 3 列表(可以在构造函数中添加一些行以用于测试目的)。

QML 端的TableView 组件现在如何利用它?我会欣赏一些允许输入和编辑一些值的 TableView 的 QML 示例。

【问题讨论】:

    标签: c++ qt qml


    【解决方案1】:

    QML TableView 使用角色而不是列号。如果您检查传递给方法data() 的模型中的列,您将看到它始终为0。

    因此,您必须转换 TableView 中给定的角色和模型中的列号。

    您可以使用代理模型来处理角色/列转换。您不必更改当前模型:

    QIdentityProxyModel 类是一个很好的基础:

    class QMLProxy: public QIdentityProxyModel
    {
        Q_OBJECT
    public:
    
        QMLProxy(QObject* parent=nullptr): QIdentityProxyModel(parent)
        {}
    
        enum Role
        {
            NameRole = Qt::UserRole + 1,
            UnitRole
        };
        QHash<int, QByteArray> roleNames() const override {
            QHash<int, QByteArray> roles;
            roles[NameRole] = "COL1";
            roles[UnitRole] = "COL2";
            return roles;
        }
    
        Q_INVOKABLE QVariant data(const QModelIndex &index, int role) const override
          {
            QModelIndex newIndex = mapIndex(index, role);
            if (role == NameRole || role == UnitRole)
                role = Qt::DisplayRole;
            return QIdentityProxyModel::data(newIndex, role);
          }
    
        Q_INVOKABLE void edit(int row,
                                 const QVariant &value,
                                 QString const& role)
        {
            if (role == QString(roleNames().value(NameRole)))
                setData(createIndex(row, 0), value, Qt::EditRole);
            else if (role == QString(roleNames().value(UnitRole)))
                setData(createIndex(row, 1), value, Qt::EditRole);
        }
    
    private:
        QModelIndex mapIndex(QModelIndex const& source, int role) const {
            switch(role)
            {
            case NameRole:
                return createIndex(source.row(), 0);
            case UnitRole:
                return createIndex(source.row(), 1);
            }
            return source;
        }
    };
    

    我覆盖了data() 以将角色转换为列号。我创建了一个方法edit,因为当它从QML中调用时,签名将与方法setData不同。

    将模型从 main 传递给 QML:

    CVarTableModel* model = new CVarTableModel();
    
    QMLProxy* proxy = new QMLProxy();
    proxy->setSourceModel(model);
    
    QQuickView *view = new QQuickView;
    view->rootContext()->setContextProperty("myModel", proxy);
    view->setSource(QUrl("qrc:/main.qml"));
    view->show();
    

    然后,在 QML 中,您需要一个委托来使您的表格可编辑(我使用了 TextInput。但是,您可以使用另一个组件):

    TableView {
        TableViewColumn {
            role: "COL1"
            title: "Col 1"
            width: 100
        }
        TableViewColumn {
            role: "COL2"
            title: "Col 2"
            width: 200
        }
        model: myModel
        itemDelegate: Component {
            TextInput {
              id:textinput
              text: styleData.value
              onAccepted: {
                      myModel.edit(styleData.row, text, styleData.role)
              }
              MouseArea {
                anchors.fill: parent
                onClicked: textinput.forceActiveFocus()
              }
          }
        }
    }
    

    【讨论】:

    • mapIndex 是做什么的?是辅助函数吗?
    • 当从 QML TableView 调用 data() 时,索引中的列将始终为 0。 mapIndex 应根据角色(NameRole、UnitRole 等)返回具有右列的新索引。我编辑了我的答案以提供mapIndex 的最小实现
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-08-07
    • 2014-05-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-24
    相关资源
    最近更新 更多