PySide2 等效于 PyQt5 的 loadUiType() 动态混合 UI 设计

PySide2 equivalent of PyQt5's loadUiType() to dynamically mix in UI designs

TL;DR:我想从其与 PySide2 和 [=55 一起使用的 uic 模块中直接替换 PyQt5 的 loadUiType() 函数=] 3.6+.


我想将 PyQt5 应用程序迁移到 PySide2。我使用的一个常见模式是,我在 Qt Designer 中创建用户界面设计并动态加载生成的 .ui 文件作为混合 class 在 Python 代码中扩展 Qt 小部件,比如主 window 本身:

from PyQt5 import QtWidgets, uic

class Window(QtWidgets.QMainWindow, uic.loadUiType('design.ui')[0]):

    def __init__(self):
        super().__init__()
        self.setupUi(self)
        print(self.label.text())

app = QtWidgets.QApplication([])
window = Window()
window.show()
app.exec_()

这意味着我可以放弃在命令行上将 .ui 设计编译为 .py Python 模块。更重要的是,混合模式允许我在导入小部件的范围内通过 self.name 访问设计中定义的所有 Qt 小部件,其中 name 在 Qt Designer 中如此分配。

为了提供一个可重现的例子,这里有一个最小的 Qt 设计文件与上面的 Python 代码一起使用,其中它被引用为 design.ui:

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
  <class>MainWindow</class>
  <widget class="QMainWindow" name="MainWindow">
    <widget class="QWidget" name="centralwidget">
      <widget class="QLabel" name="label">
        <property name="text">
          <string>Hi, Qt.</string>
        </property>
      </widget>
    </widget>
  </widget>
</ui>

我想完成同样的事情,但使用 PySide2,并且代码更改尽可能少。问题是,PySide2 不提供 PyQt5 的 uic.loadUiType() 函数的等价物,重要的是,returns the design's form class 用作混合。

有一个相关的问题,"PyQt5 to PySide2, loading UI-Files in different classes", but its premise is that the loaded objects be usable from a separate class, which is not my concern per se. Plus, the (currently) only answer to it is not the solution I am looking for. Other questions and their answers (, ) 确定设计文件 可以 通过 QtUiTools.QUiLoader().load('design.ui') 在 PySide2 中动态加载,但是该方法 returns widget 对象,不是要求的形式 class.

后一种方法,如果不混合导入的 class,将需要我为迁移更改许多代码行,因为它会导致 Python 实例变量的不同对象层次结构.在上面的示例中,self.label 然后必须在整个代码库中重命名为类似 self.ui.label 的内容。

我想要的是 PyQt5 的 loadUiType(design) 函数的直接替换,来自其 uic 模块,该模块与 PySide2 和 Python 3.6+ 一起使用,其中 design 指定.ui 文件的路径。

This answer,从 2013 年开始,完美地证明了这一点,但是对于 PySide(基于 Qt4)和(遗留)Python 2. 我如何使该代码适应 PySide2(基于 Qt5) 运行 在(现代)Python?

以下是 Python 3.6 或更高版本和 PySide2 5.13 或更旧版本(见末尾的注释)的改编版本 above-cited,较早的答案:

from PySide2 import QtWidgets
from pyside2uic import compileUi
from xml.etree import ElementTree
from io import StringIO

def loadUiType(design):
    """
    PySide2 equivalent of PyQt5's `uic.loadUiType()` function.

    Compiles the given `.ui` design file in-memory and executes the
    resulting Python code. Returns form and base class.
    """
    parsed_xml   = ElementTree.parse(design)
    widget_class = parsed_xml.find('widget').get('class')
    form_class   = parsed_xml.find('class').text
    with open(design) as input:
        output = StringIO()
        compileUi(input, output, indent=0)
        source_code = output.getvalue()
        syntax_tree = compile(source_code, filename='<string>', mode='exec')
        scope = {}
        exec(syntax_tree, scope)
        form_class = scope[f'Ui_{form_class}']
        base_class = eval(f'QtWidgets.{widget_class}')
    return (form_class, base_class)

如果与主 Python 模块一起另存为 uic.py,则只需更改 import 语句即可将问题中的示例从 PyQt5 迁移到 PySide2:

from PySide2 import QtWidgets
import uic

在 Windows 10(通过 pip install pyside2 安装)和 Manjaro Linux 18.0.4(通过 pacman-packages pyside2 and pyside2-tools).


注意:自版本 5.14.0(2019 年 12 月)起,上述解决方案中使用的 pyside2uic 模块已从 PySide2 代码库中删除。但是,随后在 PySide2 问题跟踪器上向“bring back loadUiType”提交了一个请求。从版本 5.14.2.2(2020 年 5 月)开始,loadUiType 可以从 QtUiTools 模块导入,并且就像在 PyQt5 中一样工作。这使得问题中提出的问题过时了。

PySide2 brought back loadUiType in May 2020。因此,如果您升级,可以获得 drop-in 替换。唯一的区别是导入:

from PySide2.QtUiTools import loadUiType

语法相同(您将使用 loadUiType(<file>)[0]