Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Special Interest Groups
  3. Qt Contribution
  4. Ideas to optimise QAbstractItemView::dataChanged
QtWS25 Last Chance

Ideas to optimise QAbstractItemView::dataChanged

Scheduled Pinned Locked Moved Qt Contribution
23 Posts 3 Posters 8.5k 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.
  • V Offline
    V Offline
    VRonin
    wrote on 7 Apr 2018, 15:16 last edited by
    #1

    As highlighted in this topic, when topLeft!=bottomRight the view triggers a complete repaint.

    We are putting some ideas on the table to make it better

    "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
    0
    • C Offline
      C Offline
      Christian Ehrlicher
      Lifetime Qt Champion
      wrote on 6 Apr 2018, 19:01 last edited by
      #2

      If someone has a good idea how to improve the default implementation in QAbstractItemModel - I'm open for suggestions

      Qt Online Installer direct download: https://download.qt.io/official_releases/online_installers/
      Visit the Qt Academy at https://academy.qt.io/catalog

      K 2 Replies Last reply 6 Apr 2018, 21:26
      0
      • C Christian Ehrlicher
        6 Apr 2018, 19:01

        If someone has a good idea how to improve the default implementation in QAbstractItemModel - I'm open for suggestions

        K Offline
        K Offline
        kshegunov
        Moderators
        wrote on 6 Apr 2018, 21:26 last edited by
        #3

        No ABI breaking suggestions?

        Read and abide by the Qt Code of Conduct

        1 Reply Last reply
        0
        • C Offline
          C Offline
          Christian Ehrlicher
          Lifetime Qt Champion
          wrote on 7 Apr 2018, 06:16 last edited by
          #4

          No, nothing for Qt6 - at least not for now.
          A dataChanged(QVector<QModelIndex>...) would maybe ok but it would not make anything better then doing it manually.

          Qt Online Installer direct download: https://download.qt.io/official_releases/online_installers/
          Visit the Qt Academy at https://academy.qt.io/catalog

          1 Reply Last reply
          0
          • V Offline
            V Offline
            VRonin
            wrote on 7 Apr 2018, 08:49 last edited by
            #5

            While I do realise in general it's difficult to make it better, concrete subclasses should be able to optimise it. Take QTableView for example, computing the rect for the items to update for that class should be easy but it still relies on the default implementation

            "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

            K 1 Reply Last reply 7 Apr 2018, 08:59
            0
            • V VRonin
              7 Apr 2018, 08:49

              While I do realise in general it's difficult to make it better, concrete subclasses should be able to optimise it. Take QTableView for example, computing the rect for the items to update for that class should be easy but it still relies on the default implementation

              K Offline
              K Offline
              kshegunov
              Moderators
              wrote on 7 Apr 2018, 08:59 last edited by kshegunov 4 Jul 2018, 09:04
              #6

              That's what I was thinking along, however the main issue I see is that when you give the view a range (from, to), it doesn't know which indices are actually updated. So to take your question to the extreme - assume flavor 1 and assume you pass it QModelIndex(0, 0) and QModelIndex(maxIndex, maxIndex), then the view would "think" that all the items are changed, when only the two edges are touched. Still I do believe a small optimization in the default implementation could be made:

              Instead of taking viewport->update() one could calculate the region corresponding to the range of model indices and pass viewport->update(region). However if this'd really mean something performance-wise has to be tested. Furthermore it's not quite clear if the viewport is actually going to respect the dirty region ...

              Or at least a union of the items' visual rects could be done, if using a QRegion isn't feasible.

              Read and abide by the Qt Code of Conduct

              K V 2 Replies Last reply 7 Apr 2018, 09:12
              0
              • K kshegunov
                7 Apr 2018, 08:59

                That's what I was thinking along, however the main issue I see is that when you give the view a range (from, to), it doesn't know which indices are actually updated. So to take your question to the extreme - assume flavor 1 and assume you pass it QModelIndex(0, 0) and QModelIndex(maxIndex, maxIndex), then the view would "think" that all the items are changed, when only the two edges are touched. Still I do believe a small optimization in the default implementation could be made:

                Instead of taking viewport->update() one could calculate the region corresponding to the range of model indices and pass viewport->update(region). However if this'd really mean something performance-wise has to be tested. Furthermore it's not quite clear if the viewport is actually going to respect the dirty region ...

                Or at least a union of the items' visual rects could be done, if using a QRegion isn't feasible.

                K Offline
                K Offline
                kshegunov
                Moderators
                wrote on 7 Apr 2018, 09:12 last edited by kshegunov 4 Jul 2018, 09:14
                #7

                I was thinking something like this:

                } else {
                    d->updateEditorData(topLeft, bottomRight);
                    if (isVisible() && !d->delayedPendingLayout)  {
                        QModelIndex parent = topLeft.parent();
                
                        QRect dirty;
                        for (qint32 i = topLeft.row(), maxRow = bottomRight.row(); i < maxRow; i++)  {
                            for (qint32 j = topLeft.column(), maxColumn = bottomRight.column(); j < maxColumn; j++)  {
                                dirty |= visualRect(d->model->index(i, j, parent));
                            }
                        }
                
                        dirty &= d->viewport->rect();
                        if (!dirty.isEmpty())
                            d->viewport->update(dirty);
                    }
                }

                Read and abide by the Qt Code of Conduct

                1 Reply Last reply
                1
                • K kshegunov
                  7 Apr 2018, 08:59

                  That's what I was thinking along, however the main issue I see is that when you give the view a range (from, to), it doesn't know which indices are actually updated. So to take your question to the extreme - assume flavor 1 and assume you pass it QModelIndex(0, 0) and QModelIndex(maxIndex, maxIndex), then the view would "think" that all the items are changed, when only the two edges are touched. Still I do believe a small optimization in the default implementation could be made:

                  Instead of taking viewport->update() one could calculate the region corresponding to the range of model indices and pass viewport->update(region). However if this'd really mean something performance-wise has to be tested. Furthermore it's not quite clear if the viewport is actually going to respect the dirty region ...

                  Or at least a union of the items' visual rects could be done, if using a QRegion isn't feasible.

                  V Offline
                  V Offline
                  VRonin
                  wrote on 7 Apr 2018, 09:13 last edited by
                  #8

                  @kshegunov said in QAbstractItemModel::dataChanged what would you prefer?:

                  assume flavor 1 and assume you pass it QModelIndex(0, 0) and QModelIndex(maxIndex, maxIndex), then the view would "think" that all the items are changed

                  Yes and I have some code that relies on that and it's even documented:
                  http://doc.qt.io/qt-5/qabstractitemmodel.html#dataChanged

                  If the items are of the same parent, the affected ones are those between topLeft and bottomRight inclusive. If the items do not have the same parent, the behavior is undefined.

                  the problem is that if you have a 1000x1000 table and pass index(0,0),index(1,1) it will still update 1000000 items instead of 4. This should be easily optimisable.

                  "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

                  K 1 Reply Last reply 7 Apr 2018, 09:15
                  1
                  • V VRonin
                    7 Apr 2018, 09:13

                    @kshegunov said in QAbstractItemModel::dataChanged what would you prefer?:

                    assume flavor 1 and assume you pass it QModelIndex(0, 0) and QModelIndex(maxIndex, maxIndex), then the view would "think" that all the items are changed

                    Yes and I have some code that relies on that and it's even documented:
                    http://doc.qt.io/qt-5/qabstractitemmodel.html#dataChanged

                    If the items are of the same parent, the affected ones are those between topLeft and bottomRight inclusive. If the items do not have the same parent, the behavior is undefined.

                    the problem is that if you have a 1000x1000 table and pass index(0,0),index(1,1) it will still update 1000000 items instead of 4. This should be easily optimisable.

                    K Offline
                    K Offline
                    kshegunov
                    Moderators
                    wrote on 7 Apr 2018, 09:15 last edited by
                    #9

                    @VRonin said in QAbstractItemModel::dataChanged what would you prefer?:

                    This should be easily optimisable.

                    See above code and please comment!

                    Read and abide by the Qt Code of Conduct

                    V 1 Reply Last reply 7 Apr 2018, 09:26
                    0
                    • K kshegunov
                      7 Apr 2018, 09:15

                      @VRonin said in QAbstractItemModel::dataChanged what would you prefer?:

                      This should be easily optimisable.

                      See above code and please comment!

                      V Offline
                      V Offline
                      VRonin
                      wrote on 7 Apr 2018, 09:26 last edited by VRonin 4 Jul 2018, 09:35
                      #10

                      @kshegunov Sorry, you probably ninja'd your code while I was typing.

                      Looks good and it wasn't such a big change in code. If imagine the same index(0,0),index(1,1) and the editor is open in index(0,1) you still are updating a cell too many but it's already a huge improvement over the default.

                      QTableView can optimise it further with

                      d->updateEditorData(topLeft, bottomRight);
                          if (isVisible() && !d->delayedPendingLayout)  {
                      const QRect dirty = (visualRect(topLeft) | visualRect(bottomRight)) & d->viewport->rect();
                      if (!dirty.isEmpty())
                                  d->viewport->update(dirty);
                      }
                      

                      "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

                      K 1 Reply Last reply 7 Apr 2018, 09:54
                      1
                      • V VRonin
                        7 Apr 2018, 09:26

                        @kshegunov Sorry, you probably ninja'd your code while I was typing.

                        Looks good and it wasn't such a big change in code. If imagine the same index(0,0),index(1,1) and the editor is open in index(0,1) you still are updating a cell too many but it's already a huge improvement over the default.

                        QTableView can optimise it further with

                        d->updateEditorData(topLeft, bottomRight);
                            if (isVisible() && !d->delayedPendingLayout)  {
                        const QRect dirty = (visualRect(topLeft) | visualRect(bottomRight)) & d->viewport->rect();
                        if (!dirty.isEmpty())
                                    d->viewport->update(dirty);
                        }
                        
                        K Offline
                        K Offline
                        kshegunov
                        Moderators
                        wrote on 7 Apr 2018, 09:54 last edited by
                        #11

                        @VRonin said in QAbstractItemModel::dataChanged what would you prefer?:

                        QTableView can optimise it further

                        Indeed, however the QAbstractItemView doesn't know the exact layout of the items (yet), so that's why the looping over. There's one catch though, I fear that visualRect might be rather costly, so I don't know if anything is really gained by my snippet ...

                        Read and abide by the Qt Code of Conduct

                        V 1 Reply Last reply 7 Apr 2018, 10:11
                        0
                        • K kshegunov
                          7 Apr 2018, 09:54

                          @VRonin said in QAbstractItemModel::dataChanged what would you prefer?:

                          QTableView can optimise it further

                          Indeed, however the QAbstractItemView doesn't know the exact layout of the items (yet), so that's why the looping over. There's one catch though, I fear that visualRect might be rather costly, so I don't know if anything is really gained by my snippet ...

                          V Offline
                          V Offline
                          VRonin
                          wrote on 7 Apr 2018, 10:11 last edited by VRonin 4 Jul 2018, 10:19
                          #12

                          @kshegunov said in QAbstractItemModel::dataChanged what would you prefer?:

                          I fear that visualRect might be rather costly, so I don't know if anything is really gained by my snippet ...

                          worst case is index(0, 0), index(rowCount(), columnCount()) where (assuming repaint implies evaluating more or less the same as visualRect) you end up doubling the calls. You could have something like:

                          } else {
                              d->updateEditorData(topLeft, bottomRight);
                              if (isVisible() && !d->delayedPendingLayout)  {
                                  const QModelIndex parent = topLeft.parent();
                                  const int cellsToUpdate = (bottomRight.row()-topLeft.row()+1)*(bottomRight.column()-topLeft.column()+1);
                                  const int totalCells = d->model->rowCount(parent )*d->model->columnCount(parent );
                                  if(cellsToUpdate > totalCells/2){
                                      d->viewport->update();
                                  } else{
                                          QRect dirty;
                                          for (int i = topLeft.row(), maxRow = bottomRight.row(); i < maxRow; ++i)  {
                                              for (int j = topLeft.column(), maxColumn = bottomRight.column(); j < maxColumn; ++j)  {
                                                  dirty |= visualRect(d->model->index(i, j, parent));
                                              }
                                          }
                          
                                          dirty &= d->viewport->rect();
                                          if (!dirty.isEmpty())
                                              d->viewport->update(dirty);
                                      }
                              }
                          }
                          

                          I still like your direct approach more though as:

                          • paint is probably orders of magnitude more costy than visualRect (not the same cost as I assumed above)
                          • depending how many items are inside or outside the viewport you might still end up gaining performance with your method

                          "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

                          K 1 Reply Last reply 7 Apr 2018, 10:22
                          1
                          • V VRonin
                            7 Apr 2018, 10:11

                            @kshegunov said in QAbstractItemModel::dataChanged what would you prefer?:

                            I fear that visualRect might be rather costly, so I don't know if anything is really gained by my snippet ...

                            worst case is index(0, 0), index(rowCount(), columnCount()) where (assuming repaint implies evaluating more or less the same as visualRect) you end up doubling the calls. You could have something like:

                            } else {
                                d->updateEditorData(topLeft, bottomRight);
                                if (isVisible() && !d->delayedPendingLayout)  {
                                    const QModelIndex parent = topLeft.parent();
                                    const int cellsToUpdate = (bottomRight.row()-topLeft.row()+1)*(bottomRight.column()-topLeft.column()+1);
                                    const int totalCells = d->model->rowCount(parent )*d->model->columnCount(parent );
                                    if(cellsToUpdate > totalCells/2){
                                        d->viewport->update();
                                    } else{
                                            QRect dirty;
                                            for (int i = topLeft.row(), maxRow = bottomRight.row(); i < maxRow; ++i)  {
                                                for (int j = topLeft.column(), maxColumn = bottomRight.column(); j < maxColumn; ++j)  {
                                                    dirty |= visualRect(d->model->index(i, j, parent));
                                                }
                                            }
                            
                                            dirty &= d->viewport->rect();
                                            if (!dirty.isEmpty())
                                                d->viewport->update(dirty);
                                        }
                                }
                            }
                            

                            I still like your direct approach more though as:

                            • paint is probably orders of magnitude more costy than visualRect (not the same cost as I assumed above)
                            • depending how many items are inside or outside the viewport you might still end up gaining performance with your method
                            K Offline
                            K Offline
                            kshegunov
                            Moderators
                            wrote on 7 Apr 2018, 10:22 last edited by kshegunov 4 Jul 2018, 10:26
                            #13

                            @VRonin said in QAbstractItemModel::dataChanged what would you prefer?:

                            You could have something like

                            That's an interesting twist, note however that no part of your index range may be visible in actuality, so if it were me I'd stick to just looping over and getting the visualRect. ;)
                            You can however check the edges before that and hope that a leaf (i.e. at the level of parent) of the model is displayed flat (as in the case of the QTableView. I.e. possible update of your suggestion to include some "heuristics":

                            } else {
                                d->updateEditorData(topLeft, bottomRight);
                                if (isVisible() && !d->delayedPendingLayout)  {
                                    const QRect viewportRect = d->viewport->rect(); 
                                    const QModelIndex parent = topLeft.parent();
                            
                                    // If dirty is 1/4th or more of the viewport rect, just trigger a full update
                                    QRect dirty = (visualRect(topLeft) | visualRect(bottomRight)) & viewportRect;
                                    if(dirty.width() * dirty.height() * 4 > viewportRect.width() * viewportRect.height())  {
                                        d->viewport->update();
                                    }
                                    else  { // Just fall back to iterating over the model indices
                                            QRect dirty;
                                            for (int i = topLeft.row(), maxRow = bottomRight.row(); i < maxRow; ++i)  {
                                                for (int j = topLeft.column(), maxColumn = bottomRight.column(); j < maxColumn; ++j)  {
                                                    dirty |= visualRect(d->model->index(i, j, parent));
                                                }
                                            }
                            
                                            dirty &= viewportRect;
                                            if (!dirty.isEmpty())
                                                d->viewport->update(dirty);
                                    }
                                }
                            }
                            

                            Read and abide by the Qt Code of Conduct

                            1 Reply Last reply
                            1
                            • V Offline
                              V Offline
                              VRonin
                              wrote on 7 Apr 2018, 10:46 last edited by VRonin 4 Jul 2018, 10:58
                              #14

                              While we are at it: now that a lot of models actually send the roles vector, we could replace Q_UNUSED(roles) with something like

                              if(!roles.isEmpty() && std::all_of(roles.begin(),roles.end(),[](int role)->bool{return role>=Qt::UserRole}))
                              return;
                              

                              "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

                              K 1 Reply Last reply 7 Apr 2018, 12:34
                              0
                              • V VRonin
                                7 Apr 2018, 10:46

                                While we are at it: now that a lot of models actually send the roles vector, we could replace Q_UNUSED(roles) with something like

                                if(!roles.isEmpty() && std::all_of(roles.begin(),roles.end(),[](int role)->bool{return role>=Qt::UserRole}))
                                return;
                                
                                K Offline
                                K Offline
                                kshegunov
                                Moderators
                                wrote on 7 Apr 2018, 12:34 last edited by
                                #15

                                I don't think that's too wise, there may be user roles that pertain to the display as well ... or am I misinterpreting the field?

                                Read and abide by the Qt Code of Conduct

                                V 1 Reply Last reply 7 Apr 2018, 13:13
                                0
                                • K kshegunov
                                  7 Apr 2018, 12:34

                                  I don't think that's too wise, there may be user roles that pertain to the display as well ... or am I misinterpreting the field?

                                  V Offline
                                  V Offline
                                  VRonin
                                  wrote on 7 Apr 2018, 13:13 last edited by VRonin 4 Jul 2018, 13:23
                                  #16

                                  @kshegunov said in QAbstractItemModel::dataChanged what would you prefer?:

                                  there may be user roles that pertain to the display as well

                                  I thought the point of reserving every role before Qt::UserRole was Qt telling us it can do lots of (even undocumented) stuff with the first roles but they never use anything after Qt::UserRole

                                  "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

                                  K 1 Reply Last reply 7 Apr 2018, 14:46
                                  0
                                  • V VRonin
                                    7 Apr 2018, 13:13

                                    @kshegunov said in QAbstractItemModel::dataChanged what would you prefer?:

                                    there may be user roles that pertain to the display as well

                                    I thought the point of reserving every role before Qt::UserRole was Qt telling us it can do lots of (even undocumented) stuff with the first roles but they never use anything after Qt::UserRole

                                    K Offline
                                    K Offline
                                    kshegunov
                                    Moderators
                                    wrote on 7 Apr 2018, 14:46 last edited by
                                    #17

                                    True, but correct me if I'm wrong, the point of user roles is when you need to pass some data to custom views where the role might have significance to the view display.

                                    Read and abide by the Qt Code of Conduct

                                    V 1 Reply Last reply 7 Apr 2018, 15:13
                                    0
                                    • K Offline
                                      K Offline
                                      kshegunov
                                      Moderators
                                      wrote on 7 Apr 2018, 14:59 last edited by
                                      #18

                                      This thread is going way off topic, but let's roll with it a bit more ...
                                      Can we use something like Qt::NeedsVisualUpdate role and somehow batch up the actual updates, compressing the paint events or something along those lines; deferring them to the last possible moment so to speak?

                                      Read and abide by the Qt Code of Conduct

                                      1 Reply Last reply
                                      0
                                      • K kshegunov
                                        7 Apr 2018, 14:46

                                        True, but correct me if I'm wrong, the point of user roles is when you need to pass some data to custom views where the role might have significance to the view display.

                                        V Offline
                                        V Offline
                                        VRonin
                                        wrote on 7 Apr 2018, 15:13 last edited by VRonin 4 Jul 2018, 15:19
                                        #19

                                        @kshegunov said in QAbstractItemModel::dataChanged what would you prefer?:

                                        to pass some data to custom views

                                        Exactly, so you need to reimplement QAbstractItemView::dataChanged anyway. no problem with my code

                                        This thread is going way off topic, but let's roll with it a bit more ...

                                        Forked

                                        "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

                                        K 1 Reply Last reply 7 Apr 2018, 16:10
                                        0
                                        • V VRonin
                                          7 Apr 2018, 15:13

                                          @kshegunov said in QAbstractItemModel::dataChanged what would you prefer?:

                                          to pass some data to custom views

                                          Exactly, so you need to reimplement QAbstractItemView::dataChanged anyway. no problem with my code

                                          This thread is going way off topic, but let's roll with it a bit more ...

                                          Forked

                                          K Offline
                                          K Offline
                                          kshegunov
                                          Moderators
                                          wrote on 7 Apr 2018, 16:10 last edited by
                                          #20

                                          @VRonin said in Ideas to optimise QAbstractItemView::dataChanged:

                                          Forked

                                          Yeah, thanks. We wrote a novel judging by the number of notifications I got. :)

                                          Exactly, so you need to reimplement QAbstractItemView::dataChanged anyway. no problem with my code

                                          Okay, this is my bad then.

                                          Read and abide by the Qt Code of Conduct

                                          1 Reply Last reply
                                          0

                                          2/23

                                          6 Apr 2018, 19:01

                                          21 unread
                                          • Login

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