简体   繁体   English

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

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

TL;DR : I want a drop-in replacement for PyQt5's loadUiType() function from its uic module that works with PySide2 and Python 3.6+. TL; DR :我想从它的uic模块中替换 PyQt5 的loadUiType()函数,该模块可与 PySide2 和 Python 3.6+ 一起使用。


I want to migrate a PyQt5 application to PySide2.我想将 PyQt5 应用程序迁移到 PySide2。 A common pattern I use is, I create the user-interface design in Qt Designer and dynamically load the resulting .ui file as a mix-in class extending a Qt widget in the Python code, such as the main window itself:我使用的一个常见模式是,我在 Qt 设计器中创建用户界面设计并动态加载生成的.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_()

This means I can forgo compiling the .ui design to a .py Python module on the command line.这意味着我可以放弃在命令行上将.ui设计编译为.py Python 模块。 More importantly, the mix-in pattern lets me access all Qt widgets defined in the design via self.name within the scope of the importing widgets, where name is assigned as such within Qt Designer.更重要的是,混合模式让我可以在导入小部件的范围内通过self.name访问设计中定义的所有 Qt 小部件,其中name在 Qt 设计器中分配。

For the sake of providing a reproducible example, here is a minimal Qt design file to go along with the above Python code, in which it is referenced as design.ui :为了提供可重现的示例,这里有一个最小的 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>

I would like to accomplish the same, but with PySide2, and with the fewest code changes possible.我想完成同样的事情,但使用 PySide2,并且代码更改尽可能少。 The problem is, PySide2 does not provide an equivalent of PyQt5's uic.loadUiType() function, which, importantly, returns the design's form class to be used as the mix-in.问题是,PySide2 没有提供 PyQt5 的uic.loadUiType()函数的等价物,重要的是,它返回设计的表单类以用作混合。

There is a related question, "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.有一个相关的问题, “PyQt5 到 PySide2,在不同的类中加载 UI 文件” ,但它的前提是加载的对象可以从单独的类中使用,这本身不是我关心的问题。 Plus, the (currently) only answer to it is not the solution I am looking for.另外,(目前)唯一的答案不是我正在寻找的解决方案。 Other questions and their answers ( 1 , 2 ) establish that design files can be dynamically loaded in PySide2, via QtUiTools.QUiLoader().load('design.ui') , but that method returns the widget object, not the required form class.其他问题及其答案( 1 , 2 )确定设计文件可以通过QtUiTools.QUiLoader().load('design.ui')在 PySide2 中动态加载,但该方法返回小部件对象,而不是所需的表单类.

The latter approach, without mixing in the imported class, would require me to change many lines of code for the migration, as it results in a different object hierarchy of the Python instance variables.后一种方法没有在导入的类中混合,需要我为迁移更改多行代码,因为它会导致 Python 实例变量的对象层次结构不同。 In the above example, self.label would then have to be renamed to something like self.ui.label throughout the code base.在上面的例子中, self.label必须在整个代码库中重命名为self.ui.label之类的东西。

What I want is a drop-in replacement for PyQt5's loadUiType(design) function from its uic module that works with PySide2 and Python 3.6+, wherein design designates the path to a .ui file.我想要的是 PyQt5 的loadUiType(design)函数的替代品,它的uic模块可与 PySide2 和 Python 3.6+ 一起使用,其中design指定了.ui文件的路径。

This answer , from 2013, perfectly demonstrates that, but for PySide (based on Qt4) and (legacy) Python 2. How do I adapt that code to PySide2 (based on Qt5) running on (modern) Python?这个来自 2013 年的答案完美地证明了这一点,但是对于 PySide(基于 Qt4)和(旧版)Python 2。我如何将该代码改编为在(现代)Python 上运行的 PySide2(基于 Qt5)?

The following is an adaptation for Python 3.6 or newer and PySide2 5.13 or older (see note at the end) of the solution presented in the above-cited, earlier answer:以下是对 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)

If saved as uic.py alongside the main Python module, only the import statements have to be changed in order to migrate the example in the question from PyQt5 to PySide2:如果与主 Python 模块一起保存为uic.py ,则只需更改import语句即可将问题中的示例从 PyQt5 迁移到 PySide2:

from PySide2 import QtWidgets
import uic

Tested with Python 3.7.3 and PySide2 5.12.3 on Windows 10 (installed via pip install pyside2 ) and Manjaro Linux 18.0.4 (via the pacman -packages pyside2 and pyside2-tools ).在 Windows 10(通过pip install pyside2 )和 Manjaro Linux 18.0.4(通过pacman pyside2pyside2-tools )上使用 Python 3.7.3 和 PySide2 5.12.3 进行测试。


Note : The pyside2uic module used in the above solution was removed from the PySide2 code base as of version 5.14.0 (December 2019).注意:从 5.14.0 版(2019 年 12 月)起,上述解决方案中使用的pyside2uic模块已从 PySide2 代码库中删除。 However, a request was then filed on the PySide2 issue tracker to " bring back loadUiType ".但是,随后在 PySide2 问题跟踪器上提交了“恢复 loadUiType ”的请求 As of version 5.14.2.2 (May 2020), loadUiType can be imported from the QtUiTools module and works just like it does in PyQt5.由于5.14.2.2版(2020日), loadUiType可以从进口QtUiTools模块的工作原理就像它在PyQt5。 This renders the problem presented in the question obsolete.这使得问题中提出的问题已过时。

PySide2 brought back loadUiType in May 2020 . PySide2在 2020 年 5 月带回了 loadUiType So if you upgrade, you can get a drop-in replacement.因此,如果您升级,您可以获得直接替换。 The only difference is the import:唯一的区别是导入:

from PySide2.QtUiTools import loadUiType

Syntax is the same (you will use loadUiType(<file>)[0] )语法相同(您将使用loadUiType(<file>)[0]

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM