Qt:停靠小部件如何获得其初始大小?

时间:2019-04-17 19:37:28

标签: python pyqt pyqt5 qdockwidget qtpy

停靠一个小部件时,我希望它可以改变方向,并且相对于停靠件的扩展方式具有最小的尺寸。

  • 左/右停靠=>上下方向且宽度最小
  • 顶部/底部基座=>左右方向且高度最小

问题是,每当方向改变时,扩展坞就会呈现看似任意的宽度或高度。我无法找到一种在停靠时将停靠小部件调整大小/强制使其达到特定大小的方法。我尝试了无数种方法来覆盖sizeHintminimumSizeHint,调用adjustSize和摆弄sizePolicy

如何确保初始基座尺寸?


我的基本应用程序如下:

enter image description here

应用程序显示主要和次要信息以及相应的控件集。包含主要和次要内容的选项卡小部件被设置为中央小部件。一个QStackedWidget分别位于各个仪表板中的控件中。当选项卡更改时,将显示相应的仪表板。下面的 基本应用程序代码 中给出了此代码。

困难在于,改变仪表板的方向会破坏基座的尺寸。

要调整仪表板方向,我可以考虑两种合理的解决方案:

  • 通过resizeEvent
  • 通过dockLocationChanged信号

通过resizeEvent

调整方向

在我看来,这是更可取的选择。它为用户提供了最大的灵活性。如果他们不喜欢停靠的方向,则将其拖到特定的限制范围内将允许他们更改停靠的方向。在这里,我检查宽度是否大于身高。

    class MyDock(QtWidgets.QDockWidget):

        def __init__(self):

            super(MyDock, self).__init__()

        def resizeEvent(self, event):
            size = event.size()
            is_wide = size.width() > size.height()

            container_object = self.widget().currentWidget()

            if is_wide:
                container_object.setDirection(QtWidgets.QBoxLayout.LeftToRight)
            else:
                container_object.setDirection(QtWidgets.QBoxLayout.TopToBottom)

下面在 调整大小方法 中给出了完整的代码。

更改dockLocationChange上的方向

随着调整大小事件一直发生,另一种方法可能是仅在停靠站位置更改时更改方向。为此,请将功能连接到dockLocationChanged信号并根据扩展坞调整方向。

    class MyDock(QtWidgets.QDockWidget):

        def __init__(self):

            super(MyDock, self).__init__()

            self.dockLocationChanged.connect(self.dock_location_changed)

        def dock_location_changed(self, area):
            top    = QtCore.Qt.DockWidgetArea.TopDockWidgetArea
            bottom = QtCore.Qt.DockWidgetArea.BottomDockWidgetArea

            container_object = self.widget().currentWidget()

            if area in [top, bottom]:
                container_object.setDirection(QtWidgets.QBoxLayout.LeftToRight)
            else:
                container_object.setDirection(QtWidgets.QBoxLayout.TopToBottom)

基本应用代码

该程序包含5个独立的类。

对于

  1. MyWindow
  2. PrimaryDashboard
  3. SecondaryDashboard

分离的原因应该很清楚。

对于

  1. MyDock
  2. DockContainer

分离是为了方便覆盖sizeHintsetDirection或其他方法。

    import qtpy
    from qtpy import QtWidgets, QtGui, QtCore
    import sys


    class PrimaryDashboard(QtWidgets.QWidget):

        def __init__(self):

            super(PrimaryDashboard, self).__init__()

            self.init_widgets()
            self.init_layout()

        def init_widgets(self):
            self.label = QtWidgets.QLabel('Primary dashboard')
            self.ok = QtWidgets.QPushButton('OK')
            self.cancel = QtWidgets.QPushButton('Cancel')

        def init_layout(self):
            self.layout = QtWidgets.QHBoxLayout()
            self.layout.addWidget(self.label)
            self.layout.addWidget(self.ok)
            self.layout.addWidget(self.cancel)
            self.setLayout(self.layout)

        def setDirection(self, direction):
            self.layout.setDirection(direction)


    class SecondaryDashboard(QtWidgets.QWidget):

        def __init__(self):

            super(SecondaryDashboard, self).__init__()

            self.init_widgets()
            self.init_layout()

        def init_widgets(self):
            self.label = QtWidgets.QLabel('Secondary dashboard')

            self.descr1 = QtWidgets.QLabel('Thing 1')
            self.check1 = QtWidgets.QCheckBox()

            self.descr2 = QtWidgets.QLabel('Thing 2')
            self.check2 = QtWidgets.QCheckBox()

        def init_layout(self):
            self.layout = QtWidgets.QVBoxLayout()

            self.grid = QtWidgets.QGridLayout()
            self.grid.addWidget(self.descr1, 0, 0)
            self.grid.addWidget(self.check1, 0, 1)
            self.grid.addWidget(self.descr2, 1, 0)
            self.grid.addWidget(self.check2, 1, 1)

            self.layout.addWidget(self.label)
            self.layout.addLayout(self.grid)
            self.setLayout(self.layout)

        def setDirection(self, direction):
            self.layout.setDirection(direction)


    class DockContainer(QtWidgets.QStackedWidget):

        def __init__(self):

            super(DockContainer, self).__init__()


    class MyDock(QtWidgets.QDockWidget):

        def __init__(self):

            super(MyDock, self).__init__()


    class MyWindow(QtWidgets.QMainWindow):

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

            self.resize(600, 400)

            self.init_widgets()
            self.init_layout()

        def init_widgets(self):

            self.tab_widget = QtWidgets.QTabWidget()
            self.tab1 = QtWidgets.QLabel('Primary content')
            self.tab2 = QtWidgets.QLabel('Secondary content')
            self.tab_widget.addTab(self.tab1, 'Primary')
            self.tab_widget.addTab(self.tab2, 'Secondary')
            self.tab_widget.currentChanged.connect(self.tab_selected)

            self.primary_dashboard = PrimaryDashboard()
            self.secondary_dashboard = SecondaryDashboard()

            self.dashboard = DockContainer()
            self.dashboard.addWidget(self.primary_dashboard)
            self.dashboard.addWidget(self.secondary_dashboard)
            self.dashboard.setCurrentWidget(self.primary_dashboard)

            self.dock = MyDock()
            self.dock.setWidget(self.dashboard)
            self.addDockWidget(QtCore.Qt.BottomDockWidgetArea, self.dock)

        def init_layout(self):
            self.main_layout = QtWidgets.QVBoxLayout()
            self.main_layout.addWidget(self.tab_widget)

            self.main_widget = QtWidgets.QWidget()
            self.main_widget.setLayout(self.main_layout)
            self.setCentralWidget(self.main_widget)

        def tab_selected(self):
            tab_index = self.tab_widget.currentIndex()
            if self.tab_widget.tabText(tab_index) == 'Secondary':
                self.dashboard.setCurrentWidget(self.secondary_dashboard)
                self.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.dock)
            else:  # Primary
                self.dashboard.setCurrentWidget(self.primary_dashboard)
                self.addDockWidget(QtCore.Qt.BottomDockWidgetArea, self.dock)


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

        window = MyWindow()
        window.show()
        sys.exit(app.exec_())

调整大小方法

