这两天遇到两个问题:
1、对于一个 QTreeView,如何判断一个节点的 QModelIndex 是否处于 QTreeView 可视区域内。当 QTreeView 中有太多节点,必然有些节点是处于可视区域外部的,不显示的。QTreeView 也不会对这些不显示的节点调用 itemdelegate.paint() 方法。
2、如何获取 QTreeView 内容的滚动事件。
不只是对 QTreeView,所有的 QAbstractItemView 派生类,包括 QListView, QTableView 也都如此。
一、visualRect(index)
QAbstractItemView.visualRect(index: QModelIndex) -> Qrect
返回 index 节点相对于可视区域 QAbstractItemView.viewport()
的位置矩形,即使节点在可视区域之外。(官方文档)
比如 viewport 是 (x=0, y=0, width=200, height=300),如果节点的 visualRect(index) 返回的是 (0, -50, 200, 30),那么这个节点就是在 viewport 之上的不可见区域。
所以,可以根据 visualRect.y 来判断节点是否处于可视区域。y < -height 或者 y > viewport.rect.height 的,都是在超出的不可见区域。
更简单的,可以使用 QRect.intersects(otherRect)
方法来判断 visualRect 与 viewport.rect 这两个矩形是否有交叉。如果有,就说明节点(部分/全部)处于可视区域。
intersect = treeView.visualRect(idx).intersects(treeView.viewport().rect()) logging.debug('%s 节点是否处于可视区域: %s', idx.data(), intersect)
二、indexAt(point), indexAbove(index), indexBelow(index)
QAbstractItemView.indexAt(point: QPoint) -> QModelIndex
返回 viewport 中处于 point 位置的节点 index。(官方文档)
初次之外,QTreeView 还提供了两个特有的方法: indexAbove(index) -> QModelIndex
和 indexBelow(index) -> QModelIndex
用来返回位置紧邻着 index 之上,或之下的节点。如果 index 本来就是最顶端节点,没有 above,或者 index 本来就是最底部节点,没有 below。那么就会返回一个无效的 QModelIndex 实例 idx,用 idx.isValid()
判断返回 False,用 idx.data()
返回 None。
idx_topleft = self.treeView.indexAt(self.treeView.viewport().rect().topLeft()) # 取可视区域顶部位置的节点 idx_bottomright = self.treeView.indexAt(self.treeView.viewport().rect().bottomRight()) # 取可视区域底部位置的节点,如果没有,则返回一个无效 QModelIndex self.logger.info('左上节点: %s, 右下节点: %s', idx_topleft.data(), idx_bottomright.data()) self.logger.info('上上: %s, 下下: %s', self.treeView.indexAbove(idx_topleft).isValid(), self.treeView.indexBelow(idx_bottomright).isValid())
三、verticalScrollBar(), horizontalScrollBar()
我一开始在 QTreeView, QAbstractitemView, QAbstractScrollArea 的官方文档里找了半天,也没有找到任何关于可视区域滚动的信号。后来也不知道怎么搜的,才发现原来 QAbstractitemView 是通过 verticalScrollBar / horizontalScrollBar 来触发滚动条滚动信号!!!😤 (ScrollBar 信号)
self.treeView.verticalScrollBar().valueChanged.connect(self._on_v_scroll_changed) def _on_v_scroll_changed(self, value): self.logger.debug('V scroll bar value changed to: %s', value) pass