PyQt - 面向流布局 [英] PyQt - Oriented Flow Layout

查看:14
本文介绍了PyQt - 面向流布局的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试调整

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

甚至更小的高度:

I'm trying to adapt this PyQt implementation of FlowLayout to allow vertical flow as well as horizontal. This is my current implementation:

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

This implementation has 2 (likely related) problems when handling vertical layouts:

  1. QLayout has the hasHeightForWidth and heightForWidth methods, but not their inverses hasWidthForHeight and widthForHeight. I implemented the latter two methods regardless, but I doubt they're ever actually getting called.
  2. When using the horizontal variant of the layout, the window is automatically appropriately sized to contain all the items. When using the vertical variant, this is not the case. However, the vertical layout does work properly if you manually resize the window.

How do I properly implement a vertical flow layout?

解决方案

As you already found out, Qt layouts don't support widthForHeight, and, in general, these kinds of layouts are discouraged, mostly because they tend to behave erratically in complex situation with nested layouts and mixed widget size policies. Even when being very careful about their implementation, you might end up in recursive calls to size hints, policies etc.

That said, a partial solution is to still return a height for width, but position the widgets vertically instead of horizontally.

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

This is how the widget appears as soon as it's shown (which is almost the same as the horizontal flow):

Now, resizing to allow less vertical space:

And even smaller height:

这篇关于PyQt - 面向流布局的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