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

时间:2013-10-11 18:11:44

标签: c++ qt menu qml qt5

我从Qml 2.0控件TableView连接到QAbstractItemModel。我想创建一个可以更改属性的上下文菜单,或者只是从模型中调用具体对象的方法。

示例:

QAbstractItemModel有一个std::vector<Person>Person有一个方法alter()进行一些更改(任何更改,哪些更改都无关紧要,重点是我们可以调用该方法)。

如果右键单击该行,则会显示一个菜单Alter

我能找到的就是如何制作菜单。

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

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

2 个答案:

答案 0 :(得分:5)

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

以下代码演示了如何:

  1. 为存储ObjectListModel创建一个明智的QObjects行为,可以从QML中使用。

  2. 创建一个保存数据的QObject派生类。

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

  4. 可以将模型设置为自动通知有关包含的QObject的属性的更改。如果由批量更改(例如在循环中完成)产生的此类通知将合并,并作为单个dataChanged事件发送。

    不幸的是,QObject的用户属性没有特殊含义 - 您仍然需要使用.property选择器来访问它。

    可以直接观察模型的正确行为,因为有两个链接到同一模型的列表 - 它们更好地展示了相同的东西。

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

    如果QObject的存储空间过高,则替代模型实现可以在运行中为POD类型创建QObject适配器。

    screenshot

    <强>的main.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"
    

    <强> main.qrc

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

    <强> main.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)
            }
    
        }
    }
    

答案 1 :(得分:0)

我创建了一个基于Kuba Ober答案的新项目。我还创建了一个名为QQuickList的商品类,它继承了QList并集成了一个QObjectListModel。享受。

https://github.com/Murazaki/QObjectListModel-QQuickList