Refresh view when model data has not changed (Qt/PySide/PyQt)?

守給你的承諾、 提交于 2019-12-10 10:36:15

问题


I have a tree view of a standard item model in which I can use a spinbox to change the row height in the view (see SSCCE below). This doesn't change the content of the view, only its appearance, sort of like resizing the main window except I have to do it myself:

I change the row height from within the delegate's sizeHint method. It is within sizeHint that I get the value from the spinbox and set the row height to that value. To make sure the size hint is actually called, I 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:

QtGui.QStandardItemModel.layoutChanged.emit()

It is sort of uncommon usage, as that is more for when you have rearranged your data (e.g., by sorting). This is what I include in the SSCCE below, because it works. I also tried following the more commonly suggested practice of emitting dataChanged:

 QtGui.QStandardItemModel.dataChanged.emit(QtCore.QModelIndex(), QtCore.QModelIndex())

This does not work for me. 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.

At any rate, there is a lot of discussion online about what to do when you change the data in your model (see Relevant Posts), but none I have found about what to do when you just want to simply refresh the view for purely cosmetic reasons.

Cross post

I posted the same question at Qt Centre:

http://www.qtcentre.org/threads/63982-Best-way-to-refresh-view-for-cosmetic-%28non-model%29-changes

I got an answer there that have incorporated into the accepted answer below.

Relevant posts

  • Qt Model-View update view?

  • PyQt - Automatically refresh a custom view when the model is updated?

  • http://www.qtcentre.org/threads/48230-QTreeView-How-to-refresh-the-view

  • http://www.qtcentre.org/threads/3145-QTableView-How-to-refresh-the-view-after-an-update-of-the-model

SSCCE

import sys
from PySide import QtGui, QtCore

class MainTree(QtGui.QMainWindow):
    def __init__(self, parent = None):
        QtGui.QMainWindow.__init__(self)
        self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
        self.createRowHeightSpinbox() #create first otherwise get errors
        self.tree = SimpleTree(self)
        self.setCentralWidget(self.tree)       
        #Add spinbox to toolbar
        self.rowHeightAction = QtGui.QAction("Change row height", self)
        self.toolbar = self.addToolBar("rowHeight")
        self.toolbar.addWidget(QtGui.QLabel("Row height "))
        self.toolbar.addWidget(self.rowHeightSpinBox)  
        #Expand and resize tree
        self.tree.expandAll()
        self.tree.resizeColumnToContents(0) 
        self.tree.resizeColumnToContents(1) 

    def createRowHeightSpinbox(self):
        self.rowHeightSpinBox = QtGui.QSpinBox()
        self.rowHeightSpinBox.setRange(10, 50)
        self.rowHeightSpinBox.setValue(18)
        self.rowHeightSpinBox.valueChanged.connect(self.refreshView)  #showimage uses the spinbox attribute to scale image

    def refreshView(self):
        self.tree.model.layoutChanged.emit()


class SimpleTree(QtGui.QTreeView):
    def __init__(self, parent = None):    
        QtGui.QTreeView.__init__(self, parent)
        self.setUniformRowHeights(True) #optimize
        self.model = QtGui.QStandardItemModel()
        self.rootItem = self.model.invisibleRootItem()
        item0 = [QtGui.QStandardItem('Sneeze'), QtGui.QStandardItem('You have been blocked up')]
        item00 = [QtGui.QStandardItem('Tickle nose'), QtGui.QStandardItem('Key first step')]
        item1 = [QtGui.QStandardItem('Get a job'), QtGui.QStandardItem('Do not blow it')]
        self.rootItem.appendRow(item0)
        item0[0].appendRow(item00) 
        self.rootItem.appendRow(item1)
        self.setModel(self.model)
        self.setItemDelegate(ExpandableRows(self))


class ExpandableRows(QtGui.QStyledItemDelegate):
    def __init__(self, parent=None):
        QtGui.QStyledItemDelegate.__init__(self, parent)
        self.parent = parent

    def sizeHint(self, option, index):
        rowHeight = self.parent.window().rowHeightSpinBox.value()
        text = index.model().data(index)
        document = QtGui.QTextDocument()
        document.setDefaultFont(option.font)
        document.setPlainText(text)  #for html use setHtml
        return QtCore.QSize(document.idealWidth() + 5,  rowHeight) 


def main():
    app = QtGui.QApplication(sys.argv)
    #myTree = SimpleTree()
    myMainTree = MainTree()
    myMainTree.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

回答1:


Solution

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:

class ExpandableRows(QtGui.QStyledItemDelegate):
    def __init__(self, parent=None):
        QtGui.QStyledItemDelegate.__init__(self, parent)
        self.parent = parent
        self.parent.window().rowHeightSpinBox.valueChanged.connect(self.emitSizeChange)

    def emitSizeChange(self):
        self.sizeHintChanged.emit(QtCore.QModelIndex())

Analysis

As indicated in the OP, you don't want to call dataChanged because it doesn't actually work, and because your data hasn't actually changed. Further, while calling layoutChanged works, it is less principled because it is technically meant to be used to tell the view that the model's items have been rearranged, which they have not.

Caveat: I believe that sizeHintChanged expects to use a valid index, but my solution is working with the invalid index. Because it works, I'm leaving it with the invalid QtCore.QModelIndex(). Perhaps someone can find an improvement on that and edit this answer.

Is principled better than fast?

Note when you do it the principled way, it actually is a little bit slower than using the layoutChanged trick. Specifically running layoutChanged takes about 70 microseconds, while emitting sizeHintChanged takes about 100 microseconds. This didn't depend on the size of the models I tested (up to 1000 rows). 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.

The layoutChanged trick also has the benefit of being simpler: it doesn't involve messing around with the delegate, but uses intuitively simple methods on the main window class. Also because it doesn't depend on any methods being implemented in the delegate, the trick (arguably) seems to be more modular. The "proper" way depends on creating a more brittle dependence between the delegate and the main window, which means it will be easier to break the application when developers modify one or the other.

In sum, a case could be made that in just about every measurable way, the hack from the OP is better than the "principled" solution here.

Acknowledgment

I got turned on to the existence of sizeHintChanged from answers at the same question that I cross-posted at QtCentre:

http://www.qtcentre.org/threads/63982-Best-way-to-refresh-view-for-cosmetic-%28non-model%29-changes



来源:https://stackoverflow.com/questions/33061249/refresh-view-when-model-data-has-not-changed-qt-pyside-pyqt

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!