从QML ListView操作QAbstractListModel中的数据

15

我有一个 QML ListView,它使用一个 QAbstractListModel 的子类作为模型。

ListView {
    id: myListView
    x: 208
    y: 19
    width: 110
    height: 160
    delegate: myListDelegate {}
    model: MyListModel
    opacity: 0
}

该模型是一个由MyListItem组成的列表。

class MyListModel : public QAbstractListModel
{
    Q_OBJECT
public:
    enum MyRoles {
        HeadingRole = Qt::UserRole + 1,
        DescriptionRole,
        QuantityRole
    };

    explicit MyListModel(QObject *parent = 0);

    void addMyListItem(const MyListItem &item);
    int rowCount(const QModelIndex & parent = QModelIndex()) const;
    QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
    void dropList();

private:
    QList<MyListItem> m_list;

};

在代理中我有一个MouseArea。

我如何拦截鼠标区域的点击事件并从我的QList模型中选取那个MyListItem,然后将其发送到应用程序内部的C++部分?


你尝试在MyListItem中创建一个鼠标区域或覆盖onClick方法了吗? - Gabrielle de Grimouard
@GabrieldeGrimouard 我打算使用 QList<MyListItem> 元素,而不是 myListItem 作为 ListView 的代理。 - Zhigalin - Reinstate CMs
您可以定义一个新的角色(QAbstractItemModel::roleNames),返回MyListItem并在onClick事件中使用,如此处所述。 - m7913d
3个回答

19
评论中提到从data()返回指向MyListItem的指针到QML并在QML中访问和修改它。这要求你的MyListItem继承自QObject,并为每个要在QML中访问的成员添加一个Q_PROPERTY。还需要密切关注对象所有权(QQmlEngine::ObjectOwnership)。
还有另一种方法:实现QAbstractListModel::setData()QAbstractListModel::roleNames(),并且模型内容可以从QML中更改,比如model.roleName = foo
以下是最小工作示例,每次单击委托时将数量加倍: C++:
struct MyListItem
{
    QString heading;
    QString description;
    int quantity;
};

class MyListModel : public QAbstractListModel
{
    Q_OBJECT
    Q_ENUMS(MyRoles)
public:
    enum MyRoles {
        HeadingRole = Qt::UserRole + 1,
        DescriptionRole,
        QuantityRole
    };

    using QAbstractListModel::QAbstractListModel;

    QHash<int,QByteArray> roleNames() const override {
        return { { HeadingRole, "heading" },
            { DescriptionRole, "description" },
            { QuantityRole, "quantity" },
        };
    }
    int rowCount(const QModelIndex & parent = QModelIndex()) const override {
        if (parent.isValid())
            return 0;
        return m_list.size();
    }

    bool setData(const QModelIndex &index, const QVariant &value, int role) override
    {
        if (!hasIndex(index.row(), index.column(), index.parent()) || !value.isValid())
            return false;

        MyListItem &item = m_list[index.row()];
        if (role == DescriptionRole) item.description = value.toString();
        else if (role == HeadingRole) item.heading = value.toString();
        else if (role == QuantityRole) item.quantity = value.toInt();
        else return false;

        emit dataChanged(index, index, { role } );

        return true ;

    }

    QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override {
        if (!hasIndex(index.row(), index.column(), index.parent()))
            return {};

        const MyListItem &item = m_list.at(index.row());
        if (role == DescriptionRole) return item.description;
        if (role == HeadingRole) return item.heading;
        if (role == QuantityRole) return item.quantity;

        return {};
    }

private:
    QVector<MyListItem> m_list = {
        { "heading 1", "description 1", 1 },
        { "heading 2", "description 2", 42 },
        { "heading 3", "description 3", 4711 }
    };
};

QML:

ListView {
    id: listView
    anchors.fill: parent
    model: MyListModel {}

    delegate: Item {
        implicitHeight: text.height
        width: listView.width
        Text {
            id: text
            text: model.heading + " " + model.description + " " + model.quantity
        }

        MouseArea {
            anchors.fill: text
            onClicked: {
                model.quantity *= 2;
            }
        }
    }
}

