带有 FlowLayout 小部件的 QScrollArea 无法正确调整大小

2023-11-30

我想创建一个类似于 KDE(或 Gnome 或 MacOS)系统设置的小部件(例如,像这张图片)

我已经实现了 FlowLayoutQt 文档示例.

如果我将一些 FlowLayout 小部件(用 QVBoxLayout 包装在容器小部件中)放入 QScrollArea 中并调整 QSrollArea 的大小,则所有内容都会按预期流动并重新布局。

However,如果我增加滚动区域的宽度以使其需要更少的高度,则滚动区域仍然认为其小部件需要 其最小宽度的原始高度:

0_1507630989642_flow_layout_in_scrollarea.gif

如何使用其子级的实际高度更新滚动区域,以便垂直滚动条在不再需要时消失?

下面,您将找到 FlowLayout 的 (Python) 实现,并在__main__阻止实际的例子。

干杯, 斯特凡

"""
PyQt5 port of the `layouts/flowlayout
<https://doc.qt.io/qt-5/qtwidgets-layouts-flowlayout-example.html>`_ example
from Qt5.

Usage:

    python3 -m pip install pyqt5
    python3 flow_layout.py

"""
from PyQt5.QtCore import pyqtSignal, QPoint, QRect, QSize, Qt
from PyQt5.QtWidgets import QLayout, QSizePolicy, QSpacerItem


class FlowLayout(QLayout):
    """A ``QLayout`` that aranges its child widgets horizontally and
    vertically.

    If enough horizontal space is available, it looks like an ``HBoxLayout``,
    but if enough space is lacking, it automatically wraps its children into
    multiple rows.

    """
    heightChanged = pyqtSignal(int)

    def __init__(self, parent=None, margin=0, spacing=-1):
        super().__init__(parent)
        if parent is not None:
            self.setContentsMargins(margin, margin, margin, margin)
        self.setSpacing(spacing)

        self._item_list = []

    def __del__(self):
        while self.count():
            self.takeAt(0)

    def addItem(self, item):  # pylint: disable=invalid-name
        self._item_list.append(item)

    def addSpacing(self, size):  # pylint: disable=invalid-name
        self.addItem(QSpacerItem(size, 0, QSizePolicy.Fixed, QSizePolicy.Minimum))

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

    def itemAt(self, index):  # pylint: disable=invalid-name
        if 0 <= index < len(self._item_list):
            return self._item_list[index]
        return None

    def takeAt(self, index):  # pylint: disable=invalid-name
        if 0 <= index < len(self._item_list):
            return self._item_list.pop(index)
        return None

    def expandingDirections(self):  # pylint: disable=invalid-name,no-self-use
        return Qt.Orientations(Qt.Orientation(0))

    def hasHeightForWidth(self):  # pylint: disable=invalid-name,no-self-use
        return True

    def heightForWidth(self, width):  # pylint: disable=invalid-name
        height = self._do_layout(QRect(0, 0, width, 0), True)
        return height

    def setGeometry(self, rect):  # pylint: disable=invalid-name
        super().setGeometry(rect)
        self._do_layout(rect, False)

    def sizeHint(self):  # pylint: disable=invalid-name
        return self.minimumSize()

    def minimumSize(self):  # pylint: disable=invalid-name
        size = QSize()

        for item in self._item_list:
            minsize = item.minimumSize()
            extent = item.geometry().bottomRight()
            size = size.expandedTo(QSize(minsize.width(), extent.y()))

        margin = self.contentsMargins().left()
        size += QSize(2 * margin, 2 * margin)
        return size

    def _do_layout(self, rect, test_only=False):
        m = self.contentsMargins()
        effective_rect = rect.adjusted(+m.left(), +m.top(), -m.right(), -m.bottom())
        x = effective_rect.x()
        y = effective_rect.y()
        line_height = 0

        for item in self._item_list:
            wid = item.widget()

            space_x = self.spacing()
            space_y = self.spacing()
            if wid is not None:
                space_x += wid.style().layoutSpacing(
                    QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Horizontal)
                space_y += wid.style().layoutSpacing(
                    QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Vertical)

            next_x = x + item.sizeHint().width() + space_x
            if next_x - space_x > effective_rect.right() and line_height > 0:
                x = effective_rect.x()
                y = y + line_height + space_y
                next_x = x + item.sizeHint().width() + space_x
                line_height = 0

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

            x = next_x
            line_height = max(line_height, item.sizeHint().height())

        new_height = y + line_height - rect.y()
        self.heightChanged.emit(new_height)
        return new_height


