当模型数据没有改变时刷新视图(Qt/PySide/PyQt)? [英] Refresh view when model data has not changed (Qt/PySide/PyQt)?

查看:99
本文介绍了当模型数据没有改变时刷新视图(Qt/PySide/PyQt)?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个标准项目模型的树视图,我可以在其中使用旋转框更改视图中的行高(请参阅下面的 SSCCE).这不会改变视图的内容,只会改变它的外观,有点像调整主窗口的大小,除非我必须自己做:

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:

我在委托的 sizeHint 方法中更改行高.在 sizeHint 中,我从旋转框中获取值并将行高设置为该值.为确保实际调用大小提示,我在更改旋转框值时刷新视图.

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.

有几种方法可以告诉视图是时候重新获取数据和重绘东西了:layoutChangedresetsetModel, dataChanged.见鬼,我发现即使只是在树上调用 expandAll 也足以更新我的视图以显示新的行高.

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.

在实践中,我发现使用 layoutChanged 是有效的:

In practice, I found using layoutChanged works:

QtGui.QStandardItemModel.layoutChanged.emit()

这是一种不常见的用法,因为当您重新排列数据(例如,通过排序)时更是如此.这是我在下面的 SSCCE 中包含的内容,因为它有效.我还尝试遵循更常见的发出 dataChanged 的建议做法:

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.

交叉发布

我在 Qt 中心发布了同样的问题:

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.

相关帖子

PyQt - 更新模型时自动刷新自定义视图?

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()

推荐答案

解决方案

原则上的解决方案是在 spinbox 值更改时发出 QAbstractItemDelegate.sizeHintChanged.这是因为您只想调用委托的 sizeHint,而这正是该方法所做的.

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.

在 OP 的示例中,大小提示旨在在更改 Spinbox 中的值时更改.您可以将来自微调框的 valueChanged 信号连接到委托的 sizeHintChanged 信号,如下所示:

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())

分析

如 OP 中所示,您不想调用 dataChanged,因为它实际上不起作用,而且您的数据实际上并未更改.此外,虽然调用 layoutChanged 有效,但它的原则性较差,因为从技术上讲,它旨在用于告诉视图模型的项目已重新排列,而实际上并没有.

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.

警告:我相信 sizeHintChanged 期望使用有效索引,但我的解决方案正在使用无效索引.因为它有效,所以我将其保留为无效的 QtCore.QModelIndex().也许有人可以找到改进并编辑此答案.

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.

有原则比快速更好吗?

请注意,当您以原则方式进行操作时,它实际上比使用 layoutChanged 技巧要慢一点.具体运行 layoutChanged 大约需要 70 微秒,而发出 sizeHintChanged 大约需要 100 微秒.这不取决于我测试的模型的大小(最多 1000 行).这个 30 微秒的差异非常小,在大多数应用程序中可以忽略不计,但如果有人真的想完全优化速度,他们可能会使用 layoutChanged 技巧.

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.

layoutChanged 技巧还具有更简单的好处:它不涉及对委托的处理,而是在主窗口类上使用直观简单的方法.同样因为它不依赖于委托中实现的任何方法,所以这个技巧(可以说)似乎更加模块化.正确"的方式取决于在委托和主窗口之间创建更脆弱的依赖关系,这意味着当开发人员修改其中一个时更容易破坏应用程序.

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.

总而言之,可以证明,在几乎所有可衡量的方式中,来自 OP 的 hack 都比这里的有原则的"解决方案要好.

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.

致谢

我从我在 QtCentre 交叉发布的同一问题的答案中了解到 sizeHintChanged 的存在:

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

这篇关于当模型数据没有改变时刷新视图(Qt/PySide/PyQt)?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