谢谢!我简直不敢相信我找这个问题花了这么多时间。你知道我可以在哪里看到这个文档吗?我在QT文档中搜索了一下,没有找到相关信息。 - Curtwagner1984
2
可能这在QML中没有记录,至少http://doc.qt.io/qt-5/qtquick-modelviewsdata-cppmodels.html没有提到它,甚至错误地声称您需要从QML显式调用setData()。 - Thomas McGuire
1
请在setData()中添加一些qDebug() << "...",并尝试从qml更改model.somthing...似乎DelegateModel使用数据的本地副本,并且从未在幕后调用setData... :( - Denys Rogovchenko

11

您还可以在代理中使用index属性来操作数据。您只需要使用模型的index方法将QML索引转换为QModelIndex即可。这里是一个简单的示例,在每次单击列表项时将显示值更改为字符串"3"。

ListView {
    id: listView
    anchors.fill: parent
    model: my_model

    delegate: Rectangle {
        height: 50
        width: listView.width

        MouseArea {
            anchors.fill: parent
            onClicked: {
                // Column is always zero as it's a list
                var column_number = 0; 
                // get `QModelIndex`
                var q_model_index = my_model.index(index, column_number);

                // see for list of roles: 
                // http://doc.qt.io/qt-5/qabstractitemmodel.html#roleNames
                var role = 1

                var data_changed = my_model.setData(q_model_index, "3", role);

                console.log("data change successful?", data_changed);
            }
        }
    }
}

除了代理中的index属性外,所有默认角色名称都可用于代理。例如,我之前曾使用过decoration角色来设置Rectangle代理的color属性。有关更多信息,请参见此列表

ListView {
    delegate: Rectangle {
        // list items have access to all default `roleNames` 
        // in addition to the `index` property.
        // For example, using the decoration role, demo'd below
        color: decoration
    }
}

请参见此链接,Mitch Curtis推荐使用qmlRegisterUncreatableType注册用户枚举类型。


感谢属性索引,可以解决这个问题,我可以确认。 - freelancer

2

由于setRoleNames()在QAbstractListModel中是绝对的,您可以重写roleNames()并显式添加自己的角色。以下是继承QAbstractListModel的简单实现。

    class BaseListModel : public QAbstractListModel
    {
        Q_OBJECT
        Q_ENUMS(Roles) 
        public:
        enum Roles {
        Name = Qt::UserRole + 1
        };

        virtual QHash<int, QByteArray> roleNames() const;
        virtual int rowCount(const QModelIndex &parent) const;
        virtual QVariant data(const QModelIndex &index, int role) const override;
        virtual bool setData(const QModelIndex &index, const QVariant &value, int role) override;
       private:
       QStringList _list;
    };

    // class 
    BaseListModel::BaseListModel(QObject *parent) :
        QAbstractListModel(parent)
    {
        QHash<int, QByteArray> h = RecipeListModel::roleNames();
    }

    QHash<int, QByteArray> BaseListModel::roleNames() const {
        return {
        { Name, "name" },
        };
    }

    int BaseListModel::rowCount(const QModelIndex &parent) const {
        if (parent.isValid())
        return 0;

        return _list.size();
    }

    QVariant BaseListModel::data(const QModelIndex &index, int role) const {
        if (!hasIndex(index.row(), index.column(), index.parent()))
        return {};

        return _list.at(index.row())->data(role);  
    }

    bool RecipeListModel::setData(const QModelIndex &index, const QVariant &value, int role) {
        if (!hasIndex(index.row(), index.column(), index.parent()) || !value.isValid())
        return false;

        bool ret = _list.at(index.row())->setData(role, value);

        if (ret) {
        emit dataChanged(index, index, { role });
        }

        return ret;
    }

    QVariant BaseListModel::data(int role) const {
        switch(role) {
        case Name:
        return name();
        default:
        return QVariant();
        }
    }

    bool BaseListModel::setData(int role, const QVariant &value)
        switch(role) {
        case Name:
        setName(value.toString());
        return true;
        default:
        return false;
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接