if __name__ == '__main__':
    import sys
    from PyQt5.QtWidgets import QApplication, QPushButton, QScrollArea, QVBoxLayout, QWidget


    class Container(QWidget):
        def __init__(self):
            super().__init__()
            self.setLayout(QVBoxLayout())
            self._widgets = []

        def sizeHint(self):
            w = self.size().width()
            h = 0
            for widget in self._widgets:
                h += widget.layout().heightForWidth(w)

            sh = super().sizeHint()
            print(sh)
            print(w, h)
            return sh

        def add_widget(self, widget):
            self._widgets.append(widget)
            self.layout().addWidget(widget)

        def add_stretch(self):
            self.layout().addStretch()


    app = QApplication(sys.argv)  # pylint: disable=invalid-name
    container = Container()
    for i in range(2):
        w = QWidget()
        w.setWindowTitle('Flow Layout')
        l = FlowLayout(w, 10)
        w.setLayout(l)
        l.addWidget(QPushButton('Short'))
        l.addWidget(QPushButton('Longer'))
        l.addWidget(QPushButton('Different text'))
        l.addWidget(QPushButton('More text'))
        l.addWidget(QPushButton('Even longer button text'))
        container.add_widget(w)
    container.add_stretch()

    sa = QScrollArea()
    sa.setWidgetResizable(True)
    sa.setWidget(container)
    sa.show()

    sys.exit(app.exec_())

解决方案(令人惊讶地)简单:使用 FlowLayoutheightChanged更新容器(ScrollArea 的小部件)的最小高度的信号。

这是一个工作示例:

"""
PyQt5 port of the `layouts/flowlayout
<https://doc.qt.io/qt-5/qtwidgets-layouts-flowlayout-example.html>`_ example
from Qt5.

"""
from PyQt5.QtCore import pyqtSignal, QPoint, QRect, QSize, Qt
from PyQt5.QtWidgets import QLayout, QSizePolicy, QSpacerItem


class FlowLayout(QLayout):
    """A ``QLayout`` that aranges its child widgets horizontally and
    vertically.

    If enough horizontal space is available, it looks like an ``HBoxLayout``,
    but if enough space is lacking, it automatically wraps its children into
    multiple rows.

    """
    heightChanged = pyqtSignal(int)

    def __init__(self, parent=None, margin=0, spacing=-1):
        super().__init__(parent)
        if parent is not None:
            self.setContentsMargins(margin, margin, margin, margin)
        self.setSpacing(spacing)

        self._item_list = []

    def __del__(self):
        while self.count():
            self.takeAt(0)

    def addItem(self, item):  # pylint: disable=invalid-name
        self._item_list.append(item)

    def addSpacing(self, size):  # pylint: disable=invalid-name
        self.addItem(QSpacerItem(size, 0, QSizePolicy.Fixed, QSizePolicy.Minimum))

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

    def itemAt(self, index):  # pylint: disable=invalid-name
        if 0 <= index < len(self._item_list):
            return self._item_list[index]
        return None

    def takeAt(self, index):  # pylint: disable=invalid-name
        if 0 <= index < len(self._item_list):
            return self._item_list.pop(index)
        return None

    def expandingDirections(self):  # pylint: disable=invalid-name,no-self-use
        return Qt.Orientations(Qt.Orientation(0))

    def hasHeightForWidth(self):  # pylint: disable=invalid-name,no-self-use
        return True

    def heightForWidth(self, width):  # pylint: disable=invalid-name
        height = self._do_layout(QRect(0, 0, width, 0), True)
        return height

    def setGeometry(self, rect):  # pylint: disable=invalid-name
        super().setGeometry(rect)
        self._do_layout(rect, False)

    def sizeHint(self):  # pylint: disable=invalid-name
        return self.minimumSize()

    def minimumSize(self):  # pylint: disable=invalid-name
        size = QSize()

        for item in self._item_list:
            minsize = item.minimumSize()
            extent = item.geometry().bottomRight()
            size = size.expandedTo(QSize(minsize.width(), extent.y()))

        margin = self.contentsMargins().left()
        size += QSize(2 * margin, 2 * margin)
        return size

    def _do_layout(self, rect, test_only=False):
        m = self.contentsMargins()
        effective_rect = rect.adjusted(+m.left(), +m.top(), -m.right(), -m.bottom())
        x = effective_rect.x()
        y = effective_rect.y()
        line_height = 0

        for item in self._item_list:
            wid = item.widget()

            space_x = self.spacing()
            space_y = self.spacing()
            if wid is not None:
                space_x += wid.style().layoutSpacing(
                    QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Horizontal)
                space_y += wid.style().layoutSpacing(
                    QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Vertical)

            next_x = x + item.sizeHint().width() + space_x
            if next_x - space_x > effective_rect.right() and line_height > 0:
                x = effective_rect.x()
                y = y + line_height + space_y
                next_x = x + item.sizeHint().width() + space_x
                line_height = 0

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

            x = next_x
            line_height = max(line_height, item.sizeHint().height())

        new_height = y + line_height - rect.y()
        self.heightChanged.emit(new_height)
        return new_height


