Qt: QTreeView 可视区域内节点判断与滚动事件

Qt: QTreeView 可视区域内节点判断与滚动事件

这两天遇到两个问题:

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) -> QModelIndexindexBelow(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

 

Leave a Reply

Your email address will not be published. Required fields are marked *

TOC