Qt Designer和PyQt程序中verticalLayout的大小不同

2022-01-12 00:00:00 python pyqt5 qt-designer

问题描述

在 Qt Designer 5.9 中 test.ui 中的 verticalLayout 与窗口边缘有一定距离,但在加载 test.uimain.py 中使用 PyQt 5.11.3,verticalLayout 会扩展到窗口的边缘.

In Qt Designer 5.9 the verticalLayout in test.ui had a certain distance to the edge of the window, but after loading test.ui with PyQt 5.11.3 in main.py the verticalLayout extend to the edge of the window.

ma​​in.py:

    #!/usr/bin/python3
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.uic import loadUi

class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super().__init__()
        loadUi("test.ui", self)

def main():
    app = QApplication(sys.argv)
    main_window = MainWindow(app)
    main_window.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
main()

test.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>350</width>
    <height>257</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Test</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QGridLayout" name="gridLayout_2">
    <item row="0" column="0">
     <layout class="QVBoxLayout" name="verticalLayout">
      <property name="leftMargin">
       <number>0</number>
      </property>
      <property name="topMargin">
       <number>0</number>
      </property>
      <item>
       <spacer name="verticalSpacer_2">
        <property name="orientation">
         <enum>Qt::Vertical</enum>
        </property>
        <property name="sizeHint" stdset="0">
         <size>
          <width>20</width>
          <height>40</height>
         </size>
        </property>
       </spacer>
      </item>
      <item>
       <widget class="QGroupBox" name="groupBox">
        <layout class="QGridLayout" name="gridLayout">
         <item row="1" column="1">
          <widget class="QSpinBox" name="spinBox">
           <property name="maximum">
            <number>100000</number>
           </property>
           <property name="value">
            <number>1000</number>
           </property>
          </widget>
         </item>
         <item row="1" column="0">
          <widget class="QLabel" name="label_2">
           <property name="text">
            <string>Test</string>
           </property>
           <property name="alignment">
            <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
           </property>
          </widget>
         </item>
         <item row="0" column="1">
          <widget class="QComboBox" name="comboBox">
           <property name="currentText">
            <string/>
           </property>
          </widget>
         </item>
         <item row="0" column="0">
          <widget class="QLabel" name="label1">
           <property name="text">
            <string>Test</string>
           </property>
           <property name="alignment">
            <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
           </property>
          </widget>
         </item>
        </layout>
       </widget>
      </item>
      <item>
       <spacer name="verticalSpacer">
        <property name="orientation">
         <enum>Qt::Vertical</enum>
        </property>
        <property name="sizeHint" stdset="0">
         <size>
          <width>20</width>
          <height>40</height>
         </size>
        </property>
       </spacer>
      </item>
      <item>
       <widget class="QPushButton" name="pushButton">
        <property name="layoutDirection">
         <enum>Qt::LeftToRight</enum>
        </property>
        <property name="text">
         <string>Test</string>
        </property>
       </widget>
      </item>
     </layout>
    </item>
   </layout>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>350</width>
     <height>28</height>
    </rect>
   </property>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
 </widget>
 <resources/>
 <connections/>
</ui>

<小时>

Qt Designer 5.9 中 test.ui 的屏幕截图:

main.py 加载 test.ui 的截图:

这种行为的原因是什么?

What's the reason for this behavior?


解决方案

查看uic.loadUi()的代码会发现如下代码:

If you check the code of uic.loadUi() you will find the following code:

uiparser.py

class UIParser(object):  
    # ...
    def createLayout(self, elem):
        # ...
        margin = -1 if self.stack.topIsLayout() else self.defaults['margin']
        margin = self.wprops.getProperty(elem, 'margin', margin)
        left = self.wprops.getProperty(elem, 'leftMargin', margin)
        top = self.wprops.getProperty(elem, 'topMargin', margin)
        right = self.wprops.getProperty(elem, 'rightMargin', margin)
        bottom = self.wprops.getProperty(elem, 'bottomMargin', margin)
        # A layout widget should, by default, have no margins.
        if self.stack.topIsLayoutWidget():
            if left < 0: left = 0
            if top < 0: top = 0
            if right < 0: right = 0
            if bottom < 0: bottom = 0

    def topIsLayoutWidget(self):
        # A plain QWidget is a layout widget unless it's parent is a
        # QMainWindow or a container widget.  Note that the corresponding uic
        # test is a little more complicated as it involves features not
        # supported by pyuic.

        if type(self[-1]) is not QtWidgets.QWidget:
            return False

        if len(self) < 2:
            return False

        parent = self[-2]

        return isinstance(parent, QtWidgets.QWidget) and type(parent) not in (
                QtWidgets.QMainWindow,
                QtWidgets.QStackedWidget,
                QtWidgets.QToolBox,
                QtWidgets.QTabWidget,
                QtWidgets.QScrollArea,
                QtWidgets.QMdiArea,
                QtWidgets.QWizard,
                QtWidgets.QDockWidget)

问题是由 topIsLayoutWidget() 函数引起的,因为父级将引用用作基础的小部件,在这种情况下 MainWindow 符合 isinstance(parent, QtWidgets.QWidget) 和类型 (parent) not in (QtWidgets.QMainWindow, ...) 所以 topIsLayoutWidget() 将返回 True,所以左、上、右、下为 -1由于这些属性不存在将被更新为 0,因此将通过消除默认值 (9, 9, 9, 9) 来建立对 contentsMargins 的应用,但在 Qt Designer 的情况下, contentsMarginscode>contentsMargins 尚未更新,保持其默认值.

The problem is caused by the topIsLayoutWidget() function since the parent will refer to the widget that is used as a base, in this case MainWindow complies with isinstance(parent, QtWidgets.QWidget) and type (parent) not in (QtWidgets.QMainWindow, ...) so topIsLayoutWidget() will return True, so to be left, top, right, bottom be -1 since those properties do not exist will be updated to 0 so the apply to contentsMargins will be established by eliminating the default value (9, 9, 9, 9), but in the case of Qt Designer the contentsMargins have not been updated maintaining their default value.

所以总之是一个 pyqt 错误,它也在评论中指出:

So in conclusion is a pyqt bug that also points in the comments:

# ... Note that the corresponding uic
# test is a little more complicated as it involves features not
# supported by pyuic.*

所以有几种解决方案:

  • 删除:

if self.stack.topIsLayoutWidget():
    if left < 0: left = 0
    if top < 0: top = 0
    if right < 0: right = 0
    if bottom < 0: bottom = 0

  • 使用uic.loadUiType():

    #!/usr/bin/python3
    import sys
    from PyQt5 import QtCore, QtWidgets, uic
    
    Ui_Interface, _ = uic.loadUiType('test.ui')
    
    class MainWindow(QtWidgets.QMainWindow, Ui_Interface):
        def __init__(self, parent=None):
            super().__init__(parent)
            self.setupUi(self)
    
    def main():
        app = QtWidgets.QApplication(sys.argv)
        main_window = MainWindow()
        main_window.show()
        sys.exit(app.exec_())
    
    if __name__ == "__main__":
        main()
    

  • 我更喜欢第二种解决方案,因为不应修改源代码.

    I prefer the second solution since the source code should not be modified.

    相关文章