【发布时间】:2022-01-03 19:14:05
【问题描述】:
我需要一个QTabWidget,其中每个选项卡都包含一个QTreeView。每个QTreeView 显示
更大模型的一个“分支”。用户有一个QLineEdit,它可以实时过滤
视图基于他们键入的任何内容。我在下面复制了 GUI + 问题。
问题是我使用QTreeView::setRootIndex 来显示单曲,
主模型的内部分支。 QLineEdit“过滤器”是用
QSortFilterProxyModel。每当用户键入时,我都会调用
QSortFilterProxyModel::invalidateFilter 从头开始重新过滤。这两个
方法,QTreeView::setRootIndex 和 QSortFilterProxyModel::invalidateFilter,不要
混合均匀。
当QSortFilterProxyModel::invalidateFilter 被调用时,我拥有的索引
以前使用QTreeView::setRootIndex 设置现在无效。然后QTreeView
显示整个树,而不是我之前使用setRootIndex 选择的“分支”。 QTreeView 有效地“忘记”了我设置的根索引。
我复制了下面的问题
import functools
from Qt import QtCore, QtWidgets
_NO_ROW = -1
_NUMBER_OF_BRANCHES = 3
class _MatchProxy(QtCore.QSortFilterProxyModel):
"""Use a callable function to determine if an index should be hidden from view or not."""
def __init__(self, matcher, parent=None):
super(_MatchProxy, self).__init__(parent=parent)
self._matcher = matcher
def filterAcceptsRow(self, source_row, source_index):
return self._matcher(source_index)
class _MultiTree(QtWidgets.QWidget):
"""A widget which makes smaller, individual QTreeViews for each section of an given index."""
def __init__(self, source_index, parent=None):
super(_MultiTree, self).__init__(parent=parent)
self.setLayout(QtWidgets.QVBoxLayout())
model = source_index.model()
self._views = []
for index in range(model.rowCount(source_index)):
section = model.index(index, 0, parent=source_index)
label = model.data(section, QtCore.Qt.DisplayRole)
view = QtWidgets.QTreeView()
view.setModel(model)
view.setRootIndex(section)
self.layout().addWidget(QtWidgets.QLabel(label))
self.layout().addWidget(view)
self._views.append(view)
def iter_models(self):
for view in self._views:
yield view.model()
def iter_views(self):
for view in self._views:
yield view
class Node(object):
"""A generic name + children + parent graph node class."""
def __init__(self, name, parent=None):
super(Node, self).__init__()
self._name = name
self._children = []
self._parent = parent
if self._parent:
self._parent.add_child(self)
def add_child(self, node):
node._parent = self
self._children.append(node)
def get_child(self, row):
return self._children[row]
def get_children(self):
return list(self._children)
def get_label(self):
return self._name
def get_parent(self):
return self._parent
def get_row(self):
parent = self.get_parent()
if not parent:
return _NO_ROW
return parent.get_children().index(self)
def __repr__(self):
return "{self.__class__.__name__}({self._name!r}, parent={self._parent!r})".format(
self=self
)
class Branch(Node):
"""Syntax sugar for debugging. This class isn't "necessary" for the reproduction."""
pass
class Model(QtCore.QAbstractItemModel):
"""The basic Qt model which contains the entire tree, including each branch."""
def __init__(self, roots, parent=None):
super(Model, self).__init__(parent=parent)
self._roots = roots
def _get_node(self, index):
return index.internalPointer()
def columnCount(self, _):
return 1
def data(self, index, role=QtCore.Qt.DisplayRole):
if role != QtCore.Qt.DisplayRole:
return None
node = self._get_node(index)
return node.get_label()
def index(self, row, column, parent=QtCore.QModelIndex()):
if not parent.isValid():
return self.createIndex(row, 0, self._roots[row])
parent_node = self._get_node(parent)
child_node = parent_node.get_child(row)
return self.createIndex(row, column, child_node)
def parent(self, index):
if not index.isValid():
return QtCore.QModelIndex()
node = self._get_node(index)
parent = node.get_parent()
if not parent:
return QtCore.QModelIndex()
return self.createIndex(node.get_row(), 0, parent)
def rowCount(self, index):
if not index.isValid():
return len(self._roots)
node = self._get_node(index)
return len(node.get_children())
class Widget(QtWidgets.QWidget):
"""The main widget / window which has the filterer + QTreeViews."""
def __init__(self, model, parent=None):
super(Widget, self).__init__(parent=parent)
self.setLayout(QtWidgets.QVBoxLayout())
self._filterer = QtWidgets.QLineEdit()
self._tabs = QtWidgets.QTabWidget()
self._set_model(model)
self.layout().addWidget(self._filterer)
self.layout().addWidget(self._tabs)
self._filterer.textChanged.connect(self._update_current_view)
def _replace_model_with_filterer_proxy(self, view):
def _match(line_edit, index):
if not index.isValid():
return True # Show everything, don't filter anything
current = line_edit.text().strip()
if not current:
return True # Show everything, don't filter anything.
return current in index.data(QtCore.Qt.DisplayRole)
model = view.model()
current_root = view.rootIndex()
proxy = _MatchProxy(functools.partial(_match, self._filterer))
proxy.setSourceModel(model)
proxy.setRecursiveFilteringEnabled(True)
view.setModel(proxy)
view.setRootIndex(proxy.mapFromSource(current_root))
def _set_model(self, model):
tabs_count = model.rowCount(QtCore.QModelIndex())
for row in range(tabs_count):
branch_index = model.index(row, 0)
tab_name = model.data(branch_index, QtCore.Qt.DisplayRole)
widget = _MultiTree(branch_index)
for view in widget.iter_views():
self._replace_model_with_filterer_proxy(view)
self._tabs.addTab(widget, tab_name)
def _update_current_view(self):
widget = self._tabs.currentWidget()
for proxy in widget.iter_models():
proxy.invalidateFilter()
def _make_branch_graph():
default = Node("default")
optional = Node("optional")
Node("camera", parent=default)
Node("set", parent=default)
light = Node("light", parent=default)
Node("directional light", parent=light)
spot = Node("spot light", parent=light)
Node("light center", parent=spot)
Node("volume light", parent=light)
Node("model", parent=optional)
surfacing = Node("surfacing", parent=optional)
Node("look", parent=surfacing)
Node("hair", parent=surfacing)
Node("fur", parent=surfacing)
Node("rig", parent=optional)
return (default, optional)
def _make_full_graph():
roots = []
for index in range(_NUMBER_OF_BRANCHES):
branch = Branch("branch_{index}".format(index=index))
for node in _make_branch_graph():
branch.add_child(node)
roots.append(branch)
return roots
def main():
application = QtWidgets.QApplication([])
roots = _make_full_graph()
model = Model(roots)
window = Widget(model)
window.show()
application.exec_()
if __name__ == "__main__":
main()
- 运行上面的代码打开 GUI。选项卡 + 内部 QTreeView 应如下所示:
QTabWidget tab - branch_0
QTreeView (default)
- camera
- set
- light
- directional light
- spot light
- light center
- volume light
QTreeView (optional)
- model
- surfacing
- look
- hair
- fur
- rig
QTabWidget tab - branch_1
- Same as branch_0
QTabWidget tab - branch_2
- Same as branch_0
这是正确的、预期的节点显示。现在在过滤器 QLineEdit 中,输入 在光明中”。您现在将获得:
QTabWidget tab - branch_0
QTreeView (default)
- light
- directional light
- spot light
- light center
- volume light
QTreeView (optional)
- branch_0
- default
- light
- directional light
- spot light
- light center
- volume light
- branch_1
- default
- light
- directional light
- spot light
- light center
- volume light
- branch_2
- default
- light
- directional light
- spot light
- light center
- volume light
QTabWidget tab - branch_1
- Same as branch_0
QTabWidget tab - branch_2
- Same as branch_0
在这里您可以看到标记为“可选”的 QTreeView 现在显示 每个分支的内容,而不仅仅是应该的一个分支。
这不是预期的行为。作为参考,这是我的观点 希望得到:
QTabWidget tab - branch_0
QTreeView (default)
- default
- light
- directional light
- spot light
- light center
- volume light
QTreeView (optional) [EMPTY, no children]
QTabWidget tab - branch_1
- Same as branch_0
QTabWidget tab - branch_2
- Same as branch_0
还要注意,如果你清除过滤器 QLineEdit 的文本,“”,你不会去 回到第一张图。 “可选”QTreeView 被卡住了 一切,在每个 QTreeView 中。
现在在过滤器 QLineEdit 文本中,键入“asdf”。现在图表是
QTabWidget tab - branch_0
QTreeView (default)
- branch_0
- branch_1
- branch_2
QTreeView (optional)
- branch_0
- branch_1
- branch_2
QTabWidget tab - branch_1
- Same as branch_0
QTabWidget tab - branch_2
- Same as branch_0
当我的预期视图是
QTabWidget tab - branch_0
QTreeView (default) [EMPTY]
QTreeView (optional) [EMPTY]
QTabWidget tab - branch_1
- Same as branch_0
QTabWidget tab - branch_2
- Same as branch_0
如果你用“”清除过滤器文本,现在两个 QTreeView 都会显示所有分支的所有内容。
有没有一种简单的方法来获得我所描述的预期图表?可能是 有一种方法可以在不使索引无效的情况下重新运行 QSortFilterProxyModel,或者 我可以使用其他一些机制来获得相同的效果吗?此刻我在 通过“保存和恢复”每个的 rootIndex 来解决问题 查看、前和后 invalidateFilter。但是我的方法不起作用 所有情况,感觉就像一个黑客。
对此的任何建议将不胜感激。
【问题讨论】:
-
请不要编辑问题来提供答案,因为这会使帖子变得混乱。如果您不确定建议的答案是否有效,请等到您可以测试它并然后接受它。如果你有一个不同的解决方案,那么就用它创建一个答案,但如果你只是想展示现有答案是如何在你的代码中实现的,就不要这样做。
-
我必须对其进行编辑,因为我测试了您的代码,但它并不能很好地工作。就是说,我想给你接受的答案,因为你一定在这个答案上很努力。但是,如果您希望我单独回复,那么我会为您做。 FWIW 我看到其他人在他们的问题中提供了解决方案,这并不令人困惑。在那些情况下,他们的代码要短得多。
-
您可以在问题中看到答案这一事实仅意味着没有人负责通知它或回滚。问题字段用于问题,答案必须在答案部分,这不是我的偏好,而是应该如何使用网站。也就是说,如果某些东西在答案中不起作用,请使用 cmets 指定 what 不起作用:答案不是最终的,也可能是错误的;通知作者这对您、作者(可能会学到新东西或意识到自己的错误)以及其他读者都有用。
-
另外,假设你的答案更正确,你至少应该解释一下为什么。在没有任何解释的情况下发布完整代码似乎对您很有用,但通过这种方式,您会迫使其他人比较原始代码和新代码,找出差异并尝试理解它们的为什么制成。抱歉,但现在您的答案似乎没有添加任何有用的信息来证明 不同 答案的合理性,因为修改似乎是由原始问题中的问题引起的,并且与实际问题没有真正的关系(根索引的有效性)。
标签: python qt model-view-controller pyqt pyside