Qml 2.0 TableView with QAbstractItemModel and Context Menu

前端 未结 2 857
不思量自难忘°
不思量自难忘° 2020-12-30 17:52

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 sim

相关标签:
2条回答
  • 2020-12-30 18:29

    I created a new project based on Kuba Ober answer. I also created a commodity class called QQuickList, that inherits QList and integrates a QObjectListModel. Enjoy.

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

    0 讨论(0)
  • 2020-12-30 18:36

    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. Create a sensibly behaving ObjectListModel for storage of QObjects, usable from QML.

    2. Create a QObject-derived class that keeps your data.

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

    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.

    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.

    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.

    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.

    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)
            }
    
        }
    }
    
    0 讨论(0)
提交回复
热议问题