此代码与 基本应用程序代码 相同,但在停靠小部件中覆盖了resizeEvent

    import qtpy
    from qtpy import QtWidgets, QtGui, QtCore
    import sys


    class PrimaryDashboard(QtWidgets.QWidget):

        def __init__(self):

            super(PrimaryDashboard, self).__init__()

            self.init_widgets()
            self.init_layout()

        def init_widgets(self):
            self.label = QtWidgets.QLabel('Primary dashboard')
            self.ok = QtWidgets.QPushButton('OK')
            self.cancel = QtWidgets.QPushButton('Cancel')

        def init_layout(self):
            self.layout = QtWidgets.QHBoxLayout()
            self.layout.addWidget(self.label)
            self.layout.addWidget(self.ok)
            self.layout.addWidget(self.cancel)
            self.setLayout(self.layout)

        def setDirection(self, direction):
            self.layout.setDirection(direction)


    class SecondaryDashboard(QtWidgets.QWidget):

        def __init__(self):

            super(SecondaryDashboard, self).__init__()

            self.init_widgets()
            self.init_layout()

        def init_widgets(self):
            self.label = QtWidgets.QLabel('Secondary dashboard')

            self.descr1 = QtWidgets.QLabel('Thing 1')
            self.check1 = QtWidgets.QCheckBox()

            self.descr2 = QtWidgets.QLabel('Thing 2')
            self.check2 = QtWidgets.QCheckBox()

        def init_layout(self):
            self.layout = QtWidgets.QVBoxLayout()

            self.grid = QtWidgets.QGridLayout()
            self.grid.addWidget(self.descr1, 0, 0)
            self.grid.addWidget(self.check1, 0, 1)
            self.grid.addWidget(self.descr2, 1, 0)
            self.grid.addWidget(self.check2, 1, 1)

            self.layout.addWidget(self.label)
            self.layout.addLayout(self.grid)
            self.setLayout(self.layout)

        def setDirection(self, direction):
            self.layout.setDirection(direction)


    class DockContainer(QtWidgets.QStackedWidget):

        def __init__(self):

            super(DockContainer, self).__init__()


    class MyDock(QtWidgets.QDockWidget):

        def __init__(self):

            super(MyDock, self).__init__()

        def resizeEvent(self, event):
            size = event.size()
            is_wide = size.width() > size.height()

            container_object = self.widget().currentWidget()

            if is_wide:
                container_object.setDirection(QtWidgets.QBoxLayout.LeftToRight)
            else:
                container_object.setDirection(QtWidgets.QBoxLayout.TopToBottom)


    class MyWindow(QtWidgets.QMainWindow):

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

            self.resize(600, 400)

            self.init_widgets()
            self.init_layout()

        def init_widgets(self):

            self.tab_widget = QtWidgets.QTabWidget()
            self.tab1 = QtWidgets.QLabel('Primary content')
            self.tab2 = QtWidgets.QLabel('Secondary content')
            self.tab_widget.addTab(self.tab1, 'Primary')
            self.tab_widget.addTab(self.tab2, 'Secondary')
            self.tab_widget.currentChanged.connect(self.tab_selected)

            self.primary_dashboard = PrimaryDashboard()
            self.secondary_dashboard = SecondaryDashboard()

            self.dashboard = DockContainer()
            self.dashboard.addWidget(self.primary_dashboard)
            self.dashboard.addWidget(self.secondary_dashboard)
            self.dashboard.setCurrentWidget(self.primary_dashboard)

            self.dock = MyDock()
            self.dock.setWidget(self.dashboard)
            self.addDockWidget(QtCore.Qt.BottomDockWidgetArea, self.dock)

        def init_layout(self):
            self.main_layout = QtWidgets.QVBoxLayout()
            self.main_layout.addWidget(self.tab_widget)

            self.main_widget = QtWidgets.QWidget()
            self.main_widget.setLayout(self.main_layout)
            self.setCentralWidget(self.main_widget)

        def tab_selected(self):
            tab_index = self.tab_widget.currentIndex()
            if self.tab_widget.tabText(tab_index) == 'Secondary':
                self.dashboard.setCurrentWidget(self.secondary_dashboard)
                self.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.dock)
            else:  # Primary
                self.dashboard.setCurrentWidget(self.primary_dashboard)
                self.addDockWidget(QtCore.Qt.BottomDockWidgetArea, self.dock)


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

        window = MyWindow()
        window.show()
        sys.exit(app.exec_())

dockLocationChanged方法

