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. QTreeView animation updates, data updates, layout updates - definitive correct way and performance
Forum Updated to NodeBB v4.3 + New Features

QTreeView animation updates, data updates, layout updates - definitive correct way and performance

Scheduled Pinned Locked Moved Solved General and Desktop
6 Posts 2 Posters 1.2k Views 1 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.
  • E Offline
    E Offline
    egorski
    wrote on last edited by egorski
    #1

    Hi, using QTreeView with my custom QAbstractItemModel subclass.

    What is the really correct way to conduct updates and animations, repaints?

    There are 2 main cases - animation of icons (data is same), and other case: actual change of model's data.

    Case 1:
    The icons (displayed in some of the items) are animated.
    This is done by heaving the ::data() on my subclass of QAbstractItemModel that returns different icons - different QIcon(...) depending on e.g. current time/clock. The code is like:

    if (role == Qt::DecorationRole) {  if (clock%2) return QIcon(frame1); else return QIcon(frame2); }
    

    This works, except that I do not know how to properly trigger the re-paint.

    I have a timer, like:

    QTimer * timer1 = new QTimer(nullptr);
    timer1->start(200);
    connect(timer1, &QTimer::timeout, this, &MainWindow::on_timer1);
    ...
    ::on_timer1() {
      ui->treeView->update(QRect(0,0,9000,9000));
    }
    

    the timer function fires, but repainting is not done.
    What DOES work is to instead in the timer call full ui->treeView->model()->layoutChanged(); how ever this seems expensive (CPU-intensive).

    The tree might have even 1 million nodes (items) in it, of which e.g. 10,000 are expanded tree - but due to limited size of screen only e.g. 200 of them are actually visible on screen.

    So, I do NOT want to call some data changed event on all 1,000,000 nodes, I instead want the QT to fully repaint the entire thing. Why the ->update() is not working? Tried also ui->treeView->repaint(QRect(0,0,9000,9000)); but it, same as update, does nothing at all (nothing changes on screen).
    Why this update and repaint do nothing? Only tree is repainted when I resize or move around the window manually with mouse as the user.
    How to tell Qt to actually repaint all visible items?

    Case 1b:
    small possible optimisation: say that only 10% of items have any animation in them, and the animation is only in tree column number 5 and 6. How to optimise this? E.g. I want Qt QTreeView to find out which items (AbstractItems - model nodes) are visible on screen right now, and for them call some function e.g. data() or otherwise and test which items need repainting, and which columns in them. How do I connect code that knows that into Qt so that Qt drawing code can do it?
    Perhaps I should query QTreeView which items are visible (e.g. some list of QModelIndex), and tell Qt to re-run ::data() for each this item, and then notice it returns different QIcon this time?

    Case 2:
    Besides animation, the tree data is actually dynamic (e.g. updated by background thread). My nodes have e.g. flag this->m_dirty so they know their data was updated (since last call to ::data on them). How all the changed items should tell QTreeView/Qt that their data actually changed and they might need repaint (if they are visible)? Call some Update() on them? OnDataChange...?

    1 Reply Last reply
    0
    • E egorski

      @VRonin said in QTreeView animation updates, data updates, layout updates - definitive correct way and performance:

      This will also need to get paired with a timer to emit dataChanged. That's all.

      But that doesn't seem right, if the tree is large and has also large structure (e.g. lots of subnodes).

      For example a tree with typical fanout of 10, not to mention something like binary-tree, if 30% of items are animated, it means i would need to send 300,000 dataChanged events for each animation frame (few times a second).

      If I would "pack" together events, so a typical node has 10 subnodes as assumed for my tree data heaving typical fanout=10 (and they have further subnodes) still it would be 300,000/10 = 30,000 events, right? Few times per second (e.g. 4 fps animation of the icons).
      Also this seems semantically wrong, the data is not really updated in the model at all, it's just that it needs to be painted in other way. Perhaps a totally different approach should be used with overwritting custom painter/delegate somehow? And QDecoration returning list of QIcon...

      Unless, the data change works in recursive way, e.g. emitting it for the top node would signal need to repaint it's subnodes, subsubnodes, subsubsubnodes etc?

      Since most or 30% of objects need repainting but only when visible, this is why sending dataChanged seems the bad way to approach it.

      All this, versus just e.g. 100 visible objects that really need to be painted.

      Case 1b:
      To implement this optimisation (and only if it actually make sense) emit a different dataChanged signal for each item instead of indicating a range in the first 2 arguments

      so how to obtain list of currently visible items? emiting a dataChanged only for them might make more sense for such animation.

      Just so you are aware, models are not thread safe so model and view should both live on the GUI thread. It appears as they do in your case but wanted to leave a small footnote just in case

      Right, the updates to actual data will be protected by mutex.

      E.g. my QAbstractItemModel-subclass works by storing shared_ptr to some logical_data class inside of QModelIndex::internalPointer().

      Reads of logical_data from GUI (From ::data of QAbstractItemModel-subclass etc), and writes to it (done by background thread that process logical data of the tree) will be protected by mutex (std::mutex).

      VRoninV Offline
      VRoninV Offline
      VRonin
      wrote on last edited by
      #4

      @egorski said in QTreeView animation updates, data updates, layout updates - definitive correct way and performance:

      But that doesn't seem right, if the tree is large and has also large structure (e.g. lots of subnodes).
      For example a tree with typical fanout of 10, not to mention something like binary-tree, if 30% of items are animated, it means i would need to send 300,000 dataChanged events for each animation frame (few times a second).

      Then, as mentioned, just emit the dataChaged for a range (e.g. emit dataChanged(index(0,0),index(rowCount()-1,columnCount()-1),{Qt::DecorationRole})) this is a single call and it will trigger a repaint of the entire viewport.

      so how to obtain list of currently visible items? emiting a dataChanged only for them might make more sense for such animation.

      You shouldn't. That's a problem for the view, not the model. QTreeView already takes care of this without you having to do anything.

      Reads of logical_data from GUI (From ::data of QAbstractItemModel-subclass etc), and writes to it (done by background thread that process logical data of the tree) will be protected by mutex (std::mutex).

      This should be fine as long as you don't change the shape of the tree (insert/remove/move/sort rows/columns) from a different thread

      "La mort n'est rien, mais vivre vaincu et sans gloire, c'est mourir tous les jours"
      ~Napoleon Bonaparte

      On a crusade to banish setIndexWidget() from the holy land of Qt

      E 1 Reply Last reply
      3
      • VRoninV Offline
        VRoninV Offline
        VRonin
        wrote on last edited by
        #2

        The reply for all the above is: emit dataChanged. When that signal is emitted from the model QTreeView is already smart enough to repaint only the item that changed and only if it's visible. If you emit dataChagend for multiple items at once (i.e. the topLeft argument is not the same as the bottomRight argument) the it will repaint all the items currently visible (so slightly inefficient here).

        Case 1:

        different QIcon(...) depending on e.g. current time/clock.

        This will also need to get paired with a timer to emit dataChanged. That's all.

        Case 1b:
        To implement this optimisation (and only if it actually make sense) emit a different dataChanged signal for each item instead of indicating a range in the first 2 arguments

        Case 2:
        The model should emit dataChanged to tell the view it needs repainting

        P.S.

        updated by background thread

        Just so you are aware, models are not thread safe so model and view should both live on the GUI thread. It appears as they do in your case but wanted to leave a small footnote just in case

        "La mort n'est rien, mais vivre vaincu et sans gloire, c'est mourir tous les jours"
        ~Napoleon Bonaparte

        On a crusade to banish setIndexWidget() from the holy land of Qt

        E 1 Reply Last reply
        1
        • VRoninV VRonin

          The reply for all the above is: emit dataChanged. When that signal is emitted from the model QTreeView is already smart enough to repaint only the item that changed and only if it's visible. If you emit dataChagend for multiple items at once (i.e. the topLeft argument is not the same as the bottomRight argument) the it will repaint all the items currently visible (so slightly inefficient here).

          Case 1:

          different QIcon(...) depending on e.g. current time/clock.

          This will also need to get paired with a timer to emit dataChanged. That's all.

          Case 1b:
          To implement this optimisation (and only if it actually make sense) emit a different dataChanged signal for each item instead of indicating a range in the first 2 arguments

          Case 2:
          The model should emit dataChanged to tell the view it needs repainting

          P.S.

          updated by background thread

          Just so you are aware, models are not thread safe so model and view should both live on the GUI thread. It appears as they do in your case but wanted to leave a small footnote just in case

          E Offline
          E Offline
          egorski
          wrote on last edited by egorski
          #3

          @VRonin said in QTreeView animation updates, data updates, layout updates - definitive correct way and performance:

          This will also need to get paired with a timer to emit dataChanged. That's all.

          But that doesn't seem right, if the tree is large and has also large structure (e.g. lots of subnodes).

          For example a tree with typical fanout of 10, not to mention something like binary-tree, if 30% of items are animated, it means i would need to send 300,000 dataChanged events for each animation frame (few times a second).

          If I would "pack" together events, so a typical node has 10 subnodes as assumed for my tree data heaving typical fanout=10 (and they have further subnodes) still it would be 300,000/10 = 30,000 events, right? Few times per second (e.g. 4 fps animation of the icons).
          Also this seems semantically wrong, the data is not really updated in the model at all, it's just that it needs to be painted in other way. Perhaps a totally different approach should be used with overwritting custom painter/delegate somehow? And QDecoration returning list of QIcon...

          Unless, the data change works in recursive way, e.g. emitting it for the top node would signal need to repaint it's subnodes, subsubnodes, subsubsubnodes etc?

          Since most or 30% of objects need repainting but only when visible, this is why sending dataChanged seems the bad way to approach it.

          All this, versus just e.g. 100 visible objects that really need to be painted.

          Case 1b:
          To implement this optimisation (and only if it actually make sense) emit a different dataChanged signal for each item instead of indicating a range in the first 2 arguments

          so how to obtain list of currently visible items? emiting a dataChanged only for them might make more sense for such animation.

          Just so you are aware, models are not thread safe so model and view should both live on the GUI thread. It appears as they do in your case but wanted to leave a small footnote just in case

          Right, the updates to actual data will be protected by mutex.

          E.g. my QAbstractItemModel-subclass works by storing shared_ptr to some logical_data class inside of QModelIndex::internalPointer().

          Reads of logical_data from GUI (From ::data of QAbstractItemModel-subclass etc), and writes to it (done by background thread that process logical data of the tree) will be protected by mutex (std::mutex).

          VRoninV 1 Reply Last reply
          0
          • E egorski

            @VRonin said in QTreeView animation updates, data updates, layout updates - definitive correct way and performance:

            This will also need to get paired with a timer to emit dataChanged. That's all.

            But that doesn't seem right, if the tree is large and has also large structure (e.g. lots of subnodes).

            For example a tree with typical fanout of 10, not to mention something like binary-tree, if 30% of items are animated, it means i would need to send 300,000 dataChanged events for each animation frame (few times a second).

            If I would "pack" together events, so a typical node has 10 subnodes as assumed for my tree data heaving typical fanout=10 (and they have further subnodes) still it would be 300,000/10 = 30,000 events, right? Few times per second (e.g. 4 fps animation of the icons).
            Also this seems semantically wrong, the data is not really updated in the model at all, it's just that it needs to be painted in other way. Perhaps a totally different approach should be used with overwritting custom painter/delegate somehow? And QDecoration returning list of QIcon...

            Unless, the data change works in recursive way, e.g. emitting it for the top node would signal need to repaint it's subnodes, subsubnodes, subsubsubnodes etc?

            Since most or 30% of objects need repainting but only when visible, this is why sending dataChanged seems the bad way to approach it.

            All this, versus just e.g. 100 visible objects that really need to be painted.

            Case 1b:
            To implement this optimisation (and only if it actually make sense) emit a different dataChanged signal for each item instead of indicating a range in the first 2 arguments

            so how to obtain list of currently visible items? emiting a dataChanged only for them might make more sense for such animation.

            Just so you are aware, models are not thread safe so model and view should both live on the GUI thread. It appears as they do in your case but wanted to leave a small footnote just in case

            Right, the updates to actual data will be protected by mutex.

            E.g. my QAbstractItemModel-subclass works by storing shared_ptr to some logical_data class inside of QModelIndex::internalPointer().

            Reads of logical_data from GUI (From ::data of QAbstractItemModel-subclass etc), and writes to it (done by background thread that process logical data of the tree) will be protected by mutex (std::mutex).

            VRoninV Offline
            VRoninV Offline
            VRonin
            wrote on last edited by
            #4

            @egorski said in QTreeView animation updates, data updates, layout updates - definitive correct way and performance:

            But that doesn't seem right, if the tree is large and has also large structure (e.g. lots of subnodes).
            For example a tree with typical fanout of 10, not to mention something like binary-tree, if 30% of items are animated, it means i would need to send 300,000 dataChanged events for each animation frame (few times a second).

            Then, as mentioned, just emit the dataChaged for a range (e.g. emit dataChanged(index(0,0),index(rowCount()-1,columnCount()-1),{Qt::DecorationRole})) this is a single call and it will trigger a repaint of the entire viewport.

            so how to obtain list of currently visible items? emiting a dataChanged only for them might make more sense for such animation.

            You shouldn't. That's a problem for the view, not the model. QTreeView already takes care of this without you having to do anything.

            Reads of logical_data from GUI (From ::data of QAbstractItemModel-subclass etc), and writes to it (done by background thread that process logical data of the tree) will be protected by mutex (std::mutex).

            This should be fine as long as you don't change the shape of the tree (insert/remove/move/sort rows/columns) from a different thread

            "La mort n'est rien, mais vivre vaincu et sans gloire, c'est mourir tous les jours"
            ~Napoleon Bonaparte

            On a crusade to banish setIndexWidget() from the holy land of Qt

            E 1 Reply Last reply
            3
            • VRoninV VRonin

              @egorski said in QTreeView animation updates, data updates, layout updates - definitive correct way and performance:

              But that doesn't seem right, if the tree is large and has also large structure (e.g. lots of subnodes).
              For example a tree with typical fanout of 10, not to mention something like binary-tree, if 30% of items are animated, it means i would need to send 300,000 dataChanged events for each animation frame (few times a second).

              Then, as mentioned, just emit the dataChaged for a range (e.g. emit dataChanged(index(0,0),index(rowCount()-1,columnCount()-1),{Qt::DecorationRole})) this is a single call and it will trigger a repaint of the entire viewport.

              so how to obtain list of currently visible items? emiting a dataChanged only for them might make more sense for such animation.

              You shouldn't. That's a problem for the view, not the model. QTreeView already takes care of this without you having to do anything.

              Reads of logical_data from GUI (From ::data of QAbstractItemModel-subclass etc), and writes to it (done by background thread that process logical data of the tree) will be protected by mutex (std::mutex).

              This should be fine as long as you don't change the shape of the tree (insert/remove/move/sort rows/columns) from a different thread

              E Offline
              E Offline
              egorski
              wrote on last edited by egorski
              #5

              @VRonin said in QTreeView animation updates, data updates, layout updates - definitive correct way and performance:

              Then, as mentioned, just emit the dataChaged for a range (e.g. emit dataChanged(index(0,0),index(rowCount()-1,columnCount()-1),{Qt::DecorationRole})) this is a single call and it will trigger a repaint of the entire viewport.

              Allright, that seems to work.
              I was under impression (apparently wrong) that in Tree (due to branches heaving different parent pointer) to select all elements you need to recurse yourself and select multitude of ranges.

              It seems that instead doing that thing once in the root element of tree does indeed "recurse" and selects all sub(sub*)nodes too.

              So, now the timer does obtain pointer to my QAbstractItemModel-subclass (with some dynamic casts, there is a sorting proxy model in beteween), and calls its ->emit_all_data_changed(); which is implemented as:

              void c_node_model::emit_all_data_changed() {
              	emit dataChanged(index(0,0),index(rowCount()-1,columnCount()-1),{Qt::DecorationRole});
              }
              

              the conversions&call in timer are done like:

              	auto * model_proxy1 = ui->treeView->model();
              	auto * model_proxy2 = dynamic_cast<QAbstractProxyModel*>(model_proxy1);
              	auto * model_unproxy = model_proxy2->sourceModel();
              	auto * mymodel = dynamic_cast<n_tagsentry::n_qt::c_node_model *>( model_unproxy );
              	if (mymodel) {
              		mymodel->emit_all_data_changed();
              	} else throw std::runtime_error("Can't convert to my model");
              

              It seems to work fine (much less CPU intensive than calling full layoutChanged). I will later test how it behaves with large (and deep) trees (also with nodes mostly expanded on screen).

              This should be fine as long as you don't change the shape of the tree (insert/remove/move/sort rows/columns) from a different thread
              

              To insert/remove child nodes, I plan to:
              Take exclusive mutex lock on the node (parent), then modify it (push_back into ->m_child), and then emit dataChanged event on the parent's model.

              As for changes to columns, sorting etc, they are never done from background logic, only are done as e.g. onClick and such from the GUI, so should be "in GUI thread".

              Of course, all accesses to my node's logical data will be under mutexes still.

              Thanks.

              VRoninV 1 Reply Last reply
              0
              • E egorski

                @VRonin said in QTreeView animation updates, data updates, layout updates - definitive correct way and performance:

                Then, as mentioned, just emit the dataChaged for a range (e.g. emit dataChanged(index(0,0),index(rowCount()-1,columnCount()-1),{Qt::DecorationRole})) this is a single call and it will trigger a repaint of the entire viewport.

                Allright, that seems to work.
                I was under impression (apparently wrong) that in Tree (due to branches heaving different parent pointer) to select all elements you need to recurse yourself and select multitude of ranges.

                It seems that instead doing that thing once in the root element of tree does indeed "recurse" and selects all sub(sub*)nodes too.

                So, now the timer does obtain pointer to my QAbstractItemModel-subclass (with some dynamic casts, there is a sorting proxy model in beteween), and calls its ->emit_all_data_changed(); which is implemented as:

                void c_node_model::emit_all_data_changed() {
                	emit dataChanged(index(0,0),index(rowCount()-1,columnCount()-1),{Qt::DecorationRole});
                }
                

                the conversions&call in timer are done like:

                	auto * model_proxy1 = ui->treeView->model();
                	auto * model_proxy2 = dynamic_cast<QAbstractProxyModel*>(model_proxy1);
                	auto * model_unproxy = model_proxy2->sourceModel();
                	auto * mymodel = dynamic_cast<n_tagsentry::n_qt::c_node_model *>( model_unproxy );
                	if (mymodel) {
                		mymodel->emit_all_data_changed();
                	} else throw std::runtime_error("Can't convert to my model");
                

                It seems to work fine (much less CPU intensive than calling full layoutChanged). I will later test how it behaves with large (and deep) trees (also with nodes mostly expanded on screen).

                This should be fine as long as you don't change the shape of the tree (insert/remove/move/sort rows/columns) from a different thread
                

                To insert/remove child nodes, I plan to:
                Take exclusive mutex lock on the node (parent), then modify it (push_back into ->m_child), and then emit dataChanged event on the parent's model.

                As for changes to columns, sorting etc, they are never done from background logic, only are done as e.g. onClick and such from the GUI, so should be "in GUI thread".

                Of course, all accesses to my node's logical data will be under mutexes still.

                Thanks.

                VRoninV Offline
                VRoninV Offline
                VRonin
                wrote on last edited by
                #6

                @egorski said in QTreeView animation updates, data updates, layout updates - definitive correct way and performance:

                So, now the timer does obtain pointer to my QAbstractItemModel-subclass (with some dynamic casts, there is a sorting proxy model in beteween), and calls its ->emit_all_data_changed(); which is implemented as:

                I would have moved the timer to be part of the model instead of having it external but the concept is the same

                @egorski said in QTreeView animation updates, data updates, layout updates - definitive correct way and performance:

                To insert/remove child nodes, I plan to:
                Take exclusive mutex lock on the node (parent), then modify it (push_back into ->m_child), and then emit dataChanged event on the parent's model.

                That's not how it works. To insert child elements you need to call beginInsertRows/endInsertRows. The view and the proxy models will "do stuff" as a consequences of calling those functions and your model must be in "pre-change" state until beginInsertRows returns and must be in the "post-change" state when you call endInsertRows.
                It's very useful to have the model test run alongside to make sure everything behaves as it should

                "La mort n'est rien, mais vivre vaincu et sans gloire, c'est mourir tous les jours"
                ~Napoleon Bonaparte

                On a crusade to banish setIndexWidget() from the holy land of Qt

                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