将停靠栏调整为可见内容的高度

问题描述

我正在创建一个可折叠的小部件。它包含一个表格并嵌入在某些组框下方的另一个小部件中。所有东西都放在一个码头上。当可折叠小部件展开时,可折叠小部件中包含的表格会垂直填充停靠栏;组框保持固定。但是,停靠栏会调整为组框和可折叠小部件按钮的高度仅当停靠栏尚未首先调整大小时

请注意,在调整 Dock 大小后,Dock 如何保持与折叠后的表格相同的大小:

enter image description here

我怎样才能像第一次加载时一样将停靠栏调整到组框和切换按钮的最小高度?或者也许是一个更好的问题,dock 小部件如何确定其最小尺寸以及我如何建议它为最小尺寸(如果不是通过 MinimumExpanding)?

import sys
from PyQt5 import QtCore,QtWidgets,QtWidgets


class CollapsibleWidget(QtWidgets.QWidget):

    def __init__(self,title="",parent=None):
        super().__init__(parent)

        self.toggle_button = QtWidgets.QToolButton(text=title,checkable=True,checked=True)
        self.toggle_button.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
        self.toggle_button.setArrowType(QtCore.Qt.RightArrow)
        self.toggle_button.setStyleSheet("QToolButton { border: none; }")
        self.toggle_button.pressed.connect(self.on_pressed)

        self.content_layout = QtWidgets.QVBoxLayout()
        self.content_widget = QtWidgets.QWidget()
        self.content_widget.setLayout(self.content_layout)
        self.content_widget.hide()

        lay = QtWidgets.QVBoxLayout(self)
        lay.setContentsMargins(0,0)
        lay.addWidget(self.toggle_button,alignment=QtCore.Qt.AlignTop)
        lay.addWidget(self.content_widget)

    def on_pressed(self):
        checked = self.toggle_button.isChecked()
        self.toggle_button.setArrowType(QtCore.Qt.DownArrow if checked else QtCore.Qt.RightArrow)
        self.content_widget.setVisible(checked)


class ControlWidget(QtWidgets.QWidget):

    def __init__(self,parent=None):
        super().__init__(parent)

        # CheckBoxes
        self.checkBox1 = QtWidgets.QCheckBox("CheckBox1")
        self.checkBox2 = QtWidgets.QCheckBox("CheckBox2")

        # Buttons
        self.button1 = QtWidgets.QPushButton('Button1')
        self.button2 = QtWidgets.QPushButton('Button2')
        self.button3 = QtWidgets.QPushButton('Button3')

        # CheckBox group
        self.gb_checkBox = QtWidgets.qgroupbox("CheckBoxes")
        self.layout_gb_checkBox = QtWidgets.QHBoxLayout()
        self.layout_gb_checkBox.addWidget(self.checkBox1)
        self.layout_gb_checkBox.addWidget(self.checkBox2)
        self.gb_checkBox.setLayout(self.layout_gb_checkBox)
        self.gb_checkBox.setSizePolicy(QtWidgets.QSizePolicy.Preferred,QtWidgets.QSizePolicy.Fixed)

        # Button group
        self.gb_button = QtWidgets.qgroupbox("Buttons")
        self.layout_gb_button = QtWidgets.QHBoxLayout()
        self.layout_gb_button.addWidget(self.button1)
        self.layout_gb_button.addWidget(self.button2)
        self.layout_gb_button.addWidget(self.button3)
        self.gb_button.setLayout(self.layout_gb_button)
        self.gb_button.setSizePolicy(QtWidgets.QSizePolicy.Preferred,QtWidgets.QSizePolicy.Fixed)

        # groups layout
        self.groups_layout = QtWidgets.QHBoxLayout()
        self.groups_layout.addWidget(self.gb_checkBox)
        self.groups_layout.addWidget(self.gb_button)

        # table
        self.table = QtWidgets.QTableWidget()
        for i in range(20):
            self.table.insertRow(i)

        # Collapsible widget
        self.collapsible_widget = CollapsibleWidget("Table")
        self.collapsible_widget.content_layout.addWidget(self.table)

        layout = QtWidgets.QVBoxLayout()
        layout.setSpacing(0)
        layout.setContentsMargins(0,0)
        layout.addLayout(self.groups_layout)
        layout.addWidget(self.collapsible_widget)

        self.setLayout(layout)


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)

    controls = ControlWidget()

    # Dock
    dock_layout = QtWidgets.QVBoxLayout()
    dock_layout.setContentsMargins(4,4,0)
    dock_layout.addWidget(controls)

    dock = QtWidgets.QDockWidget("Control Panel")
    dock_contents = QtWidgets.QWidget()
    dock_contents.setLayout(dock_layout)
    dock.setWidget(dock_contents)

    # central widget
    central_widget = QtWidgets.QWidget()
    central_widget.setStyleSheet('background-color: gray')

    # main window
    main_window = QtWidgets.QMainWindow()
    main_window.resize(640,480)
    main_window.addDockWidget(QtCore.Qt.TopDockWidgetArea,dock)
    main_window.setCentralWidget(central_widget)

    main_window.show()
    sys.exit(app.exec_())