此代码与 基本应用程序代码 相同,但是将dockLocationChanged信号连接到根据当前停靠位置调整方向的方法。

    import qtpy
    from qtpy import QtWidgets, QtGui, QtCore
    import sys


    class PrimaryDashboard(QtWidgets.QWidget):

        def __init__(self):

            super(PrimaryDashboard, self).__init__()

            self.init_widgets()
            self.init_layout()

        def init_widgets(self):
            self.label = QtWidgets.QLabel('Primary dashboard')
            self.ok = QtWidgets.QPushButton('OK')
            self.cancel = QtWidgets.QPushButton('Cancel')

        def init_layout(self):
            self.layout = QtWidgets.QHBoxLayout()
            self.layout.addWidget(self.label)
            self.layout.addWidget(self.ok)
            self.layout.addWidget(self.cancel)
            self.setLayout(self.layout)

        def setDirection(self, direction):
            self.layout.setDirection(direction)


    class SecondaryDashboard(QtWidgets.QWidget):

        def __init__(self):

            super(SecondaryDashboard, self).__init__()

            self.init_widgets()
            self.init_layout()

        def init_widgets(self):
            self.label = QtWidgets.QLabel('Secondary dashboard')

            self.descr1 = QtWidgets.QLabel('Thing 1')
            self.check1 = QtWidgets.QCheckBox()

            self.descr2 = QtWidgets.QLabel('Thing 2')
            self.check2 = QtWidgets.QCheckBox()

        def init_layout(self):
            self.layout = QtWidgets.QVBoxLayout()

            self.grid = QtWidgets.QGridLayout()
            self.grid.addWidget(self.descr1, 0, 0)
            self.grid.addWidget(self.check1, 0, 1)
            self.grid.addWidget(self.descr2, 1, 0)
            self.grid.addWidget(self.check2, 1, 1)

            self.layout.addWidget(self.label)
            self.layout.addLayout(self.grid)
            self.setLayout(self.layout)

        def setDirection(self, direction):
            self.layout.setDirection(direction)


    class DockContainer(QtWidgets.QStackedWidget):

        def __init__(self):

            super(DockContainer, self).__init__()


    class MyDock(QtWidgets.QDockWidget):

        def __init__(self):

            super(MyDock, self).__init__()

            self.dockLocationChanged.connect(self.dock_location_changed)

        def dock_location_changed(self, area):
            top    = QtCore.Qt.DockWidgetArea.TopDockWidgetArea
            bottom = QtCore.Qt.DockWidgetArea.BottomDockWidgetArea
            # left   = QtCore.Qt.DockWidgetArea.LeftDockWidgetArea
            # right  = QtCore.Qt.DockWidgetArea.RightDockWidgetArea

            container_object = self.widget().currentWidget()

            if area in [top, bottom]:
                container_object.setDirection(QtWidgets.QBoxLayout.LeftToRight)
            else:
                container_object.setDirection(QtWidgets.QBoxLayout.TopToBottom)


    class MyWindow(QtWidgets.QMainWindow):

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

            self.resize(600, 400)

            self.init_widgets()
            self.init_layout()

        def init_widgets(self):

            self.tab_widget = QtWidgets.QTabWidget()
            self.tab1 = QtWidgets.QLabel('Primary content')
            self.tab2 = QtWidgets.QLabel('Secondary content')
            self.tab_widget.addTab(self.tab1, 'Primary')
            self.tab_widget.addTab(self.tab2, 'Secondary')
            self.tab_widget.currentChanged.connect(self.tab_selected)

            self.primary_dashboard = PrimaryDashboard()
            self.secondary_dashboard = SecondaryDashboard()

            self.dashboard = DockContainer()
            self.dashboard.addWidget(self.primary_dashboard)
            self.dashboard.addWidget(self.secondary_dashboard)
            self.dashboard.setCurrentWidget(self.primary_dashboard)

            self.dock = MyDock()
            self.dock.setWidget(self.dashboard)
            self.addDockWidget(QtCore.Qt.BottomDockWidgetArea, self.dock)

        def init_layout(self):
            self.main_layout = QtWidgets.QVBoxLayout()
            self.main_layout.addWidget(self.tab_widget)

            self.main_widget = QtWidgets.QWidget()
            self.main_widget.setLayout(self.main_layout)
            self.setCentralWidget(self.main_widget)

        def tab_selected(self):
            tab_index = self.tab_widget.currentIndex()
            if self.tab_widget.tabText(tab_index) == 'Secondary':
                self.dashboard.setCurrentWidget(self.secondary_dashboard)
                self.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.dock)
            else:  # Primary
                self.dashboard.setCurrentWidget(self.primary_dashboard)
                self.addDockWidget(QtCore.Qt.BottomDockWidgetArea, self.dock)


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

        window = MyWindow()
        window.show()
        sys.exit(app.exec_())

