Using C++ Models in QML



  • 参考视频:Using C++ Models in QML {tutorial}

    这个视频比较长,40多分钟,内容也比较丰富。

    主要功能

    • 一个简单的待办事项
    • 可以增加新的待办事项,也可以删除已经完成的事情

    源码

    ToDoList.qml

    import QtQuick 2.11
    import QtQuick.Controls 2.4
    import QtQuick.Layouts 1.3
    import ToDo 1.0
    ColumnLayout{
        Frame {
            Layout.fillWidth: true
            ListView{
                id:listView
                implicitWidth: 300
                implicitHeight: 300
                anchors.fill: parent
                clip: true
    
                model: ToDoModel{
                    list:toDoList
                }
    
                delegate: RowLayout{
                    width: listView.width
                    CheckBox{
                        checked: model.done
                        onCheckedChanged: model.done = checked
                    }
                    TextField{
                        Layout.fillWidth: true
                        text: model.description
                        onEditingFinished: model.description = text
                    }
                }
            }
        }
        RowLayout{
            Button{
                text:qsTr("Add new item")
                onClicked: toDoList.appendItem()
                Layout.fillWidth: true
            }
            Button{
                text:qsTr("Remove complete items")
                onClicked: toDoList.removeCompletedItem()
                Layout.fillWidth: true
            }
        }
    }
    
    

    main.qml

    import QtQuick 2.11
    import QtQuick.Controls 2.4
    
    ApplicationWindow {
        visible: true
        width: 640
        height: 480
        title: qsTr("To Do List")
    
        ToDoList{
            anchors.centerIn: parent
        }
    }
    
    

    ToDoList.h

    #ifndef TODOLIST_H
    #define TODOLIST_H
    #include <QObject>
    #include <QVector>
    struct ToDoItem{
        bool done;
        QString description;
        bool equals(const ToDoItem& item) const {
            return this->done == item.done && this->description == item.description;
        }
    };
    class ToDoList : public QObject
    {
        Q_OBJECT
    public:
        explicit ToDoList(QObject *parent = nullptr);
        QVector<ToDoItem> items() const;
        bool setItemAt(int index, const ToDoItem &item);
    signals:
        void preItemAppended();
        void postItemAppended();
    
        void preItemRemoved(int index);
        void postItemRemoved();
    public slots:
        void appendItem();
        void removeCompletedItem();
    private:
        QVector<ToDoItem> m_items;
    };
    
    #endif // TODOLIST_H
    
    

    ToDoList.cpp

    #include "ToDoList.h"
    
    ToDoList::ToDoList(QObject *parent) : QObject(parent)
    {
        m_items.append({true,"Wash the car"});
        m_items.append({false,"Fix the sink"});
    }
    
    QVector<ToDoItem> ToDoList::items() const
    {
        return m_items;
    }
    
    bool ToDoList::setItemAt(int index, const ToDoItem &item)
    {
        if(index < 0 || index >= m_items.size()){
            return false;
        }
        const ToDoItem& oldItem = m_items[index];
        if(oldItem.equals(item)){
            return false;
        }
        m_items[index] = item;
        return true;
    }
    
    void ToDoList::appendItem()
    {
        emit preItemAppended();
        ToDoItem item;
        item.done = false;
        m_items.append(item);
        emit postItemAppended();
    }
    
    void ToDoList::removeCompletedItem()
    {
        for(int i=0;i<m_items.size();){
            if(m_items[i].done){
                emit preItemRemoved(i);
                m_items.remove(i);
                emit postItemRemoved();
            }else{
                i++;
            }
        }
    }
    
    

    ToDoModel.h

    #ifndef TODOMODEL_H
    #define TODOMODEL_H
    
    #include <QAbstractListModel>
    class ToDoList;
    
    class ToDoModel : public QAbstractListModel
    {
        Q_OBJECT
        Q_PROPERTY(ToDoList *list READ list WRITE setList)
    public:
        explicit ToDoModel(QObject *parent = nullptr);
        enum{
            DoneRole = Qt::UserRole,
            DescriptionRole
        };
        // Basic functionality:
        int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    
        QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
    
        // Editable:
        bool setData(const QModelIndex &index, const QVariant &value,
                     int role = Qt::EditRole) override;
    
        Qt::ItemFlags flags(const QModelIndex& index) const override;
    
    private:
        ToDoList* m_list;
        // QAbstractItemModel interface
    public:
        virtual QHash<int, QByteArray> roleNames() const override;
        ToDoList *list() const;
        void setList(ToDoList *list);
    };
    
    #endif // TODOMODEL_H
    
    

    ToDoModel.cpp

    #include "ToDoModel.h"
    #include "ToDoList.h"
    ToDoModel::ToDoModel(QObject *parent)
        : QAbstractListModel(parent),
          m_list(nullptr)
    {
    }
    
    int ToDoModel::rowCount(const QModelIndex &parent) const
    {
        // For list models only the root node (an invalid parent) should return the list's size. For all
        // other (valid) parents, rowCount() should return 0 so that it does not become a tree model.
        if (parent.isValid() || m_list == nullptr)
            return 0;
    
        return m_list->items().size();
    }
    
    QVariant ToDoModel::data(const QModelIndex &index, int role) const
    {
        if (!index.isValid() || m_list==nullptr)
            return QVariant();
        auto item = m_list->items().at(index.row());
        switch (role) {
        case DoneRole:{
            return QVariant(item.done);
        }
        case DescriptionRole:{
            return QVariant(item.description);
        }
        }
        return QVariant();
    }
    
    bool ToDoModel::setData(const QModelIndex &index, const QVariant &value, int role)
    {
        if(m_list == nullptr){
            return false;
        }
        auto item = m_list->items().at(index.row());
    
        switch (role) {
        case DoneRole:{
            item.done = value.toBool();
            break;
        }
        case DescriptionRole:{
            item.description = value.toString();
            break;
        }
        }
        if (m_list->setItemAt(index.row(),item)) {
            emit dataChanged(index, index, QVector<int>() << role);
            return true;
        }
        return false;
    }
    
    Qt::ItemFlags ToDoModel::flags(const QModelIndex &index) const
    {
        if (!index.isValid())
            return Qt::NoItemFlags;
    
        return Qt::ItemIsEditable;
    }
    
    ToDoList *ToDoModel::list() const
    {
        return m_list;
    }
    
    void ToDoModel::setList(ToDoList *list)
    {
        beginResetModel();
        if(m_list != nullptr){
            m_list->disconnect(this);
        }
        m_list = list;
        if(m_list!=nullptr){
            connect(m_list,&ToDoList::preItemAppended,[=](){
                const int index = m_list->items().size();
                beginInsertRows(QModelIndex(),index,index);
            });
            connect(m_list,&ToDoList::postItemAppended,[=](){
                endInsertRows();
            });
            connect(m_list,&ToDoList::preItemRemoved,[=](int index){
                beginRemoveRows(QModelIndex(),index,index);
            });
            connect(m_list,&ToDoList::postItemRemoved,[=](){
                endRemoveRows();
            });
        }
        endResetModel();
    }
    
    QHash<int, QByteArray> ToDoModel::roleNames() const
    {
        QHash<int, QByteArray> names;
        names[DoneRole] = "done";
        names[DescriptionRole] = "description";
        return names;
    }
    
    

    main.cpp

    #include <QGuiApplication>
    #include <QQmlApplicationEngine>
    #include <QQmlContext>
    #include "ToDoModel.h"
    #include "ToDoList.h"
    int main(int argc, char *argv[])
    {
        QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    
        QGuiApplication app(argc, argv);
        qmlRegisterType<ToDoModel>("ToDo",1,0,"ToDoModel");
        qmlRegisterUncreatableType<ToDoList>("ToDo",1,0,"ToDoList",
            QStringLiteral("ToDoList should not be created in QMl"));
        ToDoList toDoList;
        QQmlApplicationEngine engine;
        engine.rootContext()->setContextProperty("toDoList",&toDoList);
        engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
        if (engine.rootObjects().isEmpty())
            return -1;
    
        return app.exec();
    }
    
    

    截图

    最后的运行效果

    image

    自己做的时候留下的部分截图

    image
    image
    image

    思考与总结

    虽然是一个简单的ToDo-List,却是目前做过的最难的一个qml Demo,牵涉到的Qt相关的知识非常多。

    • 在Layout中使用fillWidth: true使控件填充满

    不同于css中设置display的方式,qml的layout采用这种方式使得控件填充满layout,同时还可以实现对其,如这个例子中的两个Button的宽度和ListView宽度一样

    • Model-View-Delegate的使用

    在我的使用经历中,Qt的这个模式是一个很难的点,我看了很多视频教程,也尝试着自己去写基于这个模式的Demo,往往是失败告终。在这次的ToDo-List中,视频非常详细的介绍了从简单的model,一步一步到复杂的model,如何编写,如何使用。我认为是这个视频对我最大的帮助之一。

    • 使用信号槽实现的双向绑定

    我开始只能把model的数据展示在listview上,如果我去勾选框或者去编辑,model是感应不到的,是不会改变的。后来是onCheckedChanged和onEditingFinished两个槽函数,把改变的数据赋给模型,才完成这个双向绑定。

    • 在QML是用C++编写的models

    本来这是这个视频的重点,结果发现,在最后使用的时候,非常简单,两个注册函数就搞定了。反倒是把大量的精力花在了如何编写model上。在编辑器中,qml对于C++槽函数的智能提示也做得非常的好,很方便。
    GitHub: https://github.com/PikachuHy/qml-demo/tree/master/using-cpp-models-in-qml


 

走马观花~

最近的回复

  • 很棒!求软件,我要摆在我的桌面上。

    阅读更多
  • 这个很有意思 ^_^

    阅读更多
  • 思路:
    1.把官方时钟demo改了,加了秒针,加了壁纸,加了小萝莉
    2.QWindow窗口嵌入到桌面

    0_1533891238039_20180810_164211.gif

    改版

    去掉秒针,把小萝莉正过来,沿着表盘走

    0_1533893719543_20180810_173309.gif

    源代码

    Fork me on Gitee

    阅读更多

关注我们

微博
QQ群











召唤伊斯特瓦尔