PySide - QSortFilterProxyModel和QListView - indexWidget指针在过滤时被删除

时间:2014-07-28 13:04:25

标签: filtering pyside qwidget qlistview qsortfilterproxymodel

我在使用自定义QListView时遇到问题,问题是:

我正在使用QListView使用QWidget显示QListView.setIndexWidget(index,widget)列表。

这工作得很好,但现在我想使用QSortFilterProxyModel()过滤项目模型 与.setFilterWildcard()

它不能很好地工作,因为第二次过滤模型 我得到这样的错误:

RuntimeError: Internal C++ object (PySide.QtGui.QLabel) already deleted.

不使用filteringQSortFilterProxyModel一切正常,但似乎我失踪了 使用过滤操作时,使用过滤操作删除了indexWidget() :(

这里有一个示例代码,您可以重现该错误,当显示列表视图时,点击1,2或3键盘 键激活过滤(Backspace将过滤设置为空以显示所有项目)

这里是重现问题的示例代码:

import PySide.QtGui as QtGui
import PySide.QtCore as QtCore


_DEFAULT_ITEM_SIZE = QtCore.QSize(100, 85)
_USER_ROLE = QtGui.QStandardItem.UserType + 1

class CustomItemWidget(QtGui.QWidget):
    def __init__(self, parent=None):
        super(CustomItemWidget, self).__init__(parent=parent)

        #self.setAutoFillBackground(True)
        self.main_layout = QtGui.QVBoxLayout(self)

        self.label = QtGui.QLabel(self)

        self.main_layout.addWidget(self.label)

    def paintEvent(self, event):
        painter = QtGui.QPainter(self)
        painter.setRenderHint(QtGui.QPainter.Antialiasing)

        # Default brush and pen
        bg_brush = QtGui.QBrush(QtGui.QColor("#8C8C8C"))
        pen = QtCore.Qt.NoPen

        painter.save()
        painter.setPen(pen)
        painter.setBrush(bg_brush)
        painter.drawRoundedRect(self.rect(), 12, 12)
        painter.restore()

    def setData(self, role, value):
        if role == QtCore.Qt.DisplayRole:
            self.label.setText(value)



class CustomItem(QtGui.QStandardItem):
    def __init__(self):
        super(CustomItem, self).__init__()

        self.number = None
        self.item_widget = CustomItemWidget()
        self.setSelectable(True)

    def type(self):
        return _USER_ROLE

    def data(self, role):
        if role == QtCore.Qt.DisplayRole:
            value = "DATA %s" % str(self.number)
            self.item_widget.setData(role, value)

            return value

        if role == QtCore.Qt.SizeHintRole:
            return _DEFAULT_ITEM_SIZE

        return QtGui.QStandardItem.data(self, role)



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


class CustomItemModel(QtGui.QStandardItemModel):
    def __init__(self, parent=None):
        super(CustomItemModel, self).__init__(parent)

    def flags(self, index):
        return QtCore.Qt.ItemIsEnabled | \
            QtCore.Qt.ItemIsSelectable | \
            QtCore.Qt.ItemIsDragEnabled | \
            QtCore.Qt.ItemIsDropEnabled


class CustomItemFilterProxyModel(QtGui.QSortFilterProxyModel):
    def __init__(self, parent=None):
        super(CustomItemFilterProxyModel, self).__init__(parent)

        self.setDynamicSortFilter(True)
        self.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
        self.setFilterKeyColumn(0)


class CustomView(QtGui.QListView):
    def __init__(self, parent=None):
        super(CustomView, self).__init__(parent=parent)

        self.setIconSize(_DEFAULT_ITEM_SIZE)
        self.setMovement(QtGui.QListView.Static)
        self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
        self.setSelectionBehavior(QtGui.QAbstractItemView.SelectItems)
        self.setViewMode(QtGui.QListView.IconMode)
        self.setUniformItemSizes(True)
        self.setFlow(QtGui.QListView.LeftToRight)
        self.setResizeMode(QtGui.QListView.Adjust)

        self.data_model = CustomItemModel(self)
        self.proxy_model = CustomItemFilterProxyModel(self)

        self.proxy_model.setSourceModel(self.data_model)
        self.setModel(self.proxy_model)

    def keyPressEvent(self, event):
        if event.key() == QtCore.Qt.Key_1:
            self.proxy_model.setFilterWildcard("*1*")
            print self.proxy_model.filterRegExp()
        if event.key() == QtCore.Qt.Key_2:
            self.proxy_model.setFilterWildcard("*2*")
            print self.proxy_model.filterRegExp()
        if event.key() == QtCore.Qt.Key_3:
            self.proxy_model.setFilterWildcard("*3*")
            print self.proxy_model.filterRegExp()
        if event.key() == QtCore.Qt.Key_Backspace:
            self.proxy_model.setFilterFixedString("")
            print self.proxy_model.filterRegExp()
        if event.key() == QtCore.Qt.Key_Plus:
            self.addNewItem()

        QtGui.QListView.keyPressEvent(self, event)

    def addNewItem(self):
        item = CustomItem()
        item.number = self.data_model.rowCount()

        self.addItem(item)

    def addItem(self, item):
        self.data_model.appendRow(item)
        proxy_index = self.proxy_model.mapFromSource(item.index())

        self.setIndexWidget(proxy_index, item.item_widget)


if __name__ == '__main__':
    import sys
    qapplication = QtGui.QApplication(sys.argv)

    layout = QtGui.QVBoxLayout()
    window = QtGui.QDialog()
    window.setLayout(layout)

    view = CustomView(window)
    view.resize(800, 600)

    layout.addWidget(view)

    for i in range(0, 10):
        item = CustomItem()
        item.number = i
        view.addItem(item)


    window.show()

    sys.exit(qapplication.exec_())

或示例代码:

https://gist.github.com/66e29df303d1f1825a53

有人可以帮我这个吗?这是一个已知的错误 ?或者我完全错了:P

提前感谢您的帮助。

1 个答案:

答案 0 :(得分:0)

这是一个老问题,但由于我在类似问题上挣扎了很长时间,这里是我找到的解决方案和可能的解释:

我没有缓存模型项上的自定义小部件,而是缓存了创建小部件所需的数据。就我而言,我想使用带有 html 的自定义标签,以便能够以不同颜色格式化部分文本。因此,我在项目上缓存了 html 字符串。

然后,在项目委托的 initStyleOption 方法中,如果小部件尚不存在或在过滤后消失,我将重新创建该小部件:

  label = self.parent().indexWidget(modelIndex)
  if not label:
    label = CustomLabel(item.html)
    self.parent().setIndexWidget(modelIndex, label)

过滤删除缓存在item上的widget的原因如下,我相信:widget只能“存在”在一个地方。当它作为 indexWidget 放置时,它“存在”在视图中的一行上,不再存在于模型的项目中。当过滤从视图中删除行时,这些行上的小部件将被删除。 - 一个糟糕的解释,但如果我忘记克隆元素,我在使用 JavaScript 操作 html 元素时经常会遇到类似的惊喜。