1 个答案:

答案 0 :(得分:0)

将应用程序视为一组Matryoshka dolls。内部玩偶的大小决定了随后的外部玩偶的大小。显然,一个内在的洋娃娃不能大于其中的一个! QWidgets的建模方式与此类似。

  

默认情况下,不提供大小提示的复合窗口小部件将   可以根据其子窗口小部件的空间要求进行调整大小。

QWidget.sizeHint()的文档继续说,

  

QWidget.sizeHint()的默认实现返回   如果此小部件没有布局,则尺寸无效,并返回   布局的首选尺寸。

总而言之,小部件的大小从内到外根据布局而定。

如果要为基本应用程序代码中的每个对象实现resizeEvent 1 ,您将看到以下大小调整顺序,< / p>

  1. PrimaryDashboard resizeEvent
  2. DockContainer resizeEvent
  3. MyDock resizeEvent

这是我们期望的嵌套。首先咨询PrimaryDashboard的大小,然后咨询DockContainer,然后咨询MyDock。从技术上讲,它一直是小部件。但是,PrimaryDashboard包含的按钮和标签在大多数情况下应小于MainWindow的宽度/高度。序列中第一个会显着影响码头尺寸的玩偶是PrimaryDashboard

使用resizeEvent检查event.size(),我们可以看到水平仪表板的合理最小值是120像素的高度,而垂直方向的合理最小宽度是{ {1}}。然后可以将146设置为返回sizeHint(),并使每个仪表板 2 的最小值返回minimumSizeHint()。实际上,这告诉应用程序首选最小尺寸为(146, 120),同时仍允许总体上调整大小。

(146, 120)

使用固定的大小,这是很危险的,因为绝对值是不容忍的,并且根据定义不灵活。但是,内容可能具有自然的最小大小 3 。我们可以简单地在整个应用程序上def sizeHint(self): return self.minimumSizeHint() def minimumSizeHint(self): return QtCore.QSize(146, 120) ,而不是将大小调整为小于setMinimumSize()

要更改停靠窗口小部件内容的方向,我们可以使用minimumSizeHint()信号。我们还可以使代码比问题中的代码更加整洁。可以在dockLocationChanged内的实例级别连接它,而不是在Dock小部件中连接信号。实际上,根本不需要定义MyWindow。普通的MyDock就足够了。

最小尺寸的船坞

QDockWidget

