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


Log in to reply
 

走马观花

最近的回复

  • C

    Qt for MCU需要商业授权的

    read more
  • Qt for MCUs

    搭建Qt for MCUs PC端开发环境。qt for mcus提供了一个完整的图形框架和工具包,包含了在MCUs上设计、开发和部署gui所需的一切。它允许您在裸机或实时操作系统上运行应用程序。

    先决条件

    开发主机环境支持仅限于Windows 10

    MSVC compiler v19.16 (Visual Studio 2017 15.9.9 or newer) x64

    CMake v3.13 or newer (you can install it using the Qt Online installer) x64

    使用Qt联机安装程序安装Qt for MCUs,该安装程序可通过Qt帐户下载

    安装Qt 5.14和Qt Creator 4.11 or higher

    安装链接

    › Qt: https://account.qt.io/downloads
    › CMake: https://cmake.org/download/
    › Python 2.7 32-bit: https://www.python.org/downloads/release/python-2716/
    › Arm GCC: https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnutoolchain/gnu-rm/downloads
    › J-Link Software Pack: https://www.segger.com/downloads/jlink/JLink_Windows.exe
    › J-Link OpenSDA Firmware: https://www.segger.com/downloads/jlink/OpenSDA_MIMXRT1050-EVKHyperflash
    › STM32CubeProgrammer: https://www.st.com/en/development-tools/stm32cubeprog.html
    › STM32 ST-LINK Utility: https://www.st.com/en/development-tools/stsw-link004.html​​​​​​​

    Qt Creator设置 启用Qt Creator插件 选择“帮助>关于插件”,然后从列表中选择“MCU支持(实验性)”插件,重新启动Qt Creator以应用更改
    替代文字 为MCU创建Qt工具包

    选择工具>选项>设备>MCU

    选择Qt for MCUs-Desktop 32bpp作为目标

    如果尚未设置,请提供Qt for MCUs安装目录的路径。

    单击Apply应用。

    替代文字

    替代文字
    替代文字

    注意:

    编译器要选X64,Qt版本要选64bit,CMake Tool选x64

    打开恒温器项目demo

    选择文件>打开文件或项目。。。

    打开CMakefiles.txt文件来自thermo文件夹的文件。

    选择Qt作为MCU-桌面32bpp套件。

    单击“配置项目”以完成。

    替代文字

    问题

    开发主机环境支持仅限于Windows 10

    C++编译失败,文本大字体.pixelSize.

    文本类型无法正确呈现需要复杂文本布局的unicode序列。对复杂文本使用StaticText

    read more
  • H

    hi 有问题请教你,方便加个联系方式吗

    read more
  • boost.asio是一个很棒的网络库,这回儿我也开始系统地学习起来了。想想当年接触boost,也有八年多了。这次开始接触boost,觉得既熟悉又陌生。熟悉的是小写字母+下划线的命名方式、晦涩的模板、很慢的编译速度以及较大的程序体积,陌生的是asio的各种概念:io服务、接收器、套接字等等:我之前对网络编程不是非常了解。

    于是根据我的理解,参考《Boost.Asio C++网络编程》实现了这样一个简单的客户端和服务端通信的例子,例子非常简单,还不完善,但是幸运的是,可以在本机上互通了。
    下面是客户端的代码:

    #include <iostream> #include <boost/asio.hpp> #include <boost/proto/detail/ignore_unused.hpp> using namespace std; using namespace boost::asio; using namespace boost::system; using namespace boost::proto::detail;// 提供ignore_unused方法 void writeHandler( const boost::system::error_code& ec, size_t bytesTransferred ) { if ( ec ) { cout << "Write data error, code: " << ec.value( ) << "transferred: " << bytesTransferred << endl; } else { cout << "OK! " << bytesTransferred << "bytes written. " << endl; } } int main(int argc, char *argv[]) { ignore_unused( argc ); ignore_unused( argv ); io_service service; ip::tcp::socket sock( service ); ip::tcp::endpoint ep( ip::address::from_string( "127.0.0.1" ), 6545 ); boost::system::error_code ec; sock.connect( ep, ec ); if ( ec ) { cout << "Connect error, code: " << ec.value( ) << ", We will exit." << endl; return ec.value( ); } else { char buf[1024] = "Hello world!"; sock.async_write_some( buffer( buf ), writeHandler ); sock.close( ); } return service.run( ); }

    下面是服务端的代码:

    #include <iostream> #include <boost/asio.hpp> #include <boost/proto/detail/ignore_unused.hpp> using namespace std; using namespace boost::asio; using namespace boost::system; using namespace boost::proto::detail;// 提供ignore_unused方法 void acceptHandle( const boost::system::error_code& code ) { cout << "Accepted." << endl; } int main(int argc, char *argv[]) { ignore_unused( argc ); ignore_unused( argv ); io_service service; ip::tcp::endpoint ep( ip::address::from_string( "127.0.0.1" ), 6545 ); boost::system::error_code ec; ip::tcp::socket sock( service ); ip::tcp::acceptor acceptor( service, ep ); acceptor.async_accept( sock, acceptHandle ); if ( ec ) { cout << "There is an error in server. code: " << ec.value( ) << endl; } return service.run( );// 阻塞运行 }

    运行结果是这样的:
    78448d7b-b3ae-42fc-9e2e-4dd2fbdac2c2-image.png

    我对boost.asio中几个概念的理解:

    io_service,这就是一个类似事件循环的东西,它为io设备提供服务,故名。不管是套接字、文件还是串口设备,都要使用它的服务。它的run()函数相当于启动了一个事件循环。一旦有消息了,即进行响应。这也是实现异步编程的重要基础。 socket,这个类则是套接字,可以处理TCP或者是UDP请求。有同步以及异步的处理方式,也有带异常以及不带异常的处理方式。 acceptor,接收器,仅仅是服务端使用。相当于其余框架中的listener,作接收用的。

    比较浅显,如果有不当之处,敬请指正。

    read more

关注我们

微博
QQ群