if __name__ == '__main__':
    import sys
    from PyQt5.QtWidgets import QApplication, QPushButton, QScrollArea, QVBoxLayout, QWidget, QGroupBox

    app = QApplication(sys.argv)

    container = QWidget()
    container_layout = QVBoxLayout()
    for i in range(2):
        g = QGroupBox(f'Group {i}')
        l = FlowLayout(margin=10)
        l.heightChanged.connect(container.setMinimumHeight)
        g.setLayout(l)
        l.addWidget(QPushButton('Short'))
        l.addWidget(QPushButton('Longer'))
        l.addWidget(QPushButton('Different text'))
        l.addWidget(QPushButton('More text'))
        l.addWidget(QPushButton('Even longer button text'))
        container_layout.addWidget(g)
    container_layout.addStretch()
    container.setLayout(container_layout)

    w = QScrollArea()
    w.setWindowTitle('Flow Layout')
    w.setWidgetResizable(True)
    w.setWidget(container)
    w.show()

    sys.exit(app.exec_())
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

带有 FlowLayout 小部件的 QScrollArea 无法正确调整大小 的相关文章

  • 如何在刻度标签和轴之间添加空间

    我已成功增加刻度标签的字体 但现在它们距离轴太近了 我想在刻度标签和轴之间添加一点呼吸空间 如果您不想全局更改间距 通过编辑 rcParams 并且想要更简洁的方法 请尝试以下操作 ax tick params axis both whic
  • Python PAM 模块的安全问题?

    我有兴趣编写一个 PAM 模块 该模块将利用流行的 Unix 登录身份验证机制 我过去的大部分编程经验都是使用 Python 进行的 并且我正在交互的系统已经有一个 Python API 我用谷歌搜索发现pam python http pa
  • 如何生成给定范围内的回文数列表?

    假设范围是 1 X 120 这是我尝试过的 gt gt gt def isPalindrome s check if a number is a Palindrome s str s return s s 1 gt gt gt def ge
  • 如何使用 Scrapy 从网站获取所有纯文本?

    我希望在 HTML 呈现后 可以从网站上看到所有文本 我正在使用 Scrapy 框架使用 Python 工作 和xpath body text 我能够获取它 但是带有 HTML 标签 而且我只想要文本 有什么解决办法吗 最简单的选择是ext
  • 在循环中每次迭代开始时将变量重新分配给原始值(在循环之前定义)

    在Python中 你使用 在每次迭代开始时将变量重新分配给原始值 在循环之前定义 时 也就是说 original 1D o o o for i in range 0 3 new original 1D revert back to orig
  • 从列表中的数据框列中搜索部分字符串匹配 - Pandas - Python

    我有一个清单 things A1 B2 C3 我有一个 pandas 数据框 其中有一列包含用分号分隔的值 某些行将包含与上面列表中的一项的匹配 它不会是完美的匹配 因为它在其中包含字符串的其他部分 该列 例如 该列中的一行可能有 哇 这里
  • 使用 Pycharm 在 Windows 下启动应用程序时出现 UnicodeDecodeError

    问题是当我尝试启动应用程序 app py 时 我收到以下错误 UnicodeDecodeError utf 8 编解码器无法解码位置 5 中的字节 0xb3 起始字节无效 整个文件app py coding utf 8 from flask
  • Python 中的二进制缓冲区

    在Python中你可以使用StringIO https docs python org library struct html用于字符数据的类似文件的缓冲区 内存映射文件 https docs python org library mmap
  • NameError:名称“urllib”未定义”

    CODE import networkx as net from urllib request import urlopen def read lj friends g name fetch the friend list from Liv
  • Abaqus 将曲面转化为集合

    我一直试图在模型中找到两个表面的中心 参见照片 但未能成功 它们是元素表面 面 查询中没有选项可以查找元素表面的中心 只能查找元素集的中心 找到节点集的中心也很好 但是我的节点集没有出现在工具 gt 查询 gt 质量属性选项中 而且我找不到
  • python 集合可以包含的值的数量是否有限制?

    我正在尝试使用 python 设置作为 mysql 表中 ids 的过滤器 python集存储了所有要过滤的id 现在大约有30000个 这个数字会随着时间的推移慢慢增长 我担心python集的最大容量 它可以包含的元素数量有限制吗 您最大
  • Python:字符串不会转换为浮点数[重复]

    这个问题在这里已经有答案了 我几个小时前写了这个程序 while True print What would you like me to double line raw input gt if line done break else f
  • 如何改变Python中特定打印字母的颜色?

    我正在尝试做一个简短的测验 并且想将错误答案显示为红色 欢迎来到我的测验 您想开始吗 是的 祝你好运 法国的首都是哪里 法国 随机答案不正确的答案 我正在尝试将其显示为红色 我的代码是 print Welcome to my Quiz be
  • 通过数据框与函数进行交互

    如果我有这样的日期框架 氮 EG 00 04 NEG 04 08 NEG 08 12 NEG 12 16 NEG 16 20 NEG 20 24 datum von 2017 10 12 21 69 15 36 0 87 1 42 0 76
  • 在 Pandas DataFrame Python 中添加新列[重复]

    这个问题在这里已经有答案了 例如 我在 Pandas 中有数据框 Col1 Col2 A 1 B 2 C 3 现在 如果我想再添加一个名为 Col3 的列 并且该值基于 Col2 式中 如果Col2 gt 1 则Col3为0 否则为1 所以
  • 用于运行可执行文件的python多线程进程

    我正在尝试将一个在 Windows 上运行可执行文件并管理文本输出文件的 python 脚本升级到使用多线程进程的版本 以便我可以利用多个核心 我有四个独立版本的可执行文件 每个线程都知道要访问它们 这部分工作正常 我遇到问题的地方是当它们
  • 循环标记时出现“ValueError:无法识别的标记样式 -d”

    我正在尝试编码pyplot允许不同标记样式的绘图 这些图是循环生成的 标记是从列表中选取的 为了演示目的 我还提供了一个颜色列表 版本是Python 2 7 9 IPython 3 0 0 matplotlib 1 4 3 这是一个简单的代
  • 在 Python 类中动态定义实例字段

    我是 Python 新手 主要从事 Java 编程 我目前正在思考Python中的类是如何实例化的 我明白那个 init 就像Java中的构造函数 然而 有时 python 类没有 init 方法 在这种情况下我假设有一个默认构造函数 就像
  • 您可以在 Python 类型注释中指定方差吗?

    你能发现下面代码中的错误吗 米皮不能 from typing import Dict Any def add items d Dict str Any gt None d foo 5 d Dict str str add items d f
  • Python - 字典和列表相交

    给定以下数据结构 找出这两种数据结构共有的交集键的最有效方法是什么 dict1 2A 3A 4B list1 2A 4B Expected output 2A 4B 如果这也能产生更快的输出 我可以将列表 不是 dict1 组织到任何其他数

