【问题标题】:PyQt - Oriented Flow LayoutPyQt - 面向流布局
【发布时间】:2021-06-10 04:55:27
【问题描述】:

我正在尝试调整this PyQt implementation of FlowLayout 以允许垂直流动和水平流动。这是我当前的实现:

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


class FlowLayout(QLayout):
    def __init__(self, orientation=Qt.Horizontal, parent=None, margin=0, spacing=-1):
        super().__init__(parent)
        self.orientation = orientation

        if parent is not None:
            self.setContentsMargins(margin, margin, margin, margin)

        self.setSpacing(spacing)

        self.itemList = []

    def __del__(self):
        item = self.takeAt(0)
        while item:
            item = self.takeAt(0)

    def addItem(self, item):
        self.itemList.append(item)

    def count(self):
        return len(self.itemList)

    def itemAt(self, index):
        if index >= 0 and index < len(self.itemList):
            return self.itemList[index]

        return None

    def takeAt(self, index):
        if index >= 0 and index < len(self.itemList):
            return self.itemList.pop(index)

        return None

    def expandingDirections(self):
        return Qt.Orientations(Qt.Orientation(0))

    def hasHeightForWidth(self):
        return self.orientation == Qt.Horizontal

    def heightForWidth(self, width):
        return self.doLayout(QRect(0, 0, width, 0), True)

    def hasWidthForHeight(self):
        return self.orientation == Qt.Vertical

    def widthForHeight(self, height):
        return self.doLayout(QRect(0, 0, 0, height), True)

    def setGeometry(self, rect):
        super().setGeometry(rect)
        self.doLayout(rect, False)

    def sizeHint(self):
        return self.minimumSize()

    def minimumSize(self):
        size = QSize()

        for item in self.itemList:
            size = size.expandedTo(item.minimumSize())

        margin, _, _, _ = self.getContentsMargins()

        size += QSize(2 * margin, 2 * margin)
        return size

    def doLayout(self, rect, testOnly):
        x = rect.x()
        y = rect.y()
        offset = 0
        horizontal = self.orientation == Qt.Horizontal

        for item in self.itemList:
            wid = item.widget()
            spaceX = self.spacing() + wid.style().layoutSpacing(QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Horizontal)
            spaceY = self.spacing() + wid.style().layoutSpacing(QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Vertical)

            if horizontal:
                next = x + item.sizeHint().width() + spaceX
                if next - spaceX > rect.right() and offset > 0:
                    x = rect.x()
                    y += offset + spaceY
                    next = x + item.sizeHint().width() + spaceX
                    offset = 0
            else:
                next = y + item.sizeHint().height() + spaceY
                if next - spaceY > rect.bottom() and offset > 0:
                    x += offset + spaceX
                    y = rect.y()
                    next = y + item.sizeHint().height() + spaceY
                    offset = 0

            if not testOnly:
                item.setGeometry(QRect(QPoint(x, y), item.sizeHint()))

            if horizontal:
                x = next
                offset = max(offset, item.sizeHint().height())
            else:
                y = next
                offset = max(offset, item.sizeHint().width())

        return y + offset - rect.y() if horizontal else x + offset - rect.x()


if __name__ == '__main__':
    class Window(QWidget):
        def __init__(self):
            super().__init__()

            #flowLayout = FlowLayout(orientation=Qt.Horizontal)
            flowLayout = FlowLayout(orientation=Qt.Vertical)
            flowLayout.addWidget(QPushButton("Short"))
            flowLayout.addWidget(QPushButton("Longer"))
            flowLayout.addWidget(QPushButton("Different text"))
            flowLayout.addWidget(QPushButton("More text"))
            flowLayout.addWidget(QPushButton("Even longer button text"))
            self.setLayout(flowLayout)

            self.setWindowTitle("Flow Layout")

    import sys

    app = QApplication(sys.argv)
    mainWin = Window()
    mainWin.show()
    sys.exit(app.exec_())

这个实现在处理垂直布局时有 2 个(可能相关的)问题:

  1. QLayout 具有 hasHeightForWidthheightForWidth 方法,但没有它们的逆方法 hasWidthForHeightwidthForHeight。无论如何,我都实现了后两种方法,但我怀疑它们是否真的被调用过。
  2. 当使用布局的水平变体时,窗口会自动调整大小以包含所有项目。使用垂直变体时,情况并非如此。但是,如果您手动调整窗口大小,垂直布局确实可以正常工作。

如何正确实现垂直流布局?

【问题讨论】:

    标签: python-3.x pyqt pyqt5 flowlayout


    【解决方案1】:

    正如您已经发现的那样,Qt 布局不支持 widthForHeight,并且通常不鼓励使用此类布局,主要是因为它们在嵌套布局和混合窗口小部件大小策略的复杂情况下往往表现不规则。即使对它们的实现非常小心,您最终也可能会递归调用大小提示、策略等。

    也就是说,部分解决方案是仍然返回一个高度作为宽度,但垂直放置小部件而不是水平放置。

        def doLayout(self, rect, testOnly):
            x = rect.x()
            y = rect.y()
            lineHeight = columnWidth = heightForWidth = 0
    
            for item in self.itemList:
                wid = item.widget()
                spaceX = self.spacing() + wid.style().layoutSpacing(QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Horizontal)
                spaceY = self.spacing() + wid.style().layoutSpacing(QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Vertical)
                if self.orientation == Qt.Horizontal:
                    nextX = x + item.sizeHint().width() + spaceX
                    if nextX - spaceX > rect.right() and lineHeight > 0:
                        x = rect.x()
                        y = y + lineHeight + spaceY
                        nextX = x + item.sizeHint().width() + spaceX
                        lineHeight = 0
    
                    if not testOnly:
                        item.setGeometry(QRect(QPoint(x, y), item.sizeHint()))
    
                    x = nextX
                    lineHeight = max(lineHeight, item.sizeHint().height())
                else:
                    nextY = y + item.sizeHint().height() + spaceY
                    if nextY - spaceY > rect.bottom() and columnWidth > 0:
                        x = x + columnWidth + spaceX
                        y = rect.y()
                        nextY = y + item.sizeHint().height() + spaceY
                        columnWidth = 0
    
                    heightForWidth += item.sizeHint().height() + spaceY
                    if not testOnly:
                        item.setGeometry(QRect(QPoint(x, y), item.sizeHint()))
    
                    y = nextY
                    columnWidth = max(columnWidth, item.sizeHint().width())
    
            if self.orientation == Qt.Horizontal:
                return y + lineHeight - rect.y()
            else:
                return heightForWidth - rect.y()
    

    这是小部件一显示就出现的方式(几乎与水平流相同):

    现在,调整大小以减少垂直空间:

    甚至更小的高度:

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-11-23
      • 2018-08-14
      • 2017-05-10
      • 2011-11-18
      • 2022-01-19
      • 2016-09-30
      • 2014-07-09
      相关资源
      最近更新 更多