我尝试将 Dock 的 sizeHint 设置得非常低,并将 Dock 上的 sizePolicy 设置为 MinimumExpanding 或 Expanding。我希望扩展坞然后尝试将大小调整为最小,但随后将其调整为最小的内容。行为没有明显变化。

我尝试在 on_pressed() 调用中访问停靠栏并强制它调整大小 ()。同样,行为没有明显变化。

解决方法

不幸的是,QMainWindow 的布局(以及停靠区的布局)几乎无法访问,至少在 python 中是这样。主要问题是,dock 小部件被添加到内部布局系统中,该系统还保留了手动调整大小的痕迹,并且无法(至少我知道)“重置”这些大小。

不过,存在一些可能的解决方法。

一个想法是可折叠小部件在折叠时发出信号,并且该信号与主窗口的特定功能相关联。

在这种情况下,只要将停靠小部件设置为主窗口的父级,我就会自动连接信号(但还有其他方法可以这样做)。然后诀窍是检查dock是否浮动,然后分别:

  • 根据其最小(垂直)尺寸提示调整其大小
  • 强制调整 Dock 的垂直尺寸
RewriteEngine On
RewriteBase /
RewriteCond %{SERVER_PORT} 80
RewriteRule ^(.*)$ https://%{HTTP_HOST}$1 [R,L]

每当添加(或制表)多个 Dock 时可能会出现一些问题,而且我不确定是否可以恢复 Dock 状态,因此您可能应该进行一些深入的测试。

,

根据@musicamante 的回复,我对以下内容感到满意。代码从问题中略有改进:

  • 用可折叠小部件上的拉伸替换了分组框上的固定大小策略;这使分组框大小相同
  • 删除了间距(0);这使得分组框不会相互碰撞
  • 将 on_pressed() 更新为 toggle_expanded();允许程序化使用

否则,通过测试,以下效果很好。存在“折叠”和“展开”状态变化的信号。它们直接在 MainWindow 中连接。折叠时,停靠栏会根据最小高度(根据 @musicamante)调整大小。折叠前保存当前停靠高度。在扩展时,将恢复最后一个已知的码头高度。折叠时调整大小会被锁定,以防止出现任何奇怪的中间状态(停靠栏延伸到桌子之外,如原始问题所示)。

通过保存状态和几何体进行测试,仅保留停靠小部件位置和几何体本身。 Dock 内的任何状态,例如可折叠小部件是否展开,都必须在 init 之后单独处理。使用toggle_expanded()。

