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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章