Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. QML and Qt Quick
  4. QML ListView and QAbstractListModel and prefetch part of the data from 10000 records
QtWS25 Last Chance

QML ListView and QAbstractListModel and prefetch part of the data from 10000 records

Scheduled Pinned Locked Moved Solved QML and Qt Quick
24 Posts 3 Posters 3.3k Views
  • 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.
  • F Offline
    F Offline
    Frenk21
    wrote on last edited by
    #1

    Hello to all,

    I am new in Qt and am implementing an application using Qt v5.15.2 that would show patients from some hospital on the tablet. On the table I can show only 10 patients, but the hospital has huge number of this records.

    I added a reader in c++ to read the data (we only read the data), then I am using QAbstractListModel with fetchMore option. On the UI side I only have a ListView with a delegate to show the patient name, birthday, ... Everything is working fine, but what I see is that when I scroll down, the fetchMode is being called and another 10 patients are loaded. Which is ok.

    The problem is if I swipe the finger on the tablet stronger to faster achieve the end of the list I then need to wait for all of them to be loaded.

    Second problem is that if I scroll up, I also dont know the index so I dont know how to load the proper data.

    What I want to achieve it to have cached only 10-20 patients (sliding window), but for that I would need to get the list view moving position so that I would know in which direction scrolling is being done and also for how many steps or how fast so that I could load proper data. Any ideas? Idea from my side was to maybe use hidden scrollbar, but I don't know if this is a good solution.

    Thank you very much for the answers, Frenk

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

      QAbstractItemModel::fetchMore() is indeed a limited tool.

      Another option for incremental loading is to report the row count as the full size, but delay loading the values. When QAbstractItemModel::data() is called for the first time, return a place holder value, and the emit dataChanged() when the real value can be fetched. A sliding window could be managed by also retrieving rows before or after the requested index and releasing data outside the window. Calling dataChanged() on the released data might be avoidable.

      Is memory so constrained, that keeping more than 20 records at a time is a problem?

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

      F 1 Reply Last reply
      0
      • jeremy_kJ jeremy_k

        QAbstractItemModel::fetchMore() is indeed a limited tool.

        Another option for incremental loading is to report the row count as the full size, but delay loading the values. When QAbstractItemModel::data() is called for the first time, return a place holder value, and the emit dataChanged() when the real value can be fetched. A sliding window could be managed by also retrieving rows before or after the requested index and releasing data outside the window. Calling dataChanged() on the released data might be avoidable.

        Is memory so constrained, that keeping more than 20 records at a time is a problem?

        F Offline
        F Offline
        Frenk21
        wrote on last edited by
        #3

        @jeremy_k

        Thank you for the answer.

        I see. Too bad that the method data() is const otherwise I could read it here on demand :-)

        I was thinking more in the direction to always show 10-20 patients and use ListView of this size, so would never add new rows. I would just internally shuffle the data according to the internal index. But how can I get scrolling direction of the List View? I am now reading the Flickable description and it seems I could use property verticalVelocity. Or is there a better way to get ListView scrolling index? Or am I thinking it wrongly?

        I could load more records, but not 10000 as this is too much and as I can show only 10 I just put the sliding window size to 20, I could also set it to 100 or at least 500 (which would be a maximum).

        jeremy_kJ 1 Reply Last reply
        0
        • F Frenk21

          @jeremy_k

          Thank you for the answer.

          I see. Too bad that the method data() is const otherwise I could read it here on demand :-)

          I was thinking more in the direction to always show 10-20 patients and use ListView of this size, so would never add new rows. I would just internally shuffle the data according to the internal index. But how can I get scrolling direction of the List View? I am now reading the Flickable description and it seems I could use property verticalVelocity. Or is there a better way to get ListView scrolling index? Or am I thinking it wrongly?

          I could load more records, but not 10000 as this is too much and as I can show only 10 I just put the sliding window size to 20, I could also set it to 100 or at least 500 (which would be a maximum).

          jeremy_kJ Offline
          jeremy_kJ Offline
          jeremy_k
          wrote on last edited by
          #4

          @Frenk21 said in QML ListView and QAbstractListModel and prefetch part of the data from 10000 records:

          @jeremy_k

          Thank you for the answer.

          I see. Too bad that the method data() is const otherwise I could read it here on demand :-)

          mutable exists for a reason!
          As a practical matter, data() is called frequently. Leveraging it to populate a cache does deserve to be carefully considered.

          I was thinking more in the direction to always show 10-20 patients and use ListView of this size, so would never add new rows. I would just internally shuffle the data according to the internal index. But how can I get scrolling direction of the List View? I am now reading the Flickable description and it seems I could use property verticalVelocity. Or is there a better way to get ListView scrolling index? Or am I thinking it wrongly?

          Perhaps you would be better served by a static set of delegates and and a next page/previous page button or similar. The model/view relation is intentionally decoupled. A single model might serve multiple views, or be written with no knowledge of its use. With full control of the use case from data to display, MVC overhead might not be worthwhile.

          I could load more records, but not 10000 as this is too much and as I can show only 10 I just put the sliding window size to 20, I could also set it to 100 or at least 500 (which would be a maximum).

          You know your data. I was toying with some numbers. 10,000 records at 1 byte each is ~ 10 KB. Not a big deal. 10 bytes each is 100 KB. Still nothing as a one-off in a system using Qt Quick for the UI. 100 bytes is 1 MB. Is this a low memory embedded system? 1000 bytes is ~ 10 MB. Still not a big deal for a desktop system with relatively localized access. A memory mapped file could work. Perhaps each record could be limited to essential information plus an external identifier for accessing extended information.

          Sorting and searching is a possible use case for loading more records. For that, you either need to load everything, or have a backend do it.

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

          F 1 Reply Last reply
          0
          • jeremy_kJ jeremy_k

            @Frenk21 said in QML ListView and QAbstractListModel and prefetch part of the data from 10000 records:

            @jeremy_k

            Thank you for the answer.

            I see. Too bad that the method data() is const otherwise I could read it here on demand :-)

            mutable exists for a reason!
            As a practical matter, data() is called frequently. Leveraging it to populate a cache does deserve to be carefully considered.

            I was thinking more in the direction to always show 10-20 patients and use ListView of this size, so would never add new rows. I would just internally shuffle the data according to the internal index. But how can I get scrolling direction of the List View? I am now reading the Flickable description and it seems I could use property verticalVelocity. Or is there a better way to get ListView scrolling index? Or am I thinking it wrongly?

            Perhaps you would be better served by a static set of delegates and and a next page/previous page button or similar. The model/view relation is intentionally decoupled. A single model might serve multiple views, or be written with no knowledge of its use. With full control of the use case from data to display, MVC overhead might not be worthwhile.

            I could load more records, but not 10000 as this is too much and as I can show only 10 I just put the sliding window size to 20, I could also set it to 100 or at least 500 (which would be a maximum).

            You know your data. I was toying with some numbers. 10,000 records at 1 byte each is ~ 10 KB. Not a big deal. 10 bytes each is 100 KB. Still nothing as a one-off in a system using Qt Quick for the UI. 100 bytes is 1 MB. Is this a low memory embedded system? 1000 bytes is ~ 10 MB. Still not a big deal for a desktop system with relatively localized access. A memory mapped file could work. Perhaps each record could be limited to essential information plus an external identifier for accessing extended information.

            Sorting and searching is a possible use case for loading more records. For that, you either need to load everything, or have a backend do it.

            F Offline
            F Offline
            Frenk21
            wrote on last edited by
            #5

            @jeremy_k

            Thank you again for your answer.

            I am not very keen in using the mutable, only if I really have to do it.

            My mistake, forgot to mention, that size of one record is around 100kB and the device has total memory of 1 GB.

            And this model will be used only for reading by one view only.

            Sadly I cannot use any buttons there as the customer wants to have swipe option. Even if I did mention them that it can take time if the user want to go to the end of the list.

            the fetchMore approach would be really helpful if I would get an index which record is trying to load. In this way I would know which part I need to load. At the moment I don't need sorting, searching, just simple show, scroll and lazy loading.

            jeremy_kJ 1 Reply Last reply
            0
            • F Frenk21

              @jeremy_k

              Thank you again for your answer.

              I am not very keen in using the mutable, only if I really have to do it.

              My mistake, forgot to mention, that size of one record is around 100kB and the device has total memory of 1 GB.

              And this model will be used only for reading by one view only.

              Sadly I cannot use any buttons there as the customer wants to have swipe option. Even if I did mention them that it can take time if the user want to go to the end of the list.

              the fetchMore approach would be really helpful if I would get an index which record is trying to load. In this way I would know which part I need to load. At the moment I don't need sorting, searching, just simple show, scroll and lazy loading.

              jeremy_kJ Offline
              jeremy_kJ Offline
              jeremy_k
              wrote on last edited by
              #6

              The size of those records changes my mental model. I had been picturing a name and a few lines of notes as text.

              You do have the option of implementing a new view class with added signals, or deriving from ListView's private C++ interface. It may also be necessary to deal with some of the model adapter code. The danger is that the interface can change between patch releases.

              Using Flickable.verticalVelocity as you mentioned earlier could be useful to estimate which indexes to fetch, along with ListView.indexAt() and the height of a delegate instance. It may be necessary to adjust the scrolling to account for instances being added or removed, if you choose to not use placeholder content.

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

              F 1 Reply Last reply
              0
              • jeremy_kJ jeremy_k

                The size of those records changes my mental model. I had been picturing a name and a few lines of notes as text.

                You do have the option of implementing a new view class with added signals, or deriving from ListView's private C++ interface. It may also be necessary to deal with some of the model adapter code. The danger is that the interface can change between patch releases.

                Using Flickable.verticalVelocity as you mentioned earlier could be useful to estimate which indexes to fetch, along with ListView.indexAt() and the height of a delegate instance. It may be necessary to adjust the scrolling to account for instances being added or removed, if you choose to not use placeholder content.

                F Offline
                F Offline
                Frenk21
                wrote on last edited by
                #7

                @jeremy_k

                Uh, then I should probably avoid use model adapter code as now we are using qt v5.15.2 and the plan in few months is to go to the qt v6.2.

                The plan maybe was to always show 10 patients and when listview moves up or down I just internally replace the content, so in this case I dont need a placeholder. I am worried a little if there will be too much interfering of the scrolling. Now everything is done internally in Qt (List View is being scrolled and it automatically fetch new data to show it). In case where I will handle verticalVelocity, indexAt() I would probably need to issue dataChanged(). So can it happen that listview will refresh itself 2 times (scrolling and then manually doing dataChanged())?

                ALSO Thank you very much for the hint on using the indexAt(), I somehow overlooked this method.

                Well as a placeholder I could use the list where I store ID to all of the bigger records, something like QVector<int> IDs. As I first need to fetch IDs for all of the available records. After that I could in data() read the requested data (the transfer should be very fast as the plan is to use IPC). But then I need to set data() as mutable :-(.

                Thanks again for you time and all the helpful answers, Frenk

                1 Reply Last reply
                0
                • GrecKoG Offline
                  GrecKoG Offline
                  GrecKo
                  Qt Champions 2018
                  wrote on last edited by
                  #8

                  Could you implement lazy loading in the delegate itself?
                  If you can fetch the count and metadata of your items without their expensive data it should be possible.

                  model: yourModel
                  delegate: Column {
                        id: lazyDelegate
                        property QtObject dataLoader: yourModel.dataLoader(model.uuid)
                        Label { text: model.title }
                        ProgressBar { value: lazyDelegate.dataLoader.progress }
                        Loader {
                             active: lazyDelegate.dataLoader.loaded
                            sourceComponent: YourActualDataDelegate {
                                expensiveData: lazyDelegate.dataLoader.data
                           }
                        }
                  }
                  

                  If you don't give a parent to the objects you return from dataLoader, the QML engine will handle destroying them when your delegates get destroyed.

                  You can configure how many delegates are instantiated with ListView cacheBuffer property.

                  F 1 Reply Last reply
                  1
                  • GrecKoG GrecKo

                    Could you implement lazy loading in the delegate itself?
                    If you can fetch the count and metadata of your items without their expensive data it should be possible.

                    model: yourModel
                    delegate: Column {
                          id: lazyDelegate
                          property QtObject dataLoader: yourModel.dataLoader(model.uuid)
                          Label { text: model.title }
                          ProgressBar { value: lazyDelegate.dataLoader.progress }
                          Loader {
                               active: lazyDelegate.dataLoader.loaded
                              sourceComponent: YourActualDataDelegate {
                                  expensiveData: lazyDelegate.dataLoader.data
                             }
                          }
                    }
                    

                    If you don't give a parent to the objects you return from dataLoader, the QML engine will handle destroying them when your delegates get destroyed.

                    You can configure how many delegates are instantiated with ListView cacheBuffer property.

                    F Offline
                    F Offline
                    Frenk21
                    wrote on last edited by
                    #9

                    @GrecKo
                    That looks really interesting, thanks for the suggestion. Few things are not clear to me, so maybe you can explain it further:

                    • This dataLoader() is a aka INVOKE method implemented from my side to load new data? And it should return QtObject?
                    • sourceComponent: YourActualDataDelegate ?

                    Is there any difference between cacheBuffer and reuseItems, as I use the latest one?

                    Just for the info I now implemented it using ListView and onContentYChanged and getIndexAt() + added INVOKE method to the model, but this is probably not so nice solution as you have suggested above.

                    Thanks, Frenk

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

                      @GrecKo's solution brings to mind a variation. The idea is the same, but instead of using a Q_INVOKABLE to fetch an object with properties representing the model, it could be a QObject-derived QML element.

                      model: yourModel
                      delegate: Column {
                      
                          DataLoader {
                              id: dataLoader
                              rowId: modex.uuid
                          }
                      
                          Text {
                              text: dataLoader.name
                          }
                          Image {
                              source: dataLoader.picture
                          }
                      }
                      

                      DataLoader is roughly:

                      class DataLoader : public QObject {
                        Q_OBJECT
                        Q_PROPERTY(int rowId WRITE setRowId)
                        Q_PROPERTY(QString name MEMBER NOTIFY nameChanged)
                        Q_PROPERTY(QImage picture MEMBER NOTIFY pictureChanged)
                      
                      public:
                        QString name = "Loading";
                        QImage picture = QImage("defaultpicture.png");
                      
                         void setRowId(int id) {
                          reply = getRow(id);
                          connect(reply, &Reply::finished, this, &DataLoader::setData);
                        }
                      
                        void setData(Reply *reply) {
                          this->picture = reply->picture;
                          this->name = reply->name;
                          delete request;
                          emit pictureChanged();
                          emit nameChanged();
                        }
                      };
                      

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

                      1 Reply Last reply
                      0
                      • F Frenk21

                        @GrecKo
                        That looks really interesting, thanks for the suggestion. Few things are not clear to me, so maybe you can explain it further:

                        • This dataLoader() is a aka INVOKE method implemented from my side to load new data? And it should return QtObject?
                        • sourceComponent: YourActualDataDelegate ?

                        Is there any difference between cacheBuffer and reuseItems, as I use the latest one?

                        Just for the info I now implemented it using ListView and onContentYChanged and getIndexAt() + added INVOKE method to the model, but this is probably not so nice solution as you have suggested above.

                        Thanks, Frenk

                        jeremy_kJ Offline
                        jeremy_kJ Offline
                        jeremy_k
                        wrote on last edited by
                        #11

                        @Frenk21 said in QML ListView and QAbstractListModel and prefetch part of the data from 10000 records:

                        Is there any difference between cacheBuffer and reuseItems, as I use the latest one?

                        Both are strategies for improving scrolling performance.

                        cacheBuffer tells the list view to keep delegates that are outside the visible area, effectively treating the visible area as larger by the specified number of pixels. Once a delegate instance is assigned to a model index, it will not be reused.

                        reuseItems takes delegates that are about to be destroyed, and instead keeps them in a pool for reuse by overwriting the relevant properties. A delegate instance may or may not be reassigned to the same model index.

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

                        F 1 Reply Last reply
                        0
                        • jeremy_kJ jeremy_k

                          @Frenk21 said in QML ListView and QAbstractListModel and prefetch part of the data from 10000 records:

                          Is there any difference between cacheBuffer and reuseItems, as I use the latest one?

                          Both are strategies for improving scrolling performance.

                          cacheBuffer tells the list view to keep delegates that are outside the visible area, effectively treating the visible area as larger by the specified number of pixels. Once a delegate instance is assigned to a model index, it will not be reused.

                          reuseItems takes delegates that are about to be destroyed, and instead keeps them in a pool for reuse by overwriting the relevant properties. A delegate instance may or may not be reassigned to the same model index.

                          F Offline
                          F Offline
                          Frenk21
                          wrote on last edited by
                          #12

                          @jeremy_k

                          Wow, thanks I did not know you can also do it like this. Will try it today or over the weekend and let you know about the results.

                          I just see that I will have some duplicate properties as the currently used model has the same or I can even split data parts :-)

                          Great idea and useful example, thanks to both.

                          1 Reply Last reply
                          0
                          • F Offline
                            F Offline
                            Frenk21
                            wrote on last edited by Frenk21
                            #13

                            I am trying to achive this, but no luck at the moment.

                            @jeremy_k I think I cant use DataLoader as you have suggested because my loader constructor depends on IPC connection class. So I have to use qmlRegisterUncreatedType in the QML. Or I am maybe missing something? I cant expose the IPC connection class to the QML.

                            Just to mention, that on the windows I am using QT socket and TCP/IP for testing between UI and backend. And here it takes almost 1 second to read the data, because the backend is configured to send packets of 1000 bytes. So need to add some delay here.

                            So I am going with the @Grecko approach.

                            My structure is now something like that:

                            PatientsModel holding list of all patients, but just their Ids + INVOKE method to get the PatientDetailLoader which loads additional data for the patient. Here I need a list of loaders for each id? Both are using PatientDataReader which has a FSM (lock db, get data, unlock db) using the IPC connection to read the data.

                            The patient data structure is something like that:

                            struct patientData {
                                data fields
                                array[5] specific diagnostics. // For this reason I use a submodel bellow.
                            }
                            

                            On the QML side I have a ListView with above model and a delegate:
                            ```
                            ListView {
                            id: model_patientList

                                    anchors.fill:                               parent
                                    spacing:                                    AppSettings.scaleSize(32)
                                    boundsBehavior:                             Flickable.StopAtBounds
                                    boundsMovement:                             Flickable.StopAtBounds
                                    clip:                                       true
                            
                                    model:                                      listModel
                            
                                    delegate: Item {
                                        property var itemModel: model // read 'model' from the delegate's context
                                        property int itemIndex: index // read 'index' from the delegate's context
                            
                                        property QtObject patientDetailLoader : PatientsModel.getDataLoader(index)
                            
                                        Loader {
                                            id: model_detailsLoader
                            
                                            property var model: parent.itemModel
                                            property QtObject patientDetailLoader : parent.patientDetailLoader 
                                            property int index: parent.itemIndex
                            
                                            active: patientDetailLoader.readCompleted
                                            sourceComponent: model_listDelegate
                                        }
                            
                                    }
                            
                            
                                Component {
                                    id:                                         model_listDelegate
                            
                                    Item {
                                        id: model_testPatientDetailsData
                            
                                       property var submodel : patientDetailLoader.getSubModel()
                            
                                        width:                                  model_listRectangle.width
                                        height: {
                                            var rowCnt = 0
                                            if (submodel) {
                                                rowCnt = submodel.getRowCount()
                                            }
                                            if( rowCnt < 3 ) { rowCnt = 3 }
                                            height: rowCnt * AppSettings.scaleSize(58)
                                        }
                                        ...
                            
                            
                            At the moment this part is partly working. For example if I have 3 records then from the logs I see that 1 is loaded, but in the View I see 3 are shown which are partly overlapping. Am still trying to figure out what I did wrong.
                            
                            As I see from the logs, first read request is in progress while a second one is started and it fails as its not in current state. So I need to make ListView delegate somehow to wait until the data is loaded and then continue to the next delegate.
                            
                            
                            Best regards, Frenk
                            jeremy_kJ 1 Reply Last reply
                            0
                            • F Frenk21

                              I am trying to achive this, but no luck at the moment.

                              @jeremy_k I think I cant use DataLoader as you have suggested because my loader constructor depends on IPC connection class. So I have to use qmlRegisterUncreatedType in the QML. Or I am maybe missing something? I cant expose the IPC connection class to the QML.

                              Just to mention, that on the windows I am using QT socket and TCP/IP for testing between UI and backend. And here it takes almost 1 second to read the data, because the backend is configured to send packets of 1000 bytes. So need to add some delay here.

                              So I am going with the @Grecko approach.

                              My structure is now something like that:

                              PatientsModel holding list of all patients, but just their Ids + INVOKE method to get the PatientDetailLoader which loads additional data for the patient. Here I need a list of loaders for each id? Both are using PatientDataReader which has a FSM (lock db, get data, unlock db) using the IPC connection to read the data.

                              The patient data structure is something like that:

                              struct patientData {
                                  data fields
                                  array[5] specific diagnostics. // For this reason I use a submodel bellow.
                              }
                              

                              On the QML side I have a ListView with above model and a delegate:
                              ```
                              ListView {
                              id: model_patientList

                                      anchors.fill:                               parent
                                      spacing:                                    AppSettings.scaleSize(32)
                                      boundsBehavior:                             Flickable.StopAtBounds
                                      boundsMovement:                             Flickable.StopAtBounds
                                      clip:                                       true
                              
                                      model:                                      listModel
                              
                                      delegate: Item {
                                          property var itemModel: model // read 'model' from the delegate's context
                                          property int itemIndex: index // read 'index' from the delegate's context
                              
                                          property QtObject patientDetailLoader : PatientsModel.getDataLoader(index)
                              
                                          Loader {
                                              id: model_detailsLoader
                              
                                              property var model: parent.itemModel
                                              property QtObject patientDetailLoader : parent.patientDetailLoader 
                                              property int index: parent.itemIndex
                              
                                              active: patientDetailLoader.readCompleted
                                              sourceComponent: model_listDelegate
                                          }
                              
                                      }
                              
                              
                                  Component {
                                      id:                                         model_listDelegate
                              
                                      Item {
                                          id: model_testPatientDetailsData
                              
                                         property var submodel : patientDetailLoader.getSubModel()
                              
                                          width:                                  model_listRectangle.width
                                          height: {
                                              var rowCnt = 0
                                              if (submodel) {
                                                  rowCnt = submodel.getRowCount()
                                              }
                                              if( rowCnt < 3 ) { rowCnt = 3 }
                                              height: rowCnt * AppSettings.scaleSize(58)
                                          }
                                          ...
                              
                              
                              At the moment this part is partly working. For example if I have 3 records then from the logs I see that 1 is loaded, but in the View I see 3 are shown which are partly overlapping. Am still trying to figure out what I did wrong.
                              
                              As I see from the logs, first read request is in progress while a second one is started and it fails as its not in current state. So I need to make ListView delegate somehow to wait until the data is loaded and then continue to the next delegate.
                              
                              
                              Best regards, Frenk
                              jeremy_kJ Offline
                              jeremy_kJ Offline
                              jeremy_k
                              wrote on last edited by
                              #14

                              @Frenk21 said in QML ListView and QAbstractListModel and prefetch part of the data from 10000 records:

                              I am trying to achive this, but no luck at the moment.

                              @jeremy_k I think I cant use DataLoader as you have suggested because my loader constructor depends on IPC connection class. So I have to use qmlRegisterUncreatedType in the QML. Or I am maybe missing something? I cant expose the IPC connection class to the QML.

                              It sounds like there is a misunderstanding. The DataLoader I proposed is used as a creatable type. IPC connections can be handled in C++, and invisible to QML.

                              Just to mention, that on the windows I am using QT socket and TCP/IP for testing between UI and backend. And here it takes almost 1 second to read the data, because the backend is configured to send packets of 1000 bytes. So need to add some delay here.

                              So I am going with the @Grecko approach.

                              The two strategies are imperative javascript versus declarative QML, and really a matter of preference. There should be very little implementation difference behind the scenes. I can't imagine a scenario in which one would work and the other would not.

                              A slow server doesn't matter. When the data arrives, the managing object emits the changed signal. If it never arrives, QML will continue to use the initial value.

                              My structure is now something like that:

                              PatientsModel holding list of all patients, but just their Ids + INVOKE method to get the PatientDetailLoader which loads additional data for the patient. Here I need a list of loaders for each id? Both are using PatientDataReader which has a FSM (lock db, get data, unlock db) using the IPC connection to read the data.

                              The patient data structure is something like that:

                              ...

                              
                              At the moment this part is partly working. For example if I have 3 records then from the logs I see that 1 is loaded, but in the View I see 3 are shown which are partly overlapping. Am still trying to figure out what I did wrong.
                              
                              

                              The size of the delegate needs to be declared. Otherwise, QML will use the item's implicit size, which happens to be 0x0.

                              As I see from the logs, first read request is in progress while a second one is started and it fails as its not in current state. So I need to make ListView delegate somehow to wait until the data is loaded and then continue to the next delegate.

                              For efficiency, it may be better to have a queue of data requests that is serviced by a single data manager. That manager can handle sequencing.

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

                              F 1 Reply Last reply
                              0
                              • jeremy_kJ jeremy_k

                                @Frenk21 said in QML ListView and QAbstractListModel and prefetch part of the data from 10000 records:

                                I am trying to achive this, but no luck at the moment.

                                @jeremy_k I think I cant use DataLoader as you have suggested because my loader constructor depends on IPC connection class. So I have to use qmlRegisterUncreatedType in the QML. Or I am maybe missing something? I cant expose the IPC connection class to the QML.

                                It sounds like there is a misunderstanding. The DataLoader I proposed is used as a creatable type. IPC connections can be handled in C++, and invisible to QML.

                                Just to mention, that on the windows I am using QT socket and TCP/IP for testing between UI and backend. And here it takes almost 1 second to read the data, because the backend is configured to send packets of 1000 bytes. So need to add some delay here.

                                So I am going with the @Grecko approach.

                                The two strategies are imperative javascript versus declarative QML, and really a matter of preference. There should be very little implementation difference behind the scenes. I can't imagine a scenario in which one would work and the other would not.

                                A slow server doesn't matter. When the data arrives, the managing object emits the changed signal. If it never arrives, QML will continue to use the initial value.

                                My structure is now something like that:

                                PatientsModel holding list of all patients, but just their Ids + INVOKE method to get the PatientDetailLoader which loads additional data for the patient. Here I need a list of loaders for each id? Both are using PatientDataReader which has a FSM (lock db, get data, unlock db) using the IPC connection to read the data.

                                The patient data structure is something like that:

                                ...

                                
                                At the moment this part is partly working. For example if I have 3 records then from the logs I see that 1 is loaded, but in the View I see 3 are shown which are partly overlapping. Am still trying to figure out what I did wrong.
                                
                                

                                The size of the delegate needs to be declared. Otherwise, QML will use the item's implicit size, which happens to be 0x0.

                                As I see from the logs, first read request is in progress while a second one is started and it fails as its not in current state. So I need to make ListView delegate somehow to wait until the data is loaded and then continue to the next delegate.

                                For efficiency, it may be better to have a queue of data requests that is serviced by a single data manager. That manager can handle sequencing.

                                F Offline
                                F Offline
                                Frenk21
                                wrote on last edited by
                                #15

                                Thanks for the reply @jeremy_k. I will just stop on the first part as I stumble here:

                                @jeremy_k said in QML ListView and QAbstractListModel and prefetch part of the data from 10000 records:

                                @Frenk21 said in QML ListView and QAbstractListModel and prefetch part of the data from 10000 records:

                                I am trying to achive this, but no luck at the moment.

                                @jeremy_k I think I cant use DataLoader as you have suggested because my loader constructor depends on IPC connection class. So I have to use qmlRegisterUncreatedType in the QML. Or I am maybe missing something? I cant expose the IPC connection class to the QML.

                                It sounds like there is a misunderstanding. The DataLoader I proposed is used as a creatable type. IPC connections can be handled in C++, and invisible to QML.

                                I have a class DataLoader, but this one uses IPC connections which is actually other C++ class called IPCConnection. For the moment I pass this class as a reference to the DataLoader constructor. So this is the reason I cant use qmlRegisterType.
                                I could probably make IPCConnection as singleton and then use something like getInstance(), will revise the code if this will be ok. But how it is implemented now the qmlRegisterType will fail, except if I expose IPCConnection to the QML, but I dont want to do this.
                                If I want to internally use IPCConnection with signals/slots and connect() approach I there also need to pass an instance.
                                Or is there another way to achieve that a QML exported c++ class using the qmlRegisterType can use another c++ class without exposing it to the QML?

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

                                  I have a class DataLoader, but this one uses IPC connections which is actually other C++ class called IPCConnection. For the moment I pass this class as a reference to the DataLoader constructor. So this is the reason I cant use qmlRegisterType.

                                  It's true that allowing the QML engine to instantiate objects means giving up some control of the constructor invocation. The same is true of most interfaces. There are other ways to influence the configuration. A singleton is one. A property identifying the parameter via a string, integer, or reference to another object is another.

                                  Or is there another way to achieve that a QML exported c++ class using the qmlRegisterType can use another c++ class without exposing it to the QML?

                                  Registering a type makes it available to QML, and dictates how an instance can be instantiated. It doesn't control what that type can use in its implementation, or force exposing additional classes.

                                  The important part is code that works and is maintainable by the people working with the it. If imperative use of a loader object is easier to work with, that beats any discussion forum solution.

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

                                  F 1 Reply Last reply
                                  0
                                  • jeremy_kJ jeremy_k

                                    I have a class DataLoader, but this one uses IPC connections which is actually other C++ class called IPCConnection. For the moment I pass this class as a reference to the DataLoader constructor. So this is the reason I cant use qmlRegisterType.

                                    It's true that allowing the QML engine to instantiate objects means giving up some control of the constructor invocation. The same is true of most interfaces. There are other ways to influence the configuration. A singleton is one. A property identifying the parameter via a string, integer, or reference to another object is another.

                                    Or is there another way to achieve that a QML exported c++ class using the qmlRegisterType can use another c++ class without exposing it to the QML?

                                    Registering a type makes it available to QML, and dictates how an instance can be instantiated. It doesn't control what that type can use in its implementation, or force exposing additional classes.

                                    The important part is code that works and is maintainable by the people working with the it. If imperative use of a loader object is easier to work with, that beats any discussion forum solution.

                                    F Offline
                                    F Offline
                                    Frenk21
                                    wrote on last edited by
                                    #17

                                    @jeremy_k
                                    As again thanks for your great effort in explaining the unknown. Will try to do something in the c++ code as your solution seems really simple and efficient :-)

                                    1 Reply Last reply
                                    0
                                    • F Offline
                                      F Offline
                                      Frenk21
                                      wrote on last edited by Frenk21
                                      #18

                                      @jeremy_k

                                      I have a reader which on read data always emits the same signal, but with different data. So at the moment all my objects are being updated after read operation is finished and all have at the end the same data. I assume that in your example:

                                      void setRowId(int id) {
                                         reply = getRow(id);
                                         connect(reply, &Reply::finished, this, &DataLoader::setData);
                                       }
                                      
                                       void setData(Reply *reply) {
                                         this->picture = reply->picture;
                                         this->name = reply->name;
                                         delete request;
                                         emit pictureChanged();
                                         emit nameChanged();
                                       }
                                      

                                      You made some list of Request/Response connect pairs that are unique? But if the reader returns always the same signal, how do you then distinct which slot must be called?

                                      jeremy_kJ 1 Reply Last reply
                                      0
                                      • F Frenk21

                                        @jeremy_k

                                        I have a reader which on read data always emits the same signal, but with different data. So at the moment all my objects are being updated after read operation is finished and all have at the end the same data. I assume that in your example:

                                        void setRowId(int id) {
                                           reply = getRow(id);
                                           connect(reply, &Reply::finished, this, &DataLoader::setData);
                                         }
                                        
                                         void setData(Reply *reply) {
                                           this->picture = reply->picture;
                                           this->name = reply->name;
                                           delete request;
                                           emit pictureChanged();
                                           emit nameChanged();
                                         }
                                        

                                        You made some list of Request/Response connect pairs that are unique? But if the reader returns always the same signal, how do you then distinct which slot must be called?

                                        jeremy_kJ Offline
                                        jeremy_kJ Offline
                                        jeremy_k
                                        wrote on last edited by jeremy_k
                                        #19

                                        @Frenk21 said in QML ListView and QAbstractListModel and prefetch part of the data from 10000 records:

                                        @jeremy_k

                                        I have a reader which on read data always emits the same signal, but with different data. So at the moment all my objects are being updated after read operation is finished and all have at the end the same data.

                                        Something needs to demultiplex the data. It's more efficient to do that before it hits the delegate instance.

                                        The alternative is to pass an identifier along with the data and let each delegate compare that id to its own. Doing so will lead to N * N notifications for N delegates, which isn't ideal.

                                        I assume that in your example:

                                        [ see https://forum.qt.io/post/684038 DataLoader::setRowId() and setData()]

                                        You made some list of Request/Response connect pairs that are unique?

                                        That wasn't the intention. Each delegate makes its own request, connects the reply to the delegate's reply handler, and returns. The connect statement should have used a lambda to capture the reply. Cleaning up finished replies was also omitted to focus on the core idea.

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

                                        F 1 Reply Last reply
                                        0
                                        • jeremy_kJ jeremy_k

                                          @Frenk21 said in QML ListView and QAbstractListModel and prefetch part of the data from 10000 records:

                                          @jeremy_k

                                          I have a reader which on read data always emits the same signal, but with different data. So at the moment all my objects are being updated after read operation is finished and all have at the end the same data.

                                          Something needs to demultiplex the data. It's more efficient to do that before it hits the delegate instance.

                                          The alternative is to pass an identifier along with the data and let each delegate compare that id to its own. Doing so will lead to N * N notifications for N delegates, which isn't ideal.

                                          I assume that in your example:

                                          [ see https://forum.qt.io/post/684038 DataLoader::setRowId() and setData()]

                                          You made some list of Request/Response connect pairs that are unique?

                                          That wasn't the intention. Each delegate makes its own request, connects the reply to the delegate's reply handler, and returns. The connect statement should have used a lambda to capture the reply. Cleaning up finished replies was also omitted to focus on the core idea.

                                          F Offline
                                          F Offline
                                          Frenk21
                                          wrote on last edited by Frenk21
                                          #20

                                          @jeremy_k

                                          For the moment each DataLoader has a object Message that passes it to the Reader. DataLoader slot is connected to the Message signal. And when the Reader finish with reading it emits the Message.signal. And this is working fine. I am not sure if this is a good solution as I dont know if its ok that one class emit signal from the other class.

                                          I can also assume that when I scroll the items in the ListView, the ListView will not load all of them. It will probably try to load/create visible, but as soon they become hidden it will unload/delete them. For example if I scroll fast from 0 to the row 1000 the ListView would not load all the items.

                                          Is there any performance impact on using this approach of prefetching/caching. As now it seems ListView first creates delegates for the items, then update the values after I read them in compare to using just the Model and fetchMore()?

                                          jeremy_kJ 1 Reply Last reply
                                          0

                                          • Login

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