我尝试了处理事件、延迟持续时间的各种排列,并立即调用 delay_resize()。所有这些都产生了偶尔的闪烁,其中表格扩展到 6 行,但立即调整大小。闪烁是不规则的,仅在扩展期间发生,据我所知,这是一个小细节。偶尔在浮动和停靠之间切换时,高度会略有降低。这似乎是因为浮动时停靠栏的高度包括标题栏。停靠时没有标题栏。

有一个额外的停靠小部件是为了很好的衡量。一切都符合预期。

import sys
from PyQt5 import QtCore,QtWidgets,QtWidgets


class CollapsibleWidget(QtWidgets.QWidget):

    collapsed = QtCore.pyqtSignal()
    expanded = QtCore.pyqtSignal()

    def __init__(self,title="",parent=None):
        super().__init__(parent)
        self.setObjectName('CollapsibleWidget')

        self.toggle_button = QtWidgets.QToolButton(text=title,checkable=True,checked=False)
        self.toggle_button.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
        self.toggle_button.setArrowType(QtCore.Qt.RightArrow)
        self.toggle_button.setStyleSheet("QToolButton { border: none; }")
        self.toggle_button.clicked.connect(self.toggle_expanded)

        self.content_layout = QtWidgets.QVBoxLayout()
        self.content_layout.setContentsMargins(0,0)
        self.content_widget = QtWidgets.QWidget()
        self.content_widget.setLayout(self.content_layout)
        self.content_widget.hide()

        lay = QtWidgets.QVBoxLayout(self)
        lay.setContentsMargins(0,0)
        lay.addWidget(self.toggle_button,alignment=QtCore.Qt.AlignTop)
        lay.addWidget(self.content_widget)

    def toggle_expanded(self,expanded=True):
        self.toggle_button.setArrowType(QtCore.Qt.DownArrow if expanded else QtCore.Qt.RightArrow)
        self.content_widget.setVisible(expanded)
        if not expanded:  # regardless of whether the state actually changed
            self.collapsed.emit()
        else:
            self.expanded.emit()


class ControlWidget(QtWidgets.QWidget):

    def __init__(self,parent=None):
        super().__init__(parent)
        self.setObjectName("ControlWidget")

        # Checkboxes
        self.checkbox1 = QtWidgets.QCheckBox("Checkbox1")
        self.checkbox2 = QtWidgets.QCheckBox("Checkbox2")

        # Buttons
        self.button1 = QtWidgets.QPushButton('Button1')
        self.button2 = QtWidgets.QPushButton('Button2')
        self.button3 = QtWidgets.QPushButton('Button3')

        # Checkbox group
        self.gb_checkbox = QtWidgets.QGroupBox("Checkboxes")
        self.layout_gb_checkbox = QtWidgets.QHBoxLayout()
        self.layout_gb_checkbox.addWidget(self.checkbox1)
        self.layout_gb_checkbox.addWidget(self.checkbox2)
        self.gb_checkbox.setLayout(self.layout_gb_checkbox)

        # Button group
        self.gb_button = QtWidgets.QGroupBox("Buttons")
        self.layout_gb_button = QtWidgets.QHBoxLayout()
        self.layout_gb_button.addWidget(self.button1)
        self.layout_gb_button.addWidget(self.button2)
        self.layout_gb_button.addWidget(self.button3)
        self.gb_button.setLayout(self.layout_gb_button)

        # groups layout
        self.groups_layout = QtWidgets.QHBoxLayout()
        self.groups_layout.addWidget(self.gb_checkbox)
        self.groups_layout.addWidget(self.gb_button)

        # table
        self.table = QtWidgets.QTableWidget()
        for i in range(20):
            self.table.insertRow(i)

        # Collapsible widget
        self.collapsible_widget = CollapsibleWidget("Table")
        self.collapsible_widget.content_layout.addWidget(self.table)

        layout = QtWidgets.QVBoxLayout()
        layout.setContentsMargins(0,0)
        layout.addLayout(self.groups_layout)
        layout.addWidget(self.collapsible_widget,stretch=1)

        self.setLayout(layout)

