带有 QAbstractItemModel 和上下文菜单的 Qml 2.0 TableView

2022-01-06 00:00:00 qt5 qt qml c++ menu

我有连接到 QAbstractItemModel 的 Qml 2.0 控件中的 TableView.我想制作一个上下文菜单,可以更改属性或简单地从模型中调用具体对象的方法.

I have TableView from Qml 2.0 controls that is connected to QAbstractItemModel. I want to make a context menu that can change the properties or simply call methods of a concrete object from the model.

示例:

QAbstractItemModel 有一个 std::vector.Person 有一个方法 alter() 可以进行一些更改(任何更改,究竟哪些更改并不重要,关键是我们能够调用方法).

QAbstractItemModel has an std::vector<Person>. Person has a method alter() which makes some changes (any changes, it does not matter which ones exactly are changes, the point is that the we are able to call the method).

当右键单击该行时,会出现带有 Alter 项的菜单.

When there is a right click on the row, the menu appears with an item Alter.

我所能找到的只是如何制作菜单.

All I was able to find is how to make the menu.

  rowDelegate: Item {
    Menu {
      id: myContextMenu
      MenuItem {text: "Alter"; onTriggered: {} }
    }
    MouseArea {
      id: longPressArea
      anchors.fill: parent
      acceptedButtons: Qt.LeftButton | Qt.RightButton
      onClicked: {
        if (mouse.button == Qt.RightButton)
          myContextMenu.popup()
      }
    }
  }

但我仍然不知道如何将菜单与行的确切对象连接起来.

But I still don't know how to connect the menu with an exact object of the row.

推荐答案

在委托中,您可以使用 role.property 约定来引用项目.默认角色是 display.当然,Person 必须从 QObject 派生,并且必须在 QML 引擎中注册.

Within the delegate, you can refer to the item by using the role.property convention. The default role would be display. Of course the Person has to derive from QObject, and must be registered with the QML Engine.

下面的代码演示了如何:

The code below demonstrates how to:

  1. 创建一个行为合理的 ObjectListModel 用于存储 QObjects,可从 QML 使用.

  1. Create a sensibly behaving ObjectListModel for storage of QObjects, usable from QML.

创建一个 QObject 派生类来保存您的数据.

Create a QObject-derived class that keeps your data.

从代理上显示的弹出菜单访问数据对象的属性和可调用方法.

Access the properties and invokable methods of the data objects from a pop-up menu shown on the delegate.

模型可以设置为自动通知包含的 QObject 的属性更改.此类通知,如果是由批量更改(例如在循环中完成)引起的,则会合并并作为单个 dataChanged 事件发送.

The model can be set to automatically notify about changes to the properties of the contained QObjects. Such notifications, if resulting from bulk changes (say done in a loop) are coalesced and sent off as a single dataChanged event.

不幸的是,QObject 的 user 属性没有特殊意义――你仍然需要使用 .property 选择器来访问它.

Unfortunately, the user property of a QObject acquires no special meaning - you still need to use the .property selector to access it.

可以直接观察模型的正确行为,因为有两个列表连接到同一个模型 - 它们更好地显示了相同的内容.

Proper behavior of the model can be observed directly, since there are two lists hooked to the same model - they better showed the same thing.

ObjectListModel 还可以实现角色和属性之间的映射.目前,显示和编辑角色都选择整个对象,而不是它的任何特定属性.

The ObjectListModel could also implement a mapping between the roles and the properties. Currently, both the display and edit roles select the entire object, not any particular property of it.

如果 QObject 的存储开销太大,替代模型实现可以动态创建 QObject 适配器到 POD 类型.

If the storage of QObjects is too high of an overhead, an alternative model implementation could create QObject adapters to POD types on-the-fly.

ma??in.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickWindow>
#include <QAbstractListModel>
#include <QQmlContext>
#include <QtQml>
#include <QSet>
#include <QBasicTimer>
#include <functional>