随机推荐

  • 如何随机均衡不相等的值?

    假设我有多个不相等的值 a b c d e 是否可以仅通过随机数生成将这些不相等的值变成相等的值 示例 a 100 b 140 c 200 d 2 e 1000 我希望算法随机定位这些集合 以便最常定位最大值 而大多数情况下只保留最小值 我
  • 当列类型为 nvarchar 时,将表与列值总和一起旋转

    我有一个具有以下结构的表 我想转置它 BookId Status 123A Perfect 123B Restore 123C Lost 123D Perfect 123A Perfect 123B Restore 123A Lost 12
  • rpy2 在 Windows 7 上安装

    我试图在我的计算机上安装 rpy2 但无法做到这一点 我下载了源码包 并尝试使用命令安装Rpy2 rpy2 2 3 2 python setup py install 这是结果 running install running build r
  • 我想获取 SD 卡中的音频文件

    在我的应用程序中我想设置ringtone当我接到来电时 如何打开SDCARD并得到音频文件并列出它 如何获得URI对于所选的audio file MediaScanner 为您查找音乐 填充 MediaStore 数据库 下面是一些查找音乐
  • 使用可选参数和命名参数解决歧义

    我的项目有两种方法 定义如下 void Person int ID double height 0 0 string team Knights my codes void Person int ID double height 0 0 st
  • 如何组合两个机器学习算法的输出?

    如果我有两个非常不同的数据集和两种非常不同的分类技术 是否有一个很好的方法来组合这两个输出 我知道平均值可能有效 但有没有更相关的方法来做到这一点 我听说过一些概念 例如增强学习和集成学习 这些概念是否适用 解决这个问题有两种一般方法 第一
  • 使用部分可变参数调用函数

    考虑我有以下内容 void bar int a int b template
  • 如何设计 HTML5 表单验证消息的样式? [复制]

    这个问题在这里已经有答案了 假设你有一些像这样的 HTML
  • 使用 .htaccess 来屏蔽域名?

    所以这是场景 我拥有的域名是 www abc com 我想将其指向 属于另一家公司 问题 如何使用 htaccess 转发和屏蔽 URL 使其保持为 www abc com 从安全角度来说 为www abc com购买数字证书有什么意义吗
  • 未处理的拒绝(TypeError):ships.reduce 不是函数

    我使用特定的 API 构建了一个船可视化器 API 返回一个 json 响应 我将其注入到表中 问题 有时在白天 我注意到应用程序会停止工作并抛出以下实例 Unhandled Rejection TypeError ships reduce
  • numpy einsum:嵌套点积

    我有两个n by k by 3 arrays a and b e g import numpy as np a np array 1 2 3 3 4 5 4 2 4 1 4 5 b np array 3 1 5 0 2 3 2 4 5 1
  • Mockito 和 PowerMock MethodNotFoundException 被抛出

    当使用 Powermockito 和 Mockito 为我围绕 Hikari CP 制作的连接池构建一些简单的单元测试时 遇到以下错误 测试的设置如下 让我感到困惑的是 我有一大堆未显示的单元测试 它们都使用相同的设置和方法通过了 仅此一个
  • 在 ARCore unity 中禁用/切换跟踪平面的可视化

    我已经研究 ARCore Unity 的代码有一段时间了 我想做一个简单的任务 即有一个切换按钮 以便用户可以在场景中放置一个对象 同时知道在跟踪平面可见时将其放置在哪里一旦用户放置了对象 他就可以选择仅在视觉上禁用跟踪的平面 使其看起来更
  • 在 C 的哪些版本中,括号内的块用于返回有效值?

    If I do int j int x 7 x 3 在 i686 apple darwin10 gcc 4 2 1 GCC 4 2 1 Apple Inc build 5646 gcc 中 它编译得很好 有问题的块 int x 7 x 3
  • 悬空指针,free()后值改变的原因?

    在下面的代码段中 之后free x 为什么y变成0 根据我的理解 所指向的堆中的内存x 并且仍然被指出y 还没有分配给别人 怎么会变成0呢 而且 我不认为这是free x 这将其更改为 0 任何意见 include
  • Gradle 复制任务第一次不从临时文件夹复制文件

    我有一个运行这样的任务的构建文件 Task 1 unpackWar 将war文件解压到Temp文件夹 Task 2 copyWarFilesToWebContent 将文件复制到 WebContent 文件夹 并排除一些内容 Task 3
  • Android EditText 提示大小

    如何减少EditText提示尺寸 您可以通过在字符串资源中设置大小来完成此操作 例如
  • WIX 启用 Windows 功能

    在安装软件之前 我必须检查某些 Windows 功能是否已启用 我可以使用 dism 命令行工具检查或安装它 我创建了一个自定义操作来执行此操作 但是有没有办法以 WIX 本机方式 执行此操作
  • Python正则表达式搜索句子中的单词

    我仍在学习 Python 和正则表达式的诀窍 我需要一些帮助 我需要一个可以在句子中搜索特定单词的正则表达式 我已经成功创建了一个模式来搜索单个单词 但是如何检索我需要查找的其他单词 重新模式会是什么样子来做到这一点 gt gt gt qu
  • 带有 FlowLayout 小部件的 QScrollArea 无法正确调整大小

    我想创建一个类似于 KDE 或 Gnome 或 MacOS 系统设置的小部件 例如 像这张图片 我已经实现了 FlowLayoutQt 文档示例 如果我将一些 FlowLayout 小部件 用 QVBoxLayout 包装在容器小部件中 放