Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. Saving ListModel to file
Qt 6.11 is out! See what's new in the release blog

Saving ListModel to file

Scheduled Pinned Locked Moved Solved General and Desktop
5 Posts 2 Posters 1.7k Views 2 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • K Offline
    K Offline
    keesaev
    wrote on last edited by
    #1

    Hello everyone,
    I am looking for approaches to saving and loading ListModel to any kind of file. Currently I have a ListModel located in qml, which gets data from c++ signals as custom qml types.
    I thought of moving ListModel to c++ by inheriting QAbstractListModel, but then I won't be able to write custom class objects to file using QDataStream because it doesn't allow classes inherited from QObject. My custom class needs to inherit QObject so I can register it as qml type and use it on qml side.
    I am new to the subject, so the question may sound silly, but what are the approaches to solving my problem?

    1 Reply Last reply
    0
    • jeremy_kJ Offline
      jeremy_kJ Offline
      jeremy_k
      wrote on last edited by
      #2

      Can you provide some code to explain the problem?

      The answer generally is to save and restore the basic data types that make up the entries in the model, rather than the layout of the objects in memory. This might be through providing a function in the model that exports or imports state as a list. In cases where adding to the model implementation isn't possible, walking it with QAbstractItemModel::data() or ListMode.get() can work. For large data sets, fetching each entry in sequence can be painfully slow.

      Going into more detail is easier with an implementation to discuss.

      Asking a question about code? http://eel.is/iso-c++/testcase/

      1 Reply Last reply
      2
      • K Offline
        K Offline
        keesaev
        wrote on last edited by
        #3

        Thanks for reply!
        Let me provide some code:
        PacketData is a class that represents a single row in ListView:

        class PacketData : public QObject
        {
            Q_OBJECT
            QML_ELEMENT
        public:
            explicit PacketData(QObject *parent = nullptr);
        
            int number;
            QString sourceIp, destIp, protocol, length, fullData;
            QTime timestamp;
            Q_INVOKABLE int getNumber(){ return number; }
            Q_INVOKABLE QTime getTimestamp(){ return timestamp; }
            Q_INVOKABLE QString getSourceIp(){ return sourceIp; }
            Q_INVOKABLE QString getDestIp(){ return destIp; }
            Q_INVOKABLE QString getProtocol(){ return protocol; }
            Q_INVOKABLE QString getLength(){ return length; }
            Q_INVOKABLE QString getFullData() { return fullData; }
        };
        

        There's a class called SnifferWrapper that creates an object of PacketData and emits a signal packetDeserialized, I don't think that implementation of this class is important, I will just show how I emit and connect signal:

        // signal:
        void packetDeserialized(PacketData *packet);
        ...
        // creating an object of PacketData:
            PacketData *packet = new PacketData();
        // filling it with data
        ...
        // emitting signal
            emit packetDeserialized(packet);
            packet->deleteLater(); // calling deleteLater, because later in PacketsView I will copy members of this obect to listModel
        ...
        // somewhere in main.qml:
            SnifferWrapper{
                id: snifferWrapper
        
        // connecting to addPacketToView slot
                onPacketDeserialized: {
                    packetsView.addPacketToView(packet)
                }
            }
        

        Then I have PacketsView.qml file - here I create ListModel and ListView:

            ListModel{
                id: listModel
            }
        ...
        // This function acts as a slot for packetDeserialized signal: 
            function addPacketToView(packet){
                listModel.append({
                            number: packet.getNumber(),
                            timestamp: Qt.formatTime(packet.getTimestamp(), "hh:mm:ss"),
                            source: packet.getSourceIp(),
                            destination: packet.getDestIp(),
                            protocol: packet.getProtocol(),
                            length: packet.getLength(),
                            fullData: packet.getFullData()
                        });
            }
        ...
        // Later in code there's a ListView, but I don't think it's important for the topic
        

        There's a naming mistake as function addPacketToView actually adds packet to model, please don't get confused.

        How exactly should I implement model in c++ and then be able to save it? For example, if I have a class with a list:

        QList<PacketData *> list
        

        I won't be able to use QDataStream because PacketData inherits QObject. Also if I make PacketData class not inherit QObject, I won't be able to register it as qml type and use it in qml.

        I might be missing or not understanding something here. Also sorry for bad english

        1 Reply Last reply
        0
        • jeremy_kJ Offline
          jeremy_kJ Offline
          jeremy_k
          wrote on last edited by jeremy_k
          #4

          Seeing the code helps a lot. Unless there is a significant portion missing, doing it this way is adding code complexity and copying overhead without a benefit. In particular, there's no need for the intermediate packet QObject representation.

          If this was a QML only project, using ListModel and copying data into it might be reasonable. As there's already a C++ portion, adding a QAbstractListModel shouldn't be difficult.

          Here's a rough sketch of a C++ model. It won't compile, or perform even basic error checking, but will hopefully convey the general idea.

          class PacketModel : public QAbstractListModel {
              Q_OBJECT
              QVector<packet> m_packets;
              enum class roles { NumberRole = Qt::UserRole, LengthRole };
          
          public:
              struct packet {
                  int number;
                  int length;
              };
          
              // Load packets into an existing model
              void restorePackets(QVector<packet> packets) {
                  beginResetModel();
                  m_packets = packets;
                  endResetModel();
              }
          
              // Connect to a signal that delivers packet data, or directly invoked from the UI thread.
              void appendPacket(packet p) {
                  beginInsertRows(QModelIndex(), packets.size(), packets.size());
                  m_packets.push_back(p);
                  endInsertRows();
              }
          
          // QAbstractListModel functions
              
              int QAbstractItemModel::rowCount(const QModelIndex &parent) const override {
                  return m_packets.length();
              }
          
              QVariant data(const QModelIndex &index, int role) override {
                  auto packetsIndex = index.row();
                  const packet &p = m_packets.at(packetsIndex);
                  switch (role) {
                  case roles.NumberRole:
                      return p.number;
                  case roles.LengthRole;
                      return p.length;
                  default:
                      return QAbstractListModel::data(index, role);
                  }
              }
          
              QHash<int, QByteArray> QAbstractItemModel::roleNames() const override {
                  return QHash<int, QByteArray> { {roles.NumberRole, "number"}, 
                                                  {roles.LengthRole, "length"} };
              }
          };
          

          listview.qml:

          ListView {
              model: packetModel
              delegate: Text {
                  text: "packet number " + model.number + " with length " model.length
              }
          }
          

          Asking a question about code? http://eel.is/iso-c++/testcase/

          K 1 Reply Last reply
          2
          • jeremy_kJ jeremy_k

            Seeing the code helps a lot. Unless there is a significant portion missing, doing it this way is adding code complexity and copying overhead without a benefit. In particular, there's no need for the intermediate packet QObject representation.

            If this was a QML only project, using ListModel and copying data into it might be reasonable. As there's already a C++ portion, adding a QAbstractListModel shouldn't be difficult.

            Here's a rough sketch of a C++ model. It won't compile, or perform even basic error checking, but will hopefully convey the general idea.

            class PacketModel : public QAbstractListModel {
                Q_OBJECT
                QVector<packet> m_packets;
                enum class roles { NumberRole = Qt::UserRole, LengthRole };
            
            public:
                struct packet {
                    int number;
                    int length;
                };
            
                // Load packets into an existing model
                void restorePackets(QVector<packet> packets) {
                    beginResetModel();
                    m_packets = packets;
                    endResetModel();
                }
            
                // Connect to a signal that delivers packet data, or directly invoked from the UI thread.
                void appendPacket(packet p) {
                    beginInsertRows(QModelIndex(), packets.size(), packets.size());
                    m_packets.push_back(p);
                    endInsertRows();
                }
            
            // QAbstractListModel functions
                
                int QAbstractItemModel::rowCount(const QModelIndex &parent) const override {
                    return m_packets.length();
                }
            
                QVariant data(const QModelIndex &index, int role) override {
                    auto packetsIndex = index.row();
                    const packet &p = m_packets.at(packetsIndex);
                    switch (role) {
                    case roles.NumberRole:
                        return p.number;
                    case roles.LengthRole;
                        return p.length;
                    default:
                        return QAbstractListModel::data(index, role);
                    }
                }
            
                QHash<int, QByteArray> QAbstractItemModel::roleNames() const override {
                    return QHash<int, QByteArray> { {roles.NumberRole, "number"}, 
                                                    {roles.LengthRole, "length"} };
                }
            };
            

            listview.qml:

            ListView {
                model: packetModel
                delegate: Text {
                    text: "packet number " + model.number + " with length " model.length
                }
            }
            
            K Offline
            K Offline
            keesaev
            wrote on last edited by
            #5

            @jeremy_k
            Thank you for such a detailed reply!
            You definitely solved my problem. Let me leave minimal working code for someone who has same problem:

            Packet class:

            class Packet
            {
            public:
                Packet();
                int number, length;
            };
            

            PacketModel class:

            #include <QAbstractListModel>
            #include <QObject>
            #include <QDataStream>
            #include <QFile>
            #include <packet.h>
            
            class PacketModel : public QAbstractListModel
            {
                Q_OBJECT
                QList<Packet> m_packets;
                enum roles { NumberRole = Qt::UserRole, LengthRole };
            public:
                PacketModel();
            
                int rowCount(const QModelIndex &parent) const {
                    return m_packets.length();
                }
            
                QVariant data(const QModelIndex &index, int role) const {
                    auto packetsIndex = index.row();
                    const Packet &p = m_packets.at(packetsIndex);
                    switch(role){
                        case NumberRole:
                            return p.number;
                        case LengthRole:
                            return p.length;
                        default:
                            return data(index, role);
                    }
                }
            
                QHash<int, QByteArray> roleNames() const{
                    return QHash<int, QByteArray> {
                        { NumberRole, "number"},
                        { LengthRole, "length"}
                    };
                }
            
                Q_INVOKABLE void add(){
                    // Temporary method simulating insertion from other class
                    Packet p;
                    p.number = 1;
                    p.length = 2;
                    beginInsertRows(QModelIndex(), m_packets.size(), m_packets.size());
                    m_packets.push_back(p);
                    endInsertRows();
                }
            
                Q_INVOKABLE void saveModel(){
                    QFile file("snifff.txt");
                    file.open(QIODevice::WriteOnly);
                    QDataStream stream(&file);
                    for(auto i : m_packets){
                        stream << i.number << i.length;
                    }
                    file.close();
                }
            
                Q_INVOKABLE void loadModel(){
                    QFile file("snifff.txt");
                    file.open(QIODevice::ReadOnly);
                    QDataStream stream(&file);
            
                    beginResetModel();
                    m_packets.clear();
            
                    while(!stream.atEnd()){
                        Packet p;
                        stream >> p.number >> p.length;
                        m_packets.push_back(p);
                    }
            
                    endResetModel();
                    file.close();
                }
            };
            

            PacketModel class needs to be registered as qml type as well:

                qmlRegisterType<PacketModel>("PacketModel", 1, 0, "PacketModel");
            

            Using model in qml:

            import QtQuick 2.12
            import QtQuick.Window 2.12
            import QtQuick.Controls 2.12
            import PacketModel 1.0
            
            Window {
                width: 640
                height: 480
                visible: true
                title: qsTr("Hello World")
            
                ListView{
                    width: parent.width
                    height: parent.height
                    id: listView
            
                    model: packetModel
            
                    delegate: Text{
                        text: number + ", " + length
                    }
                }
            
                PacketModel{
                    id: packetModel
                }
            
                Button {
                    text: "Add"
                    anchors.right: parent.right
                    onClicked: {
                        packetModel.add()
                    }
                }
            
                Button {
                    text: "Save"
                    anchors.bottom: parent.bottom
                    anchors.left: parent.left
                    onClicked: {
                        packetModel.saveModel()
                    }
                }
            
                Button{
                    text: "Load"
                    anchors.right: parent.right
                    anchors.bottom: parent.bottom
                    onClicked: {
                        packetModel.loadModel()
                    }
                }
            }
            
            1 Reply Last reply
            1

            • Login

            • Login or register to search.
            • First post
              Last post
            0
            • Categories
            • Recent
            • Tags
            • Popular
            • Users
            • Groups
            • Search
            • Get Qt Extensions
            • Unsolved