class Person : public QObject {
    Q_OBJECT
    Q_PROPERTY(QString name NOTIFY nameChanged MEMBER m_name)
    QString m_name;
public:
    Q_INVOKABLE Person(QObject * parent = 0) : QObject(parent) { setRandomName(); }
    Q_INVOKABLE Person(QString name, QObject * parent = 0) :
                       QObject(parent), m_name(name) {}
    Q_SIGNAL void nameChanged(const QString &);
    Q_INVOKABLE void setRandomName() {
        static const QString names = "Badger,Shopkeeper,Pepperpots,Gumbys,Colonel";
        static const QStringList nameList = names.split(',');
        QString newName = nameList.at(qrand() % nameList.length());
        if (newName != m_name) {
            m_name = newName;
            emit nameChanged(m_name);
        }
    }
};

class ObjectListModel : public QAbstractListModel {
    Q_OBJECT
    Q_DISABLE_COPY(ObjectListModel)
    //! Whether changes to underlying objects are exposed via `dataChanged` signals
    Q_PROPERTY(bool elementChangeTracking
               READ elementChangeTracking WRITE setElementChangeTracking
               NOTIFY elementChangeTrackingChanged)
    QObjectList m_data;
    std::function<QObject*()> m_factory;
    bool m_tracking;
    QBasicTimer m_notifyTimer;
    QMap<int, char> m_notifyIndexes;
    //! Updates the property tracking connections on given object.
    void updateTracking(QObject* obj) {
        const int nIndex = metaObject()->indexOfSlot("propertyNotification()");
        QMetaMethod const nSlot = metaObject()->method(nIndex);
        const int props = obj->metaObject()->propertyCount();
        if (m_tracking) for (int i = 0; i < props; ++i) {
            const QMetaProperty prop = obj->metaObject()->property(i);
            if (prop.hasNotifySignal()) connect(obj, prop.notifySignal(), this, nSlot);
        } else {
            disconnect(obj, 0, this, 0);
        }
    }
    //! Receives property notification changes
    Q_SLOT void propertyNotification() {
        int i = m_data.indexOf(sender());
        if (i >= 0) m_notifyIndexes.insert(i, 0);
        // All of the notifications will be sent as a single signal from the event loop.
        if (!m_notifyTimer.isActive()) m_notifyTimer.start(0, this);
    }
protected:
    //! Emits the notifications of changes done on the underlying QObject properties
    void timerEvent(QTimerEvent * ev) {
        if (ev->timerId() != m_notifyTimer.timerId()) return;
        emit dataChanged(index(m_notifyIndexes.begin().key()),
                         index((m_notifyIndexes.end()-1).key()),
                         QVector<int>(1, Qt::DisplayRole));
        m_notifyTimer.stop();
        m_notifyIndexes.clear();
    }
public:
    //! A model that creates instances via a given metaobject
    ObjectListModel(const QMetaObject * mo, QObject * parent = 0) :
        QAbstractListModel(parent),
        m_factory([mo, this](){
            return mo->newInstance(Q_ARG(QObject*, this));
        }),
        m_tracking(false)
    {}
    //! A model that creates instances using a factory function
    ObjectListModel(const std::function<QObject*()> & factory,
                    QObject * parent = 0) :
        QAbstractListModel(parent), m_factory(factory), m_tracking(false)
    {}
    ~ObjectListModel() {
        qDeleteAll(m_data);
    }
    bool elementChangeTracking() const { return m_tracking; }
    void setElementChangeTracking(bool tracking) {
        if (m_tracking == tracking) return;
        for (QObject* obj : m_data) updateTracking(obj);
        emit elementChangeTrackingChanged(m_tracking = tracking);
    }
    Q_SIGNAL void elementChangeTrackingChanged(bool);
    int rowCount(const QModelIndex &) const Q_DECL_OVERRIDE {
        return m_data.count();
    }
    QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE {
        if (role == Qt::DisplayRole || role == Qt::EditRole) {
            return QVariant::fromValue(m_data.at(index.row()));
        }
        return QVariant();
    }
    bool setData(const QModelIndex &index, const QVariant &value, int role)
    Q_DECL_OVERRIDE {
        Q_UNUSED(role);
        QObject* object = value.value<QObject*>();
        if (!object) return false;
        if (object == m_data.at(index.row())) return true;
        delete m_data.at(index.row());
        m_data[index.row()] = object;
        emit dataChanged(index, index, QVector<int>(1, role));
        return true;
    }
    Q_INVOKABLE bool insertRows(int row, int count,
                                const QModelIndex &parent = QModelIndex())
    Q_DECL_OVERRIDE {
        Q_UNUSED(parent);
        beginInsertRows(QModelIndex(), row, row + count - 1);
        for (int i = row; i < row + count; ++ i) {
            QObject * object = m_factory();
            Q_ASSERT(object);
            m_data.insert(i, object);
            updateTracking(object);
            QQmlEngine::setObjectOwnership(object, QQmlEngine::CppOwnership);
        }
        endInsertRows();
        return true;
    }
    Q_INVOKABLE bool removeRows(int row, int count,
                                const QModelIndex &parent = QModelIndex())
    Q_DECL_OVERRIDE {
        Q_UNUSED(parent);
        beginRemoveRows(QModelIndex(), row, row + count - 1);
        while (count--) delete m_data.takeAt(row);
        endRemoveRows();
        return true;
    }
};

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);
    QQmlApplicationEngine engine;
    qmlRegisterType<Person>();
    ObjectListModel model1(&Person::staticMetaObject);
    model1.setElementChangeTracking(true);
    model1.insertRows(0, 1);
    engine.rootContext()->setContextProperty("model1", &model1);
    engine.load(QUrl("qrc:/main.qml"));
    QObject *topLevel = engine.rootObjects().value(0);
    QQuickWindow *window = qobject_cast<QQuickWindow *>(topLevel);
    window->show();
    return app.exec();
}