1。如何实现这样的import qtpy from qtpy import QtWidgets, QtGui, QtCore import sys class PrimaryDashboard(QtWidgets.QWidget): def __init__(self): super(PrimaryDashboard, self).__init__() self.init_widgets() self.init_layout() def init_widgets(self): self.label = QtWidgets.QLabel('Primary dashboard') self.ok = QtWidgets.QPushButton('OK') self.cancel = QtWidgets.QPushButton('Cancel') def init_layout(self): self.layout = QtWidgets.QHBoxLayout() self.layout.addWidget(self.label) self.layout.addWidget(self.ok) self.layout.addWidget(self.cancel) self.setLayout(self.layout) def setDirection(self, direction): self.layout.setDirection(direction) def sizeHint(self): return self.minimumSizeHint() def minimumSizeHint(self): return QtCore.QSize(146, 120) class SecondaryDashboard(QtWidgets.QWidget): def __init__(self): super(SecondaryDashboard, self).__init__() self.init_widgets() self.init_layout() def init_widgets(self): self.label = QtWidgets.QLabel('Secondary dashboard') self.descr1 = QtWidgets.QLabel('Thing 1') self.check1 = QtWidgets.QCheckBox() self.descr2 = QtWidgets.QLabel('Thing 2') self.check2 = QtWidgets.QCheckBox() def init_layout(self): self.layout = QtWidgets.QVBoxLayout() self.grid = QtWidgets.QGridLayout() self.grid.addWidget(self.descr1, 0, 0) self.grid.addWidget(self.check1, 0, 1) self.grid.addWidget(self.descr2, 1, 0) self.grid.addWidget(self.check2, 1, 1) self.layout.addWidget(self.label) self.layout.addLayout(self.grid) self.setLayout(self.layout) def setDirection(self, direction): self.layout.setDirection(direction) def sizeHint(self): return self.minimumSizeHint() def minimumSizeHint(self): return QtCore.QSize(146, 120) class DockContainer(QtWidgets.QStackedWidget): def __init__(self): super(DockContainer, self).__init__() def dock_location_changed(self, area): top = QtCore.Qt.DockWidgetArea.TopDockWidgetArea bottom = QtCore.Qt.DockWidgetArea.BottomDockWidgetArea container_object = self.currentWidget() if area in [top, bottom]: container_object.setDirection(QtWidgets.QBoxLayout.LeftToRight) else: container_object.setDirection(QtWidgets.QBoxLayout.TopToBottom) class MyWindow(QtWidgets.QMainWindow): def __init__(self, parent=None): super(MyWindow, self).__init__(parent=parent) # Force minimumSize to ensure a sensible dashboard size self.setMinimumSize(QtCore.QSize(600, 400)) self.init_widgets() self.init_layout() def init_widgets(self): self.tab_widget = QtWidgets.QTabWidget() self.tab1 = QtWidgets.QLabel('Primary content') self.tab2 = QtWidgets.QLabel('Secondary content') self.tab_widget.addTab(self.tab1, 'Primary') self.tab_widget.addTab(self.tab2, 'Secondary') self.tab_widget.currentChanged.connect(self.tab_selected) self.primary_dashboard = PrimaryDashboard() self.secondary_dashboard = SecondaryDashboard() self.dashboard = DockContainer() self.dashboard.addWidget(self.primary_dashboard) self.dashboard.addWidget(self.secondary_dashboard) self.dashboard.setCurrentWidget(self.primary_dashboard) self.dock = QtWidgets.QDockWidget() self.dock.setWidget(self.dashboard) self.addDockWidget(QtCore.Qt.BottomDockWidgetArea, self.dock) # Connect signal at the main application level self.dock.dockLocationChanged.connect(self.dashboard.dock_location_changed) def init_layout(self): self.main_layout = QtWidgets.QVBoxLayout() self.main_layout.addWidget(self.tab_widget) self.main_widget = QtWidgets.QWidget() self.main_widget.setLayout(self.main_layout) self.setCentralWidget(self.main_widget) def tab_selected(self): tab_index = self.tab_widget.currentIndex() if self.tab_widget.tabText(tab_index) == 'Secondary': self.dashboard.setCurrentWidget(self.secondary_dashboard) self.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.dock) else: # Primary self.dashboard.setCurrentWidget(self.primary_dashboard) self.addDockWidget(QtCore.Qt.BottomDockWidgetArea, self.dock) if __name__ == '__main__': app = QtWidgets.QApplication(sys.argv) window = MyWindow() window.show() sys.exit(app.exec_()) 来查看谁正在调整大小:

resizeEvent

2。一个自然的问题是,“为什么不简单地将 def resizeEvent(self, event): print('resizeEvent for ', self, flush=True) 设置为返回最小大小而不是调用sizeHint()我得到的最佳答复是,“它不能那样工作。” 仅设置minimumSizeHint()并不能将扩展坞的大小调整到您所期望的最小。

sizeHint()sizeHint()方法是虚函数。我的猜测是Qt还有其他我们不了解的功能,它们独立地引用这些方法,要求我们以这种方式定义事物。

3。例如,如果内容是地图,则用户不太可能希望该地图为minimumSizeHint()。此外,有合理的假设,除非用户在移动设备上,否则其屏幕分辨率不会低于10px x 10px。但是,如果您正在使用PySide或PyQt5开发移动设备,则应该问自己一些重要问题,例如“为什么?”

相关问题