I'm creating a collapsible widget. It contains a table and is embedded in another widget beneath some group boxes. Everything is put into a dock. The table contained in the collapsible widget vertically fills the dock when the collapsible widget is expanded; the group boxes remain fixed. However, the dock resizes to the height of the group boxes and the collapsible widget button only if the dock hasn't been resized first.
Notice how after resizing the dock, the dock remains the same size as the table which was collapsed:
How can I have the dock resize like on first load, to the minimum height of the group boxes and toggle button? Or maybe a better question, how does the dock widget determine its minimum size and how can I advise it to be minimum size (if not through 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_())
I tried setting the sizeHint of the dock really low and setting sizePolicy on the dock to MinimumExpanding or Expanding. I expected the dock to then try resizing to the minimum, but then resize to the minimum of its contents. There was no noticeable change in behavior.
I tried accessing the dock within the on_pressed() call and forcing it to resize(). Again, no noticeable change in behavior.
Unfortunately, the layout of a QMainWindow (and the layout of the dock areas) is almost unaccessible, at least from python. The main problem is that dock widgets are added to an internal system of layouts that also keeps trace of manual resizing, and there's no way (at least, that I know of) to "reset" those sizes.
There exist some possible workarounds, though.
One idea is that the collapsible widget emits a signal whenever it's collapsed, and that signal is connected to a specialized function of the main window.
In this case, I'll automatically connect the signal whenever a dock widget is set as parent of the main window (but there are other ways to do so). Then the trick is to check whether the dock is floating or not, and then, respectively:
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)
There could be some issues whenever multiple dock are added (or tabified), and I'm not sure about restoring dock state, so you should probably do some deep testing.