class MainWindow(QtWidgets.QMainWindow):

    def __init__(self):
        super().__init__()

        # Controls
        self.controls = ControlWidget()
        self.controls.collapsible_widget.collapsed.connect(self.on_collapse)
        self.controls.collapsible_widget.expanded.connect(self.on_expand)

        # Dock
        self.dock_layout = QtWidgets.QVBoxLayout()
        self.dock_layout.setContentsMargins(4,4,0)
        self.dock_layout.addWidget(self.controls)

        self.dock_contents = QtWidgets.QWidget()
        self.dock_contents.setLayout(self.dock_layout)
        # Don't allow resizing unless expanded; initial states is collapsed
        self.dock_contents.setSizePolicy(QtWidgets.QSizePolicy.Preferred,QtWidgets.QSizePolicy.Fixed)

        self.dock = QtWidgets.QDockWidget("Control Panel")
        self.dock.setWidget(self.dock_contents)
        self._dock_last_expanded_height = self.dock.minimumSizeHint().height()

        # Extra Dock
        self.extra_dock_layout = QtWidgets.QVBoxLayout()
        self.extra_dock_layout.addWidget(QtWidgets.QLabel('Extra Dock'))
        self.extra_dock_layout.addWidget(QtWidgets.QLabel('Extra Dock'))
        self.extra_dock_layout.addWidget(QtWidgets.QLabel('Extra Dock'))
        self.extra_dock_layout.addWidget(QtWidgets.QLabel('Extra Dock'))
        self.extra_dock_layout.addWidget(QtWidgets.QLabel('Extra Dock'))
        self.extra_dock_layout.addWidget(QtWidgets.QLabel('Extra Dock'))
        self.extra_dock_layout.addWidget(QtWidgets.QLabel('Extra Dock'))

        self.extra_dock_contents = QtWidgets.QWidget()
        self.extra_dock_contents.setLayout(self.extra_dock_layout)

        self.extra_dock = QtWidgets.QDockWidget("Extra dock")
        self.extra_dock.setWidget(self.extra_dock_contents)

        # Central widget
        self.central_widget = QtWidgets.QWidget()
        self.central_widget.setStyleSheet('background-color: gray')

        self.resize(640,480)
        self.addDockWidget(QtCore.Qt.TopDockWidgetArea,self.dock)
        self.addDockWidget(QtCore.Qt.TopDockWidgetArea,self.extra_dock)
        self.setCentralWidget(self.central_widget)

    def on_collapse(self):
        self._dock_last_expanded_height = self.dock.height()

        if self.dock.isFloating():
            def delayed_resize():
                self.dock.resize(self.dock.width(),self.dock.minimumSizeHint().height())

        else:
            def delayed_resize():
                self.resizeDocks(
                    [self.dock],[self.dock.widget().minimumSizeHint().height()],QtCore.Qt.Vertical
                )
        # Don't allow resizing unless expanded
        self.dock_contents.setSizePolicy(QtWidgets.QSizePolicy.Preferred,QtWidgets.QSizePolicy.Fixed)
        # QtWidgets.QApplication.processEvents()
        # QtCore.QTimer.singleShot(0,delayed_resize)
        delayed_resize()

    def on_expand(self):
        if self.dock.isFloating():
            def delayed_resize():
                self.dock.resize(self.dock.width(),self._dock_last_expanded_height)

        else:
            def delayed_resize():
                self.resizeDocks(
                    [self.dock],[self._dock_last_expanded_height],QtCore.Qt.Vertical
                )
        # Allow resizing when expanded
        self.dock_contents.setSizePolicy(QtWidgets.QSizePolicy.Preferred,QtWidgets.QSizePolicy.Preferred)
        # QtWidgets.QApplication.processEvents()
        # QtCore.QTimer.singleShot(0,delayed_resize)
        delayed_resize()

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)

    main_window = MainWindow()
    main_window.show()

    sys.exit(app.exec_())