【问题标题】:Inserting rows in QTreeView that uses QSortFilterProxyModel在使用 QSortFilterProxyModel 的 QTreeView 中插入行
【发布时间】:2018-11-06 11:27:55
【问题描述】:

我有一个非常基本的应用程序,它显示一个树视图和一个将项目添加到该树视图的按钮,使用当前选择作为父级。插入第一级子级效果很好,而插入 3d 级子级由于某种原因失败(插入完成后不显示。我准备了完全可验证的代码,您可以自己测试,测试用例如下:

  1. 点击任何项目
  2. 点击“添加行”按钮
  3. 点击新创建的项目
  4. 再次单击“添加行”按钮 预期结果:添加了一个子项 实际结果:什么都没有发生。

这里是代码

main.py

from PyQt5 import QtWidgets
import application
import sys


def main():
    app = QtWidgets.QApplication(sys.argv)
    window = application.Application()  
    window.show()  
    app.exec_()

main()

应用程序.py

from PyQt5 import QtWidgets
from PyQt5.QtCore import QSortFilterProxyModel, QModelIndex

import tree
from TreeModel import TreeModel


class Application(QtWidgets.QMainWindow, tree.Ui_MainWindow):
    data = [
        "test1",
        "test2",
        "test3"
    ]

    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.proxy_model = QSortFilterProxyModel(self.treeView)
        self.model = TreeModel(self.treeView)
        for data in self.data:
            index = QModelIndex()
            self.model.insertRows(self.model.rowCount(index), 1, index)
            self.model.setData(self.model.index(self.model.rowCount(index) - 1, 0, index), data)
            self.proxy_model.setSourceModel(self.model)
        self.treeView.setModel(self.proxy_model)
        self.pushButton.clicked.connect(lambda: self.add_row_click())

    def add_row_click(self):
        index = self.treeView.selectionModel().selectedIndexes()[0]
        self.proxy_model.insertRows(self.proxy_model.rowCount(index), 1, index)
        self.proxy_model.setData(self.proxy_model.index(self.proxy_model.rowCount(index) - 1, 0, index), "new_test")

TreeItem.py

class TreeItem(object):
    ind_column_name = 0
    ind_column_id = 1
    ind_column_parent_id = 2

    key_name = "name"
    key_id = "id"
    key_parent_id = "parent"
    key_new_id = "new_item_id"

    def __init__(self, data, parent=None):
        self.parentItem = parent
        self.itemData = data
        self.childItems = []

    def child(self, row):
        try:
            return self.childItems[row]
        except IndexError:
            return ""

    def childCount(self):
        return len(self.childItems)

    def childNumber(self):
        if self.parentItem is None:
            return self.parentItem.childItems.index(self)
        return 0

    def columnCount(self):
        return len(self.itemData)

    def data(self, column):
        if column != self.ind_column_id and column != self.ind_column_parent_id:
            return self.itemData[column]
        return None

    def id_data(self, column):
        if column == self.ind_column_id or column == self.ind_column_parent_id:
            return self.itemData[column]

    def insertChildren(self, position, count, columns):
        if position < 0 or position > len(self.childItems):
            return False

        for row in range(count):
            data = [None for v in range(columns)]
            item = TreeItem(data, self)
            self.childItems.insert(position, item)

        return True

    def insertColumns(self, position, columns):
        if position < 0 or position > len(self.itemData):
            return False

        for column in range(columns):
            self.itemData.insert(position, None)

        for child in self.childItems:
            child.insertColumns(position, columns)

        return True

    def parent(self):
        return self.parentItem

    def removeChildren(self, position, count):
        if position < 0 or position + count > len(self.childItems):
            return False

        for row in range(count):
            self.childItems.pop(position)

        return True

    def removeColumns(self, position, columns):
        if position < 0 or position + columns > len(self.itemData):
            return False

        for column in range(columns):
            self.itemData.pop(position)

        for child in self.childItems:
            child.removeColumns(position, columns)

        return True

    def setData(self, column, value):
        if column < 0 or column >= len(self.itemData):
            return False

        self.itemData[column] = value

        return True

    def to_json(self):
        parent_id = self.itemData[self.ind_column_parent_id]
        item_id = self.itemData[self.ind_column_id]
        json_data = dict()
        json_data[self.key_name] = self.itemData[self.ind_column_name]
        if parent_id is not None:
            json_data[self.key_parent_id] = parent_id
        if item_id is not None:
            json_data[self.key_id] = item_id
        return json_data

TreeModel.py

from PyQt5.QtCore import (QAbstractItemModel, QModelIndex, Qt)

from TreeItem import TreeItem


class TreeModel(QAbstractItemModel):
    def __init__(self, parent=None):
        super(TreeModel, self).__init__(parent)
        self.rootItem = TreeItem(["Категории", None, None])

    def columnCount(self, parent=QModelIndex()):
        # subtract hidden columns
        return self.rootItem.columnCount() - 2

    def all_rows_count(self, root_item, row_count=0):
        if root_item is None:
            root_item = self.rootItem
        for x in range(root_item.childCount()):
            row_count += 1
            row_count = self.all_rows_count(root_item.child(x), row_count)
        return row_count

    def data(self, index, role):
        if not index.isValid():
            return None

        if role != Qt.DisplayRole and role != Qt.EditRole:
            return None

        item = self.getItem(index)
        return item.data(index.column())

    def flags(self, index):
        if not index.isValid():
            return 0

        return Qt.ItemIsEditable | super(TreeModel, self).flags(index)

    def getItem(self, index):
        if index.isValid():
            item = index.internalPointer()
            if item:
                return item

        return self.rootItem

    def headerData(self, section, orientation, role=Qt.DisplayRole):
        if orientation == Qt.Horizontal and role == Qt.DisplayRole:
            return self.rootItem.data(section)

        return None

    def index(self, row, column, parent=QModelIndex()):
        if parent.isValid() and parent.column() != 0:
            return QModelIndex()

        parentItem = self.getItem(parent)
        childItem = parentItem.child(row)
        if childItem:
            return self.createIndex(row, column, childItem)
        else:
            return QModelIndex()

    def insertColumns(self, position, columns, parent=QModelIndex()):
        self.beginInsertColumns(parent, position, position + columns - 1)
        success = self.rootItem.insertColumns(position, columns)
        self.endInsertColumns()

        return success

    def insertRows(self, position, rows, parent=QModelIndex(), *args, **kwargs):
        parentItem = self.getItem(parent)
        self.beginInsertRows(parent, position, position + rows - 1)
        success = parentItem.insertChildren(position, rows,
                self.rootItem.columnCount())
        self.endInsertRows()

        return success

    def parent(self, index):
        if not index.isValid():
            return QModelIndex()

        childItem = self.getItem(index)
        parentItem = childItem.parent()

        if parentItem == self.rootItem:
            return QModelIndex()

        return self.createIndex(parentItem.childNumber(), 0, parentItem)

    def removeColumns(self, position, columns, parent=QModelIndex()):
        self.beginRemoveColumns(parent, position, position + columns - 1)
        success = self.rootItem.removeColumns(position, columns)
        self.endRemoveColumns()

        if self.rootItem.columnCount() == 0:
            self.removeRows(0, self.rowCount())

        return success

    def removeRows(self, position, rows, parent=QModelIndex()):
        parentItem = self.getItem(parent)

        self.beginRemoveRows(parent, position, position + rows - 1)
        success = parentItem.removeChildren(position, rows)
        self.endRemoveRows()

        return success

    def rowCount(self, parent=QModelIndex()):
        parentItem = self.getItem(parent)

        return parentItem.childCount()

    def setData(self, index, value, role=Qt.EditRole):
        if role != Qt.EditRole:
            return False

        item = self.getItem(index)
        result = item.setData(index.column(), value)

        if result:
            print("setData(), item name = %s, index row = %d" % (str(item.data(TreeItem.ind_column_name)), index.row()))
            self.dataChanged.emit(index, index)
        else:
            print("Failed to set value: " + str(value))
        return result

    def setHeaderData(self, section, orientation, value, role=Qt.EditRole):
        if role != Qt.EditRole or orientation != Qt.Horizontal:
            return False

        result = self.rootItem.setData(section, value)
        if result:
            self.headerDataChanged.emit(orientation, section, section)

        return result

tree.py(视图文件)

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'forso.ui'
#
# Created by: PyQt5 UI code generator 5.10.1
#
# WARNING! All changes made in this file will be lost!

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(800, 523)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.treeView = QtWidgets.QTreeView(self.centralwidget)
        self.treeView.setGeometry(QtCore.QRect(10, 10, 771, 411))
        self.treeView.setObjectName("treeView")
        self.pushButton = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton.setGeometry(QtCore.QRect(10, 430, 75, 23))
        self.pushButton.setObjectName("pushButton")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 21))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.pushButton.setText(_translate("MainWindow", "Add row"))

