将停靠栏调整为可见内容的高度
Resize dock to height of visible contents
我正在创建一个可折叠的小部件。它包含一个 table 并嵌入在某些组框下方的另一个小部件中。一切都被放入码头。当可折叠小部件展开时,可折叠小部件中包含的 table 垂直填充停靠栏;组框保持固定。但是,停靠栏会根据组框和可折叠小部件按钮的高度调整大小 仅当停靠栏未首先调整大小时。
请注意调整停靠栏大小后,停靠栏如何保持与折叠的 table 相同的大小:
我怎样才能像第一次加载时那样调整扩展坞的大小,使其达到组框和切换按钮的最小高度?或者也许是一个更好的问题,停靠小部件如何确定其最小尺寸以及我如何建议它为最小尺寸(如果不是通过 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, 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, 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, 0, 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_())
我尝试将扩展坞的 sizeHint 设置得非常低,并将扩展坞上的 sizePolicy 设置为 MinimumExpanding 或 Expanding。我希望扩展坞然后尝试将大小调整到最小,但随后将其大小调整到其内容的最小值。行为没有明显变化。
我尝试在 on_pressed() 调用中访问停靠栏并强制其调整大小 ()。同样,行为没有明显变化。
不幸的是,QMainWindow 的布局(以及停靠区域的布局)几乎无法访问,至少从 python 开始是这样。主要问题是停靠小部件被添加到内部布局系统中,该系统还保留手动调整大小的痕迹,并且没有办法(至少,据我所知)“重置”这些大小。
虽然存在一些可能的解决方法。
一个想法是,可折叠小部件在折叠时发出一个信号,并且该信号连接到主 window.
的一个专门函数
在这种情况下,只要将停靠小部件设置为主 window 的父级,我就会自动连接信号(但还有其他方法可以这样做)。然后技巧就是检查dock是否漂浮,然后,分别:
- 根据其最小(垂直)大小提示调整大小
- 强制扩展坞的垂直尺寸
class CollapsibleWidget(QtWidgets.QWidget):
collapsed = QtCore.pyqtSignal()
# ...
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)
if not checked:
self.collapsed.emit()
class MainWindow(QtWidgets.QMainWindow):
def childEvent(self, event):
if event.added() and isinstance(event.child(), QtWidgets.QDockWidget):
for resizable in event.child().findChildren(CollapsibleWidget):
resizable.collapsed.connect(self.collapsibleResized)
def collapsibleResized(self):
widget = self.sender()
dock = widget.parent()
while not isinstance(dock, QtWidgets.QDockWidget):
dock = dock.parent()
if dock.isFloating():
def delayedResize():
dock.resize(dock.width(), dock.minimumSizeHint().height())
else:
def delayedResize():
self.resizeDocks(
[dock],
[dock.widget().minimumSizeHint().height()],
QtCore.Qt.Vertical
)
QtWidgets.QApplication.processEvents()
QtCore.QTimer.singleShot(0, delayedResize)
添加(或标签化)多个停靠栏时可能会出现一些问题,我不确定是否恢复停靠栏状态,因此您可能应该进行一些深入测试。
根据@musicamante 的回复,我对以下内容感到满意。代码比问题略有改进:
- 将组框上的固定大小策略替换为可折叠小部件上的拉伸;这使得组框大小相同
- 删除了间距(0);这使得组框不会 运行 变成另一个
- 已将 on_pressed() 更新为 toggle_expanded();允许编程使用
否则,通过测试,下面的效果很好。存在“折叠”和“展开”状态变化的信号。这些在主窗口中直接连接。折叠时,停靠栏会根据最小高度调整大小(根据@musicamante)。当前停靠高度在折叠前保存。扩展时,将恢复最后已知的停靠高度。折叠时锁定调整大小以防止出现任何奇怪的中间状态(停靠栏超出 table,如原始问题所示)。
通过保存状态和几何图形进行测试,仅保留停靠小部件位置和几何图形本身。 dock 中的任何状态,例如可折叠小部件是否展开,都必须在 init 之后单独处理。使用 toggle_expanded()。
我尝试了处理事件、延迟持续时间和立即调用 delayed_resize() 的各种排列组合。所有产生的偶尔闪烁其中 table 扩展到类似 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, 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, 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, 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, 0, 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_())
我正在创建一个可折叠的小部件。它包含一个 table 并嵌入在某些组框下方的另一个小部件中。一切都被放入码头。当可折叠小部件展开时,可折叠小部件中包含的 table 垂直填充停靠栏;组框保持固定。但是,停靠栏会根据组框和可折叠小部件按钮的高度调整大小 仅当停靠栏未首先调整大小时。
请注意调整停靠栏大小后,停靠栏如何保持与折叠的 table 相同的大小:
我怎样才能像第一次加载时那样调整扩展坞的大小,使其达到组框和切换按钮的最小高度?或者也许是一个更好的问题,停靠小部件如何确定其最小尺寸以及我如何建议它为最小尺寸(如果不是通过 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, 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, 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, 0, 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_())
我尝试将扩展坞的 sizeHint 设置得非常低,并将扩展坞上的 sizePolicy 设置为 MinimumExpanding 或 Expanding。我希望扩展坞然后尝试将大小调整到最小,但随后将其大小调整到其内容的最小值。行为没有明显变化。
我尝试在 on_pressed() 调用中访问停靠栏并强制其调整大小 ()。同样,行为没有明显变化。
不幸的是,QMainWindow 的布局(以及停靠区域的布局)几乎无法访问,至少从 python 开始是这样。主要问题是停靠小部件被添加到内部布局系统中,该系统还保留手动调整大小的痕迹,并且没有办法(至少,据我所知)“重置”这些大小。
虽然存在一些可能的解决方法。
一个想法是,可折叠小部件在折叠时发出一个信号,并且该信号连接到主 window.
的一个专门函数在这种情况下,只要将停靠小部件设置为主 window 的父级,我就会自动连接信号(但还有其他方法可以这样做)。然后技巧就是检查dock是否漂浮,然后,分别:
- 根据其最小(垂直)大小提示调整大小
- 强制扩展坞的垂直尺寸
class CollapsibleWidget(QtWidgets.QWidget):
collapsed = QtCore.pyqtSignal()
# ...
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)
if not checked:
self.collapsed.emit()
class MainWindow(QtWidgets.QMainWindow):
def childEvent(self, event):
if event.added() and isinstance(event.child(), QtWidgets.QDockWidget):
for resizable in event.child().findChildren(CollapsibleWidget):
resizable.collapsed.connect(self.collapsibleResized)
def collapsibleResized(self):
widget = self.sender()
dock = widget.parent()
while not isinstance(dock, QtWidgets.QDockWidget):
dock = dock.parent()
if dock.isFloating():
def delayedResize():
dock.resize(dock.width(), dock.minimumSizeHint().height())
else:
def delayedResize():
self.resizeDocks(
[dock],
[dock.widget().minimumSizeHint().height()],
QtCore.Qt.Vertical
)
QtWidgets.QApplication.processEvents()
QtCore.QTimer.singleShot(0, delayedResize)
添加(或标签化)多个停靠栏时可能会出现一些问题,我不确定是否恢复停靠栏状态,因此您可能应该进行一些深入测试。
根据@musicamante 的回复,我对以下内容感到满意。代码比问题略有改进:
- 将组框上的固定大小策略替换为可折叠小部件上的拉伸;这使得组框大小相同
- 删除了间距(0);这使得组框不会 运行 变成另一个
- 已将 on_pressed() 更新为 toggle_expanded();允许编程使用
否则,通过测试,下面的效果很好。存在“折叠”和“展开”状态变化的信号。这些在主窗口中直接连接。折叠时,停靠栏会根据最小高度调整大小(根据@musicamante)。当前停靠高度在折叠前保存。扩展时,将恢复最后已知的停靠高度。折叠时锁定调整大小以防止出现任何奇怪的中间状态(停靠栏超出 table,如原始问题所示)。
通过保存状态和几何图形进行测试,仅保留停靠小部件位置和几何图形本身。 dock 中的任何状态,例如可折叠小部件是否展开,都必须在 init 之后单独处理。使用 toggle_expanded()。
我尝试了处理事件、延迟持续时间和立即调用 delayed_resize() 的各种排列组合。所有产生的偶尔闪烁其中 table 扩展到类似 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, 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, 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, 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, 0, 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_())