Results 1 to 13 of 13

Thread: Best way to refresh view for cosmetic (non-model) changes?

  1. #1
    Join Date
    Jun 2014
    Posts
    98
    Platforms
    Windows
    Thanks
    43
    Thanked 4 Times in 4 Posts

    Default Best way to refresh view for cosmetic (non-model) changes?

    I change the row height of a tree view from within a delegate's sizeHint method. It is within sizeHint that I get the value from a spinbox in a toolbar, and set the row height to the value in the spinbox. To make sure the size hint is actually called, I aim to refresh the view when the spinbox value is changed.

    My question is this: in such cases of purely cosmetic changes, what is the recommended way to tell the view to refresh? Is there some method built specifically for such cases? Obviously, this question assumes my general strategy for adjusting row height is sound, which I am also open to correction on. There are a few methods for telling the view that it is time to refetch the data and redraw things: layoutChanged, reset, setModel, dataChanged. Hell, I found that even just calling expandAll on the tree was enough to update my view to show the new row height.

    In practice, I found using layoutChanged works extremely well:
    Qt Code:
    1. QtGui.QStandardItemModel.layoutChanged.emit()
    To copy to clipboard, switch view to plain text mode 

    It is sort of uncommon usage, as that is more for when you have rearranged your data (e.g., by sorting).

    I also tried following the more commonly suggested practice of emitting dataChanged:

    Qt Code:
    1. QtGui.QStandardItemModel.dataChanged.emit(QtCore.QModelIndex(), QtCore.QModelIndex())
    To copy to clipboard, switch view to plain text mode 

    This does not work. Even if it did, it would also be something of a hack, because it is telling the view that the data has changed in the model. When it hasn't. Interestingly, I found that while layoutChanged does call sizeHint, calling dataChanged only calls paint, but not sizeHint. So the suggestion here:

    http://www.qtcentre.org/threads/4823...fresh-the-view

    Doesn't work for purely cosmetic changes like changing row height, it seems.

    Perhaps I'm missing a better technique, but layoutChanged() seems to work well in my standard item model. I'm wondering if I'm missing an obvious better way.

    ----------------------

    Note I posed this question at Stack Overflow:
    http://stackoverflow.com/questions/3...qt-pyside-pyqt

  2. #2
    Join Date
    Jan 2006
    Location
    Graz, Austria
    Posts
    8,416
    Qt products
    Qt3 Qt4 Qt5
    Platforms
    Unix/X11 Windows
    Thanks
    37
    Thanked 1,544 Times in 1,494 Posts

    Default Re: Best way to refresh view for cosmetic (non-model) changes?

    So you change the value that is later returned by SizeHintRole in all cells in your model, then emit the dataChanged and the view doesn't pick it up?

    Cheers,
    _

  3. #3
    Join Date
    Jun 2014
    Posts
    98
    Platforms
    Windows
    Thanks
    43
    Thanked 4 Times in 4 Posts

    Default Re: Best way to refresh view for cosmetic (non-model) changes?

    Quote Originally Posted by anda_skoa View Post
    So you change the value that is later returned by SizeHintRole in all cells in your model, then emit the dataChanged and the view doesn't pick it up?
    Yes. Strangely, the paint method is called when I invoke dataChanged, but sizeHint is not.

    Note I'm using QStandardItemModel, not QAbstractItemModel. Here is my code:
    Qt Code:
    1. import sys
    2. from PySide import QtGui, QtCore
    3.  
    4. class MainTree(QtGui.QMainWindow):
    5. def __init__(self, parent = None):
    6. QtGui.QMainWindow.__init__(self)
    7. self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
    8. self.createRowHeightSpinbox() #create first otherwise get errors
    9. self.tree = SimpleTree(self)
    10. self.setCentralWidget(self.tree)
    11. #Add spinbox to toolbar
    12. self.rowHeightAction = QtGui.QAction("Change row height", self)
    13. self.toolbar = self.addToolBar("rowHeight")
    14. self.toolbar.addWidget(QtGui.QLabel("Row height "))
    15. self.toolbar.addWidget(self.rowHeightSpinBox)
    16. #Expand and resize tree
    17. self.tree.expandAll()
    18. self.tree.resizeColumnToContents(0)
    19. self.tree.resizeColumnToContents(1)
    20.  
    21. def createRowHeightSpinbox(self):
    22. self.rowHeightSpinBox = QtGui.QSpinBox()
    23. self.rowHeightSpinBox.setRange(10, 50)
    24. self.rowHeightSpinBox.setValue(18)
    25. self.rowHeightSpinBox.valueChanged.connect(self.refreshView) #showimage uses the spinbox attribute to scale image
    26.  
    27. def refreshView(self):
    28. self.tree.model.layoutChanged.emit() #this works
    29. ###self.tree.model.dataChanged.emit(QtCore.QModelIndex(), QtCore.QModelIndex()) #this does not work
    30.  
    31.  
    32. class SimpleTree(QtGui.QTreeView):
    33. def __init__(self, parent = None):
    34. QtGui.QTreeView.__init__(self, parent)
    35. self.setUniformRowHeights(True) #optimize
    36. self.model = QtGui.QStandardItemModel()
    37. self.rootItem = self.model.invisibleRootItem()
    38. item0 = [QtGui.QStandardItem('Sneeze'), QtGui.QStandardItem('You have been blocked up')]
    39. item00 = [QtGui.QStandardItem('Tickle nose'), QtGui.QStandardItem('Key first step')]
    40. item1 = [QtGui.QStandardItem('Get a job'), QtGui.QStandardItem('Do not blow it')]
    41. self.rootItem.appendRow(item0)
    42. item0[0].appendRow(item00)
    43. self.rootItem.appendRow(item1)
    44. self.setModel(self.model)
    45. self.setItemDelegate(ExpandableRows(self))
    46.  
    47.  
    48. class ExpandableRows(QtGui.QStyledItemDelegate):
    49. def __init__(self, parent=None):
    50. QtGui.QStyledItemDelegate.__init__(self, parent)
    51. self.parent = parent
    52.  
    53. def sizeHint(self, option, index):
    54. rowHeight = self.parent.window().rowHeightSpinBox.value()
    55. text = index.model().data(index)
    56. document = QtGui.QTextDocument()
    57. document.setDefaultFont(option.font)
    58. document.setPlainText(text) #for html use setHtml
    59. return QtCore.QSize(document.idealWidth() + 5, rowHeight)
    60.  
    61.  
    62. def main():
    63. app = QtGui.QApplication(sys.argv)
    64. #myTree = SimpleTree()
    65. myMainTree = MainTree()
    66. myMainTree.show()
    67. sys.exit(app.exec_())
    68.  
    69.  
    70. if __name__ == "__main__":
    71. main()
    To copy to clipboard, switch view to plain text mode 


    Added after 7 minutes:


    Just did some experimenting trying to get sizeHint called with dataChanged by editing data in the model, and that only worked when I edited data in the first row (i.e., the first child of the invisible root item). Any other rows I edit, it repaints but does not invoke sizeHint so the row doesn't track the spinbox. But when I turn off 'setUniformRowHeight', this no longer is the case: upon editing an item in a row, when the editor is closed the row height for that row is adjusted.

    So I seem to be screwing up something in my delegate, in that when I have uniform row height set to True, it only applies size hint when I edit the first row.

    But this "insight" doesn't fix the problem: even turning off setUniformRowHeight doesn't make the row height update with the spinbox. Only editing data in the model does that.

    Note all of this addendum only applies when I try to use dataChanged. Emitting layoutChanged seems to work fine.

    Not sure which of these behaviors I've found are bugs and which are features: I assume all of them are features. I am pretty new to delegates.
    Last edited by neuronet; 15th October 2015 at 19:41.

  4. #4
    Join Date
    Jan 2006
    Location
    Graz, Austria
    Posts
    8,416
    Qt products
    Qt3 Qt4 Qt5
    Platforms
    Unix/X11 Windows
    Thanks
    37
    Thanked 1,544 Times in 1,494 Posts

    Default Re: Best way to refresh view for cosmetic (non-model) changes?

    Your posted code seems to be missing the lines that set the sizehint values for the cells in the model.

    So when dataChanged() triggers getting new data, the values will be the same as before.

    Cheers,
    _

  5. #5
    Join Date
    Jun 2014
    Posts
    98
    Platforms
    Windows
    Thanks
    43
    Thanked 4 Times in 4 Posts

    Default Re: Best way to refresh view for cosmetic (non-model) changes?

    Quote Originally Posted by anda_skoa View Post
    Your posted code seems to be missing the lines that set the sizehint values for the cells in the model.

    So when dataChanged() triggers getting new data, the values will be the same as before.

    Cheers,
    _
    Line 53:

    Qt Code:
    1. def sizeHint(self, option, index):
    2. rowHeight = self.parent.window().rowHeightSpinBox.value()
    3. ....
    4. return QtCore.QSize(document.idealWidth() + 5, rowHeight)
    To copy to clipboard, switch view to plain text mode 

    I thought this was returning the size, and it would be automatically called when the view refreshed. It works great when I use 'layoutChanged' (which is what I include in the SSCCE). Again, I'm new to delegates I'm sure I'm doing something wrong, but not sure why you are saying I am not setting the height, I thought I was returning the new values right there in sizeHint.
    Last edited by neuronet; 16th October 2015 at 00:11.

  6. #6
    Join Date
    Jan 2006
    Location
    Graz, Austria
    Posts
    8,416
    Qt products
    Qt3 Qt4 Qt5
    Platforms
    Unix/X11 Windows
    Thanks
    37
    Thanked 1,544 Times in 1,494 Posts

    Default Re: Best way to refresh view for cosmetic (non-model) changes?

    Quote Originally Posted by neuronet View Post
    Line 53:

    Qt Code:
    1. def sizeHint(self, option, index):
    2. rowHeight = self.parent.window().rowHeightSpinBox.value()
    3. ....
    4. return QtCore.QSize(document.idealWidth() + 5, rowHeight)
    To copy to clipboard, switch view to plain text mode 
    Ah.
    That is in the delegate.
    dataChanged() tells the view that the model's data has changed, so it will retrieve the data again.
    But it could very well check if the data has actually changed and not do anything if it hasn't.
    For example if the model does not return anything for SizeHintRole and, again after dataChanged(), then the view could decide to not redraw.

    If the delegate wants the view to consider a new size hint, it has a signal on its own for that.

    Using layoutChanged() does not appropriate too.

    Cheers,
    _

  7. #7
    Join Date
    Jun 2014
    Posts
    98
    Platforms
    Windows
    Thanks
    43
    Thanked 4 Times in 4 Posts

    Default Re: Best way to refresh view for cosmetic (non-model) changes?

    Quote Originally Posted by anda_skoa View Post
    Ah.
    That is in the delegate.
    dataChanged() tells the view that the model's data has changed, so it will retrieve the data again.
    But it could very well check if the data has actually changed and not do anything if it hasn't.
    For example if the model does not return anything for SizeHintRole and, again after dataChanged(), then the view could decide to not redraw.

    If the delegate wants the view to consider a new size hint, it has a signal on its own for that.

    Using layoutChanged() does not appropriate too.

    Cheers,
    _
    So, what is the appropriate thing to use then? Is there a more direct way to force sizeHint to be invoked? While it may seem weird for a tree view, it is pretty common for tables to need to change the row height. What would people use for a spinbox to change row height for a table? layoutChanged() may be a hack, but it seems to work well, and is very fast. I guess I'll use that until something better comes along.

    I guess if it were a table we'd have 'resizeRowsToContents: Resizes all rows based on the size hints of the delegate used to render each item in the rows.' Unfortunately we don't have that for trees. Or hell, we'd just use `setRowHeight` for a table view, another method we don't have for tree views.

    I found a similar problem someone encountered:
    http://www.qtcentre.org/threads/4401...-amp-QTreeView
    Last edited by neuronet; 16th October 2015 at 15:30.

  8. #8
    Join Date
    Mar 2009
    Location
    Brisbane, Australia
    Posts
    7,729
    Qt products
    Qt4 Qt5
    Platforms
    Unix/X11 Windows
    Thanks
    13
    Thanked 1,610 Times in 1,537 Posts
    Wiki edits
    17

    Default Re: Best way to refresh view for cosmetic (non-model) changes?


  9. The following user says thank you to ChrisW67 for this useful post:

    neuronet (17th October 2015)

  10. #9
    Join Date
    Jun 2014
    Posts
    98
    Platforms
    Windows
    Thanks
    43
    Thanked 4 Times in 4 Posts

    Default Re: Best way to refresh view for cosmetic (non-model) changes?

    Quote Originally Posted by ChrisW67 View Post
    Interesting. I added the following within the paint reimplementation (I specified just the first row.column index so it wouldn't be invoked for every item):
    Qt Code:
    1. if index.row()==0 and index.column() == 0:
    2. self.sizeHintChanged.emit(QtCore.QModelIndex())
    To copy to clipboard, switch view to plain text mode 
    It seems to work well. Not sure if I'm using it as I should: does using it this way seem appropriate? Seems a good way to force the issue.

    That said, I fear something is amiss, as now I don't even need to call dataChanged and when I add that line to paint my app is actually calling paint continuously, even with zero interactions with the window.

    So something is amiss....

    Here is my revised paint function:
    Qt Code:
    1. def paint(self, painter, option, index):
    2. print 'paint'
    3. if index.row()==0 and index.column() == 0:
    4. self.sizeHintChanged.emit(QtCore.QModelIndex())
    5. QtGui.QStyledItemDelegate.paint(self, painter, option, index)
    To copy to clipboard, switch view to plain text mode 
    Last edited by neuronet; 17th October 2015 at 02:21.

  11. #10
    Join Date
    Mar 2009
    Location
    Brisbane, Australia
    Posts
    7,729
    Qt products
    Qt4 Qt5
    Platforms
    Unix/X11 Windows
    Thanks
    13
    Thanked 1,610 Times in 1,537 Posts
    Wiki edits
    17

    Default Re: Best way to refresh view for cosmetic (non-model) changes?

    At a guess:
    When the view paints you tell the view that the size might have changed, so it queues a repaint...
    Then, when the view paints you tell the view that the size might have changed, so it queues a repaint...

    You should emit the sizeHintChanged() signal only if the size hint actually changes, and also only for the indexes that the size change applies to (i.e. not an invalid index)

  12. #11
    Join Date
    Jun 2014
    Posts
    98
    Platforms
    Windows
    Thanks
    43
    Thanked 4 Times in 4 Posts

    Default Re: Best way to refresh view for cosmetic (non-model) changes?

    Quote Originally Posted by ChrisW67 View Post
    At a guess:
    When the view paints you tell the view that the size might have changed, so it queues a repaint...
    Then, when the view paints you tell the view that the size might have changed, so it queues a repaint...

    You should emit the sizeHintChanged() signal only if the size hint actually changes....
    The problem is emitting it from within the main window when the qspinbox value changes. That's what I want to do. I'll think about how to do it. Maybe I can construct the valueChanged connection to sizeHint within the delegate?

    Must sleep, but this seems to work, even when I don't worry about the index (after all I want all rows to change so this is convenient ):
    Qt Code:
    1. class ExpandableRows(QtGui.QStyledItemDelegate):
    2. def __init__(self, parent=None):
    3. QtGui.QStyledItemDelegate.__init__(self, parent)
    4. self.parent = parent
    5. self.parent.window().rowHeightSpinBox.valueChanged.connect(self.emitSizeChange)
    6.  
    7. def emitSizeChange(self):
    8. self.sizeHintChanged.emit(QtCore.QModelIndex())
    To copy to clipboard, switch view to plain text mode 

    This looks a little ugly, but it actually seems elegant. Maybe my syntax is a little off, but it seems pretty.
    Last edited by neuronet; 17th October 2015 at 06:37.

  13. #12
    Join Date
    Mar 2009
    Location
    Brisbane, Australia
    Posts
    7,729
    Qt products
    Qt4 Qt5
    Platforms
    Unix/X11 Windows
    Thanks
    13
    Thanked 1,610 Times in 1,537 Posts
    Wiki edits
    17

    Default Re: Best way to refresh view for cosmetic (non-model) changes?

    You could try this approach.

    Give your delegate a slot to receive the value of the spin box when sent by the spin box's valueChanged(), e.g. setRowHeight(int).
    When received, store the value in a delegate member variable and use that when sizeHint() is called.
    Connect the spin box valueChanged() signal to your custom delegate's setRowHeight().
    Connect the spin box valueChanged() signal to your view's update() slot (You should be able to ignore the parameter mismatch)

  14. #13
    Join Date
    Jun 2014
    Posts
    98
    Platforms
    Windows
    Thanks
    43
    Thanked 4 Times in 4 Posts

    Default Re: Best way to refresh view for cosmetic (non-model) changes?

    From your suggestion, I connect spin box valueChanged() directly to the delegate's sizeHintChanged.emit(), and it works great. I posted my full solution in more detail here:
    http://stackoverflow.com/a/33193340/1886357


    The principled solution is to emit QAbstractItemDelegate.sizeHintChanged when the spinbox value changes. This is because you only want to call sizeHint of your delegate, and that's exactly what this method does.

    In the example in the OP, the size hint is intended to change when the value in the spinbox is changed. You can connect the valueChanged signal from the spinbox to the delegate's sizeHintChanged signal as follows:
    Qt Code:
    1. class ExpandableRows(QtGui.QStyledItemDelegate):
    2. def __init__(self, parent=None):
    3. QtGui.QStyledItemDelegate.__init__(self, parent)
    4. self.parent = parent
    5. self.parent.window().rowHeightSpinBox.valueChanged.connect(self.emitSizeChange)
    6.  
    7. def emitSizeChange(self):
    8. self.sizeHintChanged.emit(QtCore.QModelIndex())
    To copy to clipboard, switch view to plain text mode 

    As indicated in the OP, you don't want to call dataChanged because it doesn't actually work, and because your data hasn't changed. And while calling layoutChanged works (indeed, it works very well: see below), it is less principled because it is technically meant to be used to tell the view that the models items have been rearranged, which isn't the case here.

    Note when you do it the principled way, it actually is a tiny bit slower than using the layoutChanged trick, so it isn't clear this way is objectively better in any way! Specifically running layoutChanged takes about 70 microseconds, while emitting sizeHintChanged took about 100 microseconds. This didn't depend on the size of my models (up to 1000 row trees). This difference of 30 microseconds is so small as to be negligible in most applications, but if someone really wants to fully optimize for speed, they might go with the layoutChanged trick.

    One caveat: technically I believe that sizeHintChanged expects to receive a valid index, but it seems to work with the invalid index, so I'm not sure what is going on there. Because it works, I'm leaving it with the invalid QtCore.QModelIndex() but am open to improvements on that.

Similar Threads

  1. QTreeView: How to refresh the view?
    By Kira in forum Qt Programming
    Replies: 18
    Last Post: 1st December 2015, 00:11
  2. [solved]My SqlModel and refresh view
    By Hostel in forum Newbie
    Replies: 0
    Last Post: 15th September 2011, 23:55
  3. Arm7-based embedded doesn't refresh 7" touchscreen
    By psantofe in forum Qt for Embedded and Mobile
    Replies: 0
    Last Post: 26th April 2011, 13:57
  4. How to constantly refresh time on a view
    By salmanmanekia in forum Qt Programming
    Replies: 5
    Last Post: 23rd June 2008, 13:44
  5. [model/view] slow refresh on big table
    By lauranger in forum Qt Programming
    Replies: 4
    Last Post: 3rd March 2008, 22:40

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  
Qt is a trademark of The Qt Company.