QScrollArea:从一个项目滚动到另一个项目

2024-04-20 01:43:44 发布

您现在位置:Python中文网/ 问答频道 /正文

请考虑以下代码:

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, 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),
            0,
            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

这并不是真正的破坏,但我在更大的项目中看不到它,所以我想有些东西并没有按它应该的方式工作


Tags: fromimportselfeventsizedefwidgetqt
1条回答
网友
1楼 · 发布于 2024-04-20 01:43:44

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

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

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

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

下面是一个可能的实现(为了正确显示结果,我更改了项的创建):

from random import randrange

class Gallery(QAbstractScrollArea):

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

        self.setFixedWidth(175)
        self.items = []
        self.currentIndex = -1

        for _ in range(10):
            widget = QLabel(str(len(self.items) + 1), 
                self, alignment=Qt.AlignCenter)
            widget.setStyleSheet('background: #{:02x}{:02x}{:02x}'.format(
                randrange(255), randrange(255), randrange(255)))
            widget.setFixedSize(randrange(60, 100), randrange(50, 200))
            self.addItem(widget)

    def addItem(self, widget):
        self.insertItem(len(self.items), widget)

    def insertItem(self, index, widget):
        widget.setParent(self.viewport())
        widget.show()
        self.items.insert(index, widget)
        if len(self.items) == 1:
            self.currentIndex = 0
        self.updateGeometry()

    def setCurrentIndex(self, index):
        if not self.items:
            self.currentIndex = -1
            return
        self.currentIndex = max(0, min(index, len(self.items) - 1))
        self.updateGeometry()

    def stepBy(self, step):
        self.setCurrentIndex(self.currentIndex + step)

    def updateGeometry(self):
        super().updateGeometry()
        if not self.items:
            return
        rects = []
        y = 0
        for i, widget in enumerate(self.items):
            rect = widget.rect()
            rect.moveTop(y)
            rects.append(rect)
            if i == self.currentIndex:
                centerY = rect.center().y()
            y = rect.bottom() + 25
        centerY -= self.height() / 2
        centerX = self.width() / 2
        for widget, rect in zip(self.items, rects):
            widget.setGeometry(rect.translated(centerX - rect.width() / 2, -centerY))

    def sizeHint(self):
        return QSize(175, 400)

    def resizeEvent(self, event: QResizeEvent):
        self.updateGeometry()

    def wheelEvent(self, event):
        if event.angleDelta().y() < 0:
            self.stepBy(1)
        else:
            self.stepBy(-1)

相关问题 更多 >