#include "main.moc"

ma??in.qrc

<RCC>
    <qresource prefix="/">
        <file>main.qml</file>
    </qresource>
</RCC>

ma??in.qml

import QtQuick 2.0
import QtQml.Models 2.1
import QtQuick.Controls 1.0

ApplicationWindow {
    width: 300; height: 300
    Row {
        width: parent.width
        anchors.top: parent.top
        anchors.bottom: row2.top
        Component {
            id: commonDelegate
            Rectangle {
                width: view.width
                implicitHeight: editor.implicitHeight + 10
                border.color: "red"
                border.width: 2
                radius: 5
                TextInput {
                    id: editor
                    anchors.margins: 1.5 * parent.border.width
                    anchors.fill: parent
                    text: edit.name // "edit" role of the model, to break the binding loop
                    onTextChanged: {
                        display.name = text; // set the name property of the data object
                    }
                }
                Menu {
                  id: myContextMenu
                  MenuItem { text: "Randomize"; onTriggered: display.setRandomName() }
                  MenuItem { text: "Remove"; onTriggered: model1.removeRows(index, 1) }
                }
                MouseArea {
                  id: longPressArea
                  anchors.fill: parent
                  acceptedButtons: Qt.RightButton
                  onClicked: myContextMenu.popup()
                }
            }
        }
        spacing: 2
        ListView {
            id: view
            width: (parent.width - parent.spacing)/2
            height: parent.height
            model: DelegateModel {
                id: delegateModel1
                model: model1
                delegate: commonDelegate
            }
            spacing: 2
        }
        ListView {
            width: (parent.width - parent.spacing)/2
            height: parent.height
            model: DelegateModel {
                model: model1
                delegate: commonDelegate
            }
            spacing: 2
        }
    }
    Row {
        id: row2
        anchors.bottom: parent.bottom
        Button {
            text: "Add Page";
            onClicked: model1.insertRows(delegateModel1.count, 1)
        }

    }
}

相关文章