TL; DR :我希望从其PyQt5的loadUiType()函数直接替换为可与PySide2和Python 3.6+一起使用的uic模块。

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

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中进行了分配。

为了提供可重现的示例,以下是与上述Python代码一起使用的最小Qt设计文件,在其中将其称为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",但前提是加载的对象可以在单独的类中使用,这本身并不是我关心的问题。另外,(当前)唯一的答案不是我正在寻找的解决方案。其他问题及其答案(12)确定可以通过QtUiTools.QUiLoader().load('design.ui')将设计文件动态加载到PySide2中,但该方法将返回小部件对象,而不是所需的表单类。

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

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

2013年的This answer完美地演示了这一点,但对于PySide(基于Qt4)和(旧版)Python 2来说,如何使该代码适应在(现代)Python上运行的PySide2(基于Qt5)?

最佳答案

以下是对上述引用的较早答案中提供的解决方案的Python 3.6或更高版本以及PySide2 5.13或更高版本(请参阅结尾处的注释)的改编:

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 pyside2pyside2-tools)上使用Python 3.7.3和PySide2 5.12.3进行了测试。

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

09-26 20:48