如何过滤包含小部件的 TreeView
How to filter TreeView that contains widgets
我正在 PyQt5 中开发一个包含 QTreeView、QStandardItemModel 和 QSortFilterProxyModel 的应用程序。 TreeView 在最后一列的某些行上也有一个 QToolBar。
我做了一个简化版的例子:
这是源代码:
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
class view(QWidget):
def __init__(self):
super(view, self).__init__()
self.tree = QTreeView(self)
layout = QVBoxLayout(self)
layout.addWidget(self.tree)
self.model = QStandardItemModel()
self.model.setHorizontalHeaderLabels(["Col 0", "Col 1", "Col 2", "Toolbar"])
self.tree.header().setDefaultSectionSize(180)
self.tree.setModel(self.model)
self.importData()
self.tree.expandAll()
def importData(self, root=None):
for i in range(3):
parent1 = QStandardItem("Family {}".format(i))
for j in range(3):
self.createRow(parent1, i, j)
def createRow(self, parent, i, j):
child1 = QStandardItem("Child {}".format(i * 3 + j))
child2 = QStandardItem("row: {}, col: {}".format(i, j + 1))
child3 = QStandardItem("row: {}, col: {}".format(i, j + 2))
child4 = QStandardItem("")
parent.appendRow([child1, child2, child3, child4])
self.model.appendRow(parent)
toolbar = QToolBar()
toolbar.addWidget(QLabel("Toolbar Btn: "))
toolbar.addWidget(QPushButton("Btn"))
self.tree.setIndexWidget(child4.index(), toolbar)
if __name__ == "__main__":
app = QApplication(sys.argv)
view = view()
view.setGeometry(300, 100, 600, 300)
view.setWindowTitle("QTreeview Example")
view.show()
sys.exit(app.exec_())
现在我想使用 QLineEdit 小部件和 QSortFilterProxyModel 添加过滤器,但是正如您在下面看到的那样,工具栏被删除了。有人可以解释为什么以及如何解决这个问题吗?
到目前为止,这是我的代码:
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
class view(QWidget):
def __init__(self):
super(view, self).__init__()
self.tree = QTreeView(self)
layout = QVBoxLayout(self)
self.filter = QLineEdit()
self.filter.textChanged.connect(self.onTextChanged)
layout.addWidget(self.filter)
layout.addWidget(self.tree)
self.model = QStandardItemModel()
self.model.setHorizontalHeaderLabels(["Col 0", "Col 1", "Col 2", "Toolbar"])
self.proxyModel = QSortFilterProxyModel(
self.tree, recursiveFilteringEnabled=True
)
self.proxyModel.setSourceModel(self.model)
self.tree.header().setDefaultSectionSize(180)
self.tree.setModel(self.proxyModel)
self.importData()
self.tree.expandAll()
def importData(self, root=None):
for i in range(3):
parent1 = QStandardItem("Family {}".format(i))
for j in range(3):
self.createRow(parent1, i, j)
def createRow(self, parent, i, j):
child1 = QStandardItem("Child {}".format(i * 3 + j))
child2 = QStandardItem("row: {}, col: {}".format(i, j + 1))
child3 = QStandardItem("row: {}, col: {}".format(i, j + 2))
child4 = QStandardItem("")
parent.appendRow([child1, child2, child3, child4])
self.model.appendRow(parent)
toolbar = QToolBar()
toolbar.addWidget(QLabel("Toolbar Btn: "))
toolbar.addWidget(QPushButton("Btn"))
self.tree.setIndexWidget(child4.index(), toolbar)
def onTextChanged(self, text):
self.proxyModel.setFilterRegExp(text)
if __name__ == "__main__":
app = QApplication(sys.argv)
view = view()
view.setGeometry(300, 100, 600, 300)
view.setWindowTitle("QTreeview Example")
view.show()
sys.exit(app.exec_())
感谢您的帮助!
问题部分与操作发生的顺序有关。考虑代码...
def createRow(self, parent, i, j):
child1 = QStandardItem("Child {}".format(i * 3 + j))
child2 = QStandardItem("row: {}, col: {}".format(i, j + 1))
child3 = QStandardItem("row: {}, col: {}".format(i, j + 2))
child4 = QStandardItem("")
parent.appendRow([child1, child2, child3, child4])
self.model.appendRow(parent)
toolbar = QToolBar()
toolbar.addWidget(QLabel("Toolbar Btn: "))
toolbar.addWidget(QPushButton("Btn"))
self.tree.setIndexWidget(child4.index(), toolbar)
在 createRow
调用树视图的源模型时是代理 -- 不是 QStandardItemModel
。因此呼吁...
self.tree.setIndexWidget(child4.index(), toolbar)
将失败,因为 child4.index()
返回的 QModelIndex
未被视图识别为属于其源模型。
相反,您需要像这样将 child4.index()
返回的值从 QStandardItemModel
映射到 QSortFilterProxyModel
...
self.tree.setIndexWidget(self.proxyModel.mapFromSource(child4.index()), toolbar)
在旁注中,行...
self.model.appendRow(parent)
in createRow
由于多次添加 parent
导致警告。此行应移出 createRow
并移入 importData
。正确的 importData
和 createRow
实现将是...
def importData(self, root=None):
for i in range(3):
parent1 = QStandardItem("Family {}".format(i))
self.model.appendRow(parent1)
for j in range(3):
self.createRow(parent1, i, j)
def createRow(self, parent, i, j):
child1 = QStandardItem("Child {}".format(i * 3 + j))
child2 = QStandardItem("row: {}, col: {}".format(i, j + 1))
child3 = QStandardItem("row: {}, col: {}".format(i, j + 2))
child4 = QStandardItem("")
parent.appendRow([child1, child2, child3, child4])
toolbar = QToolBar()
toolbar.addWidget(QLabel("Toolbar Btn: "))
toolbar.addWidget(QPushButton("Btn"))
self.tree.setIndexWidget(self.proxyModel.mapFromSource(child4.index()), toolbar)
你的代码存在三个问题。
首先,不应该在 createRow()
函数中添加父项,而是在 importData()
中,并且可能 在 添加子项之前。
Qt 在输出中警告您:
StdErr: QStandardItem::insertRows: Ignoring duplicate insertion of item ...
然后,用于 setIndexWidget()
的索引必须 基于视图的模型(即代理),而您正在使用源模型, 因此索引无效,因为它属于另一个模型。
最后,当使用过滤时,过滤后的索引及其索引小部件将被完全销毁,因此当您取消设置过滤器时,这些小部件将不会恢复。
解决方案是在子索引实际添加到模型时设置小部件,这可以通过连接到 rowsInserted()
信号来完成,在验证父索引有效后(否则它们会是顶级项目)。
请注意,QToolBar 不适用于这种用途,应该使用标准的 QWidget(或 QFrame)。
class view(QWidget):
def __init__(self):
# ...
self.proxyModel.rowsInserted.connect(self.updateWidgets)
def importData(self, root=None):
for i in range(3):
parent1 = QStandardItem("Family {}".format(i))
self.model.appendRow(parent1)
for j in range(3):
self.createRow(parent1, i, j)
def createRow(self, parent, i, j):
child1 = QStandardItem("Child {}".format(i * 3 + j))
child2 = QStandardItem("row: {}, col: {}".format(i, j + 1))
child3 = QStandardItem("row: {}, col: {}".format(i, j + 2))
child4 = QStandardItem("")
parent.appendRow([child1, child2, child3, child4])
def updateWidgets(self, parent, first, last):
if not parent.isValid():
return
for row in range(first, last + 1):
toolbar = QWidget()
layout = QHBoxLayout(toolbar)
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(QLabel("Toolbar Btn: "))
layout.addWidget(QPushButton("Btn"))
childIndex = self.proxyModel.index(row, 3, parent)
self.tree.setIndexWidget(childIndex, toolbar)
注意:类 和常量的名称应始终大写。
我正在 PyQt5 中开发一个包含 QTreeView、QStandardItemModel 和 QSortFilterProxyModel 的应用程序。 TreeView 在最后一列的某些行上也有一个 QToolBar。
我做了一个简化版的例子:
这是源代码:
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
class view(QWidget):
def __init__(self):
super(view, self).__init__()
self.tree = QTreeView(self)
layout = QVBoxLayout(self)
layout.addWidget(self.tree)
self.model = QStandardItemModel()
self.model.setHorizontalHeaderLabels(["Col 0", "Col 1", "Col 2", "Toolbar"])
self.tree.header().setDefaultSectionSize(180)
self.tree.setModel(self.model)
self.importData()
self.tree.expandAll()
def importData(self, root=None):
for i in range(3):
parent1 = QStandardItem("Family {}".format(i))
for j in range(3):
self.createRow(parent1, i, j)
def createRow(self, parent, i, j):
child1 = QStandardItem("Child {}".format(i * 3 + j))
child2 = QStandardItem("row: {}, col: {}".format(i, j + 1))
child3 = QStandardItem("row: {}, col: {}".format(i, j + 2))
child4 = QStandardItem("")
parent.appendRow([child1, child2, child3, child4])
self.model.appendRow(parent)
toolbar = QToolBar()
toolbar.addWidget(QLabel("Toolbar Btn: "))
toolbar.addWidget(QPushButton("Btn"))
self.tree.setIndexWidget(child4.index(), toolbar)
if __name__ == "__main__":
app = QApplication(sys.argv)
view = view()
view.setGeometry(300, 100, 600, 300)
view.setWindowTitle("QTreeview Example")
view.show()
sys.exit(app.exec_())
现在我想使用 QLineEdit 小部件和 QSortFilterProxyModel 添加过滤器,但是正如您在下面看到的那样,工具栏被删除了。有人可以解释为什么以及如何解决这个问题吗?
到目前为止,这是我的代码:
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
class view(QWidget):
def __init__(self):
super(view, self).__init__()
self.tree = QTreeView(self)
layout = QVBoxLayout(self)
self.filter = QLineEdit()
self.filter.textChanged.connect(self.onTextChanged)
layout.addWidget(self.filter)
layout.addWidget(self.tree)
self.model = QStandardItemModel()
self.model.setHorizontalHeaderLabels(["Col 0", "Col 1", "Col 2", "Toolbar"])
self.proxyModel = QSortFilterProxyModel(
self.tree, recursiveFilteringEnabled=True
)
self.proxyModel.setSourceModel(self.model)
self.tree.header().setDefaultSectionSize(180)
self.tree.setModel(self.proxyModel)
self.importData()
self.tree.expandAll()
def importData(self, root=None):
for i in range(3):
parent1 = QStandardItem("Family {}".format(i))
for j in range(3):
self.createRow(parent1, i, j)
def createRow(self, parent, i, j):
child1 = QStandardItem("Child {}".format(i * 3 + j))
child2 = QStandardItem("row: {}, col: {}".format(i, j + 1))
child3 = QStandardItem("row: {}, col: {}".format(i, j + 2))
child4 = QStandardItem("")
parent.appendRow([child1, child2, child3, child4])
self.model.appendRow(parent)
toolbar = QToolBar()
toolbar.addWidget(QLabel("Toolbar Btn: "))
toolbar.addWidget(QPushButton("Btn"))
self.tree.setIndexWidget(child4.index(), toolbar)
def onTextChanged(self, text):
self.proxyModel.setFilterRegExp(text)
if __name__ == "__main__":
app = QApplication(sys.argv)
view = view()
view.setGeometry(300, 100, 600, 300)
view.setWindowTitle("QTreeview Example")
view.show()
sys.exit(app.exec_())
感谢您的帮助!
问题部分与操作发生的顺序有关。考虑代码...
def createRow(self, parent, i, j):
child1 = QStandardItem("Child {}".format(i * 3 + j))
child2 = QStandardItem("row: {}, col: {}".format(i, j + 1))
child3 = QStandardItem("row: {}, col: {}".format(i, j + 2))
child4 = QStandardItem("")
parent.appendRow([child1, child2, child3, child4])
self.model.appendRow(parent)
toolbar = QToolBar()
toolbar.addWidget(QLabel("Toolbar Btn: "))
toolbar.addWidget(QPushButton("Btn"))
self.tree.setIndexWidget(child4.index(), toolbar)
在 createRow
调用树视图的源模型时是代理 -- 不是 QStandardItemModel
。因此呼吁...
self.tree.setIndexWidget(child4.index(), toolbar)
将失败,因为 child4.index()
返回的 QModelIndex
未被视图识别为属于其源模型。
相反,您需要像这样将 child4.index()
返回的值从 QStandardItemModel
映射到 QSortFilterProxyModel
...
self.tree.setIndexWidget(self.proxyModel.mapFromSource(child4.index()), toolbar)
在旁注中,行...
self.model.appendRow(parent)
in createRow
由于多次添加 parent
导致警告。此行应移出 createRow
并移入 importData
。正确的 importData
和 createRow
实现将是...
def importData(self, root=None):
for i in range(3):
parent1 = QStandardItem("Family {}".format(i))
self.model.appendRow(parent1)
for j in range(3):
self.createRow(parent1, i, j)
def createRow(self, parent, i, j):
child1 = QStandardItem("Child {}".format(i * 3 + j))
child2 = QStandardItem("row: {}, col: {}".format(i, j + 1))
child3 = QStandardItem("row: {}, col: {}".format(i, j + 2))
child4 = QStandardItem("")
parent.appendRow([child1, child2, child3, child4])
toolbar = QToolBar()
toolbar.addWidget(QLabel("Toolbar Btn: "))
toolbar.addWidget(QPushButton("Btn"))
self.tree.setIndexWidget(self.proxyModel.mapFromSource(child4.index()), toolbar)
你的代码存在三个问题。
首先,不应该在 createRow()
函数中添加父项,而是在 importData()
中,并且可能 在 添加子项之前。
Qt 在输出中警告您:
StdErr: QStandardItem::insertRows: Ignoring duplicate insertion of item ...
然后,用于 setIndexWidget()
的索引必须 基于视图的模型(即代理),而您正在使用源模型, 因此索引无效,因为它属于另一个模型。
最后,当使用过滤时,过滤后的索引及其索引小部件将被完全销毁,因此当您取消设置过滤器时,这些小部件将不会恢复。
解决方案是在子索引实际添加到模型时设置小部件,这可以通过连接到 rowsInserted()
信号来完成,在验证父索引有效后(否则它们会是顶级项目)。
请注意,QToolBar 不适用于这种用途,应该使用标准的 QWidget(或 QFrame)。
class view(QWidget):
def __init__(self):
# ...
self.proxyModel.rowsInserted.connect(self.updateWidgets)
def importData(self, root=None):
for i in range(3):
parent1 = QStandardItem("Family {}".format(i))
self.model.appendRow(parent1)
for j in range(3):
self.createRow(parent1, i, j)
def createRow(self, parent, i, j):
child1 = QStandardItem("Child {}".format(i * 3 + j))
child2 = QStandardItem("row: {}, col: {}".format(i, j + 1))
child3 = QStandardItem("row: {}, col: {}".format(i, j + 2))
child4 = QStandardItem("")
parent.appendRow([child1, child2, child3, child4])
def updateWidgets(self, parent, first, last):
if not parent.isValid():
return
for row in range(first, last + 1):
toolbar = QWidget()
layout = QHBoxLayout(toolbar)
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(QLabel("Toolbar Btn: "))
layout.addWidget(QPushButton("Btn"))
childIndex = self.proxyModel.index(row, 3, parent)
self.tree.setIndexWidget(childIndex, toolbar)
注意:类 和常量的名称应始终大写。