布局不会扩展超过使用设计器设置的初始大小

Layout doesn't expand more than initial size it was set using designer

我正在尝试根据 GUI window 大小调整我的小部件的大小,但卡住了。

首先,我使用设计器放置了一些小部件,包括 QVBoxLayout,并使用 PyQt5.uic.loadUi() 加载它。

之后我尝试通过 resizeEvent:

调整一些小部件的大小
def resizeEvent(self, event):
    print("Resized")
    QtWidgets.QMainWindow.resizeEvent(self, event)
    self.resize_widget(self.label, 'both', self.ratio_label)
    self.resize_widget(self.slider_cv2, 'width', self.ratio_slider_cv2)
    self.resize_widget(self.graph_layout, 'both', self.ratio_graph_layout)

def resize_widget(self, widget, mode, ratio):
    if mode == 'width':
        widget.resize(int(self.size().width() * ratio), widget.geometry().height())
    elif mode == 'height':
        widget.resize(widget.geometry().width(), int(self.size().height() * ratio))
    else:
        try:
            widget.resize(int(self.size().width() * ratio[0]), int(self.size().height() * ratio[1]))
        except AttributeError:
            widget.setGeometry(QtCore.QRect(widget.geometry().left(), widget.geometry().top(), int(self.geometry().width() * ratio[0]), int(self.geometry().height() * ratio[1])))

self.labelself.slider_cv2self.graph_layout分别是QlabelQSliderQVBoxLayout的实例。

self.labelself.slider_cv2 的大小如我所料动态变化,但 self.graph_layout 没有。当 GUI window 变小时,它会变小,但不会增长到大于其初始大小。


添加了最少的可重现代码:

from PyQt5 import QtWidgets, uic, QtCore
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent, QVideoFrame
from PyQt5.QtCore import QUrl, QTimer
from PyQt5.QtGui import QPixmap, QImage
import sys
import os
import threading
app = QtWidgets.QApplication(sys.argv)
import cv2
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from time import sleep
import numpy as np


