QScrollArea:从项目滚动到项目

问题描述

请考虑以下代码

from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *


class gallery(QScrollArea):

    def __init__(self,*args,**kwargs):
        super().__init__(*args,**kwargs)

        self.setFixedWidth(175)
        self.setSizePolicy(QSizePolicy.Ignored,QSizePolicy.Expanding)
        self.setVerticalScrollBarPolicy(Qt.ScrollBaralwaysOff)
        self.setHorizontalScrollBarPolicy(Qt.ScrollBaralwaysOff)

        # Set widget and layout
        self._scroll_widget = QWidget()

        self._layout = QVBoxLayout()
        self._layout.setContentsMargins(0,0)
        self._layout.setSpacing(25)
        self._scroll_widget.setLayout(self._layout)

        self.setWidget(self._scroll_widget)
        self.setWidgetResizable(True)

        # Stretch
        self._layout.addStretch(1)  # Stretch above widgets
        self._layout.addStretch(1)  # Stretch below widgets

        # Initialize ---------------------------------|
        for _ in range(10):
            self.add_item()

    def resizeEvent(self,event: QResizeEvent) -> None:
        super().resizeEvent(event)

        # Calculate Margins --------------------|
        children = self._scroll_widget.findChildren(QLabel)
        first_widget = children[0]
        last_widget = children[-1]

        self._layout.setContentsMargins(
            0,int(event.size().height() / 2 - first_widget.size().height() / 2),int(event.size().height() / 2 - last_widget.size().height() / 2)
        )

    def add_item(self) -> None:
        widget = QLabel()
        widget.setStyleSheet('background: #22FF88')
        widget.setFixedSize(90,125)

        child_count = len(
            self._scroll_widget.findChildren(QLabel)
        )
        self._layout.insertWidget(1 + child_count,widget,alignment=Qt.AlignCenter)


if __name__ == '__main__':
    app = QApplication([])
    window = gallery()
    window.show()
    app.exec()

目前,布局的边距是动态设置的,因此无论窗口大小如何,第一项和最后一项始终垂直居中:

Scrollbar containing multiple green rectangles,the first is vertically centered

我现在想要实现的是,每当我滚动(通过鼠标滚轮或箭头键,因为滚动条被禁用)下一个小部件应该占据垂直中心的位置,即我想切换滚动从每像素到基于每小部件的模式,这样无论我滚动多远,我都不会落在两个小部件之间.

如何做到这一点?

我发现 QAbstractItemView 提供了将 ScrollMode 切换为 ScrollPerItem 的选项,但我不确定这是否是我需要的,因为我有点不知所措尝试子类化 QAbstractItemView


编辑:
显示了我在调整 @musicamante 的回答后注意到的延迟:

GIF showing a short delay before the widgets are drawn

这并不是真正的干扰,但我在大型项目中看不到它,所以我想有些事情没有按预期工作。

解决方法

由于 QScrollArea 提供的大部分功能实际上都将被忽略,因此从它继承子类并不会带来很多好处。相反,它可能会使事情变得更加复杂。

此外,使用布局并不是很有用:小部件的“容器”不受滚动区域的限制,在这种情况下,所有用于大小提示和调整大小的功能几乎都没用。

一个解决方案可能是将所有项目设置为“滚动区域”的子项,甚至可以是基本的 QWidget 或 QFrame,但为了更好的样式支持,我选择使用 QAbstractScrollArea。

诀窍是根据其几何形状计算每个子小部件的正确位置。请注意,我假设所有小部件都具有固定大小,否则您可能需要使用它们的 sizeHint、minimumSizeHint 或最小尺寸,并检查它们的尺寸策略。

这是一个可能的实现(我更改了项目创建以正确显示结果):

vimgd <commit1> <commit2>