【问题讨论】:

    标签: python pyqt pyqt5 qtreeview qsortfilterproxymodel


    【解决方案1】:

    该问题仅在元素编辑器被激活时出现,这意味着在某种程度上,视图不会被通知模型中的更改。那时,模型的dataChanged 没有发送到代理。另一种解决方案是调用代理dataChanged并更改扩展或收缩状态以刷新视图,最后恢复扩展和收缩状态。

    def add_row_click(self):
        index = self.treeView.selectionModel().selectedIndexes()[0]
        self.treeView.setCurrentIndex(index)
        self.proxy_model.insertRows(self.proxy_model.rowCount(index), 1, index)
        self.proxy_model.setData(self.proxy_model.index(self.proxy_model.rowCount(index) - 1, 0, index), "new_test")
        self.proxy_model.dataChanged.emit(index, index)
        v = self.treeView.isExpanded(index)
        self.treeView.setExpanded(index, not v)
        self.treeView.setExpanded(index, v)
    

    总之,如果插入了一个子项但视图未正确更新,则可以使用另一个QTreeView 并将self.model 设置为模型来验证。

    【讨论】:

    • 感谢您的回答。现在测试:添加了一个孩子,但是当您尝试将第二个孩子添加到同一个父母时,它没有被添加
    • 我的好像是最新的稳定版
    • 我很抱歉,但谷歌和维基百科说 pyqt 有 5.10.1 是最新的,pip 还说它不能升级 pyqt,因为它是最新的。您能否提供下载 pyqt 5.11 的链接?附言QT 本身有 5.11 作为最新版本,但没有 pyqt
    • 顺便说一句,在您看来,这不是一个错误,我们必须集中操作才能通知代理模型?
    • @SergeyMaslov 我尝试的是,如果我不刷新屏幕,就不展开或折叠,所以即使这个展开或收缩我投资状态,然后我恢复它。
    猜你喜欢
    • 2015-06-16
    • 2018-04-02
    • 1970-01-01
    • 2015-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-08
    • 2015-05-08
    相关资源
    最近更新 更多