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

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