class UI(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        uic.loadUi('/workspace/demo_cv2_load_entire_layout.ui', self)

        self.fig = plt.Figure()
        self.canvas = FigureCanvas(self.fig)
        self.graph_layout.addWidget(self.canvas)

        #---------For resizeing widget-----------
        w, h = self.size().width(), self.size().height()
        self.graph_layout.activate()
        self.ratio_graph_layout = (self.graph_layout.geometry().width() / w, self.graph_layout.geometry().height() / h)
        self.ratio_label = (self.label.size().width() / w, self.label.size().height() / h)
        self.ratio_slider_cv2 = self.slider_cv2.size().width() / w
        self.initial_wh = (w, h)
        #----------------------------------------
        self.show()
        
    def resizeEvent(self, event):
        print("Resized")
        QtWidgets.QMainWindow.resizeEvent(self, event)
        self.resize_widget(self.label, 'both', self.ratio_label)
        self.resize_widget(self.slider_cv2, 'width', self.ratio_slider_cv2)
        self.resize_widget(self.graph_layout, 'both', self.ratio_graph_layout)
    
    def resize_widget(self, widget, mode, ratio):
        if mode == 'width':
            widget.resize(int(self.size().width() * ratio), widget.geometry().height())
        elif mode == 'height':
            widget.resize(widget.geometry().width(), int(self.size().height() * ratio))
        else:
            try:
                widget.resize(int(self.size().width() * ratio[0]), int(self.size().height() * ratio[1]))
            except AttributeError:
                widget.setGeometry(QtCore.QRect(widget.geometry().left(), widget.geometry().top(), int(self.geometry().width() * ratio[0]), int(self.geometry().height() * ratio[1])))
        print(widget.geometry())


if __name__ == "__main__":
    window = UI()
    app.exec_()

和.UI文件:

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>1018</width>
    <height>814</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <widget class="QWidget" name="verticalLayoutWidget">
    <property name="geometry">
     <rect>
      <x>240</x>
      <y>410</y>
      <width>291</width>
      <height>151</height>
     </rect>
    </property>
    <layout class="QVBoxLayout" name="graph_layout">
     <property name="sizeConstraint">
      <enum>QLayout::SetMaximumSize</enum>
     </property>
    </layout>
   </widget>
   <widget class="QSlider" name="slider_cv2">
    <property name="geometry">
     <rect>
      <x>220</x>
      <y>620</y>
      <width>349</width>
      <height>15</height>
     </rect>
    </property>
    <property name="orientation">
     <enum>Qt::Horizontal</enum>
    </property>
   </widget>
   <widget class="QLabel" name="label">
    <property name="geometry">
     <rect>
      <x>90</x>
      <y>0</y>
      <width>711</width>
      <height>371</height>
     </rect>
    </property>
    <property name="frameShape">
     <enum>QFrame::Box</enum>
    </property>
    <property name="text">
     <string>Video</string>
    </property>
   </widget>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>1018</width>
     <height>20</height>
    </rect>
   </property>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
 </widget>
 <resources/>
 <connections/>
</ui>

如果您想手动调整元素的大小,则不应使用布局。在此您必须使用 QWidget 作为容器,然后使用布局将 FigureCanvas 放置在该容器中。

另一方面,我实现了一种逻辑,可以简化并允许轻松处理调整大小功能,而无需覆盖 resizeEvent。

*.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>1018</width>
    <height>814</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <widget class="QSlider" name="slider_cv2">
    <property name="geometry">
     <rect>
      <x>220</x>
      <y>620</y>
      <width>349</width>
      <height>15</height>
     </rect>
    </property>
    <property name="orientation">
     <enum>Qt::Horizontal</enum>
    </property>
   </widget>
   <widget class="QLabel" name="label">
    <property name="geometry">
     <rect>
      <x>90</x>
      <y>0</y>
      <width>711</width>
      <height>371</height>
     </rect>
    </property>
    <property name="frameShape">
     <enum>QFrame::Box</enum>
    </property>
    <property name="text">
     <string>Video</string>
    </property>
   </widget>
   <widget class="QWidget" name="graph_container" native="true">
    <property name="geometry">
     <rect>
      <x>240</x>
      <y>390</y>
      <width>291</width>
      <height>151</height>
     </rect>
    </property>
   </widget>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>1018</width>
     <height>28</height>
    </rect>
   </property>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
 </widget>
 <resources/>
 <connections/>
</ui>

*.py

import os
import sys
from dataclasses import dataclass
from enum import IntFlag, auto
from pathlib import Path


from PyQt5 import QtWidgets, uic, QtCore

from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas


class ModeRatio(IntFlag):
    NONE = auto()
    WIDTH = auto()
    HEIGHT = auto()
    BOTH = WIDTH | HEIGHT


@dataclass
class SizeManager(QtCore.QObject):
    widget: QtWidgets.QWidget
    mode: ModeRatio

    def __post_init__(self):
        super().__init__(self.widget)
        self.widget.window().installEventFilter(self)
        self._width_ratio = self.widget.width() * 1.0 / self.widget.window().width()
        self._height_ratio = self.widget.height() * 1.0 / self.widget.window().height()

    def eventFilter(self, obj, event):
        if obj is self.widget.window():
            if event.type() == QtCore.QEvent.Resize:
                self.apply_resize()
        return super().eventFilter(obj, event)

    def apply_resize(self):
        width = self.widget.width()
        height = self.widget.height()
        if self.mode & ModeRatio.WIDTH:
            width = self.widget.window().width() * self._width_ratio
        if self.mode & ModeRatio.HEIGHT:
            height = self.widget.window().height() * self._height_ratio
        self.widget.resize(QtCore.QSizeF(width, height).toSize())


class UI(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()

        filename = os.fspath(
            Path(__file__).resolve().parent / "demo_cv2_load_entire_layout.ui"
        )
        uic.loadUi(filename, self)

        self.fig = Figure()
        self.canvas = FigureCanvas(self.fig)

        lay = QtWidgets.QVBoxLayout(self.graph_container)
        lay.addWidget(self.canvas)

        self.label_size_manager = SizeManager(widget=self.label, mode=ModeRatio.BOTH)
        self.slider_size_manager = SizeManager(
            widget=self.slider_cv2, mode=ModeRatio.WIDTH
        )
        self.graph_size_manager = SizeManager(
            widget=self.graph_container, mode=ModeRatio.BOTH
        )


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

    window = UI()
    window.show()

    app.exec_()
├── demo_cv2_load_entire_layout.ui
└── main.py

首先,感谢@eyllanesc 为问题提供了直接而优雅的解决方案。

这是使用 qt-designer 设置类似 属性 的方法: 将布局放在 QWidget.

之前尝试过直接修改layout或者mainwindow的centralwidget,但是都失败了

很奇怪 self.graph_layout.geometry() returns 类似 (0, 0, 200, 100) 的东西,尽管它在主窗口的视角下远低于 (0, 0)。 所以我认为有一个不可见的父对象(即使 self.graph_layout.parent() returns mainwindow 的 centralwidget),并通过明确地将布局放在一个小部件中来替换这个幽灵父对象。

此外,用户必须将小部件的布局(通过右键单击)设置为Layout HorizontallyLayout Vertically等,以使布局的大小自动跟随小部件。