PySide2 эквивалент PyQt5 loadUiType () для динамического смешивания в дизайн пользовательского интерфейса - PullRequest
0 голосов
/ 19 июня 2019

TL; DR : я хочу замену функции loadUiType() PyQt5 из ее модуля uic, который работает с PySide2 и Python 3.6 +.


Я хочу перенести приложение PyQt5 на PySide2. Распространенный шаблон, который я использую, заключается в том, что я создаю дизайн пользовательского интерфейса в Qt Designer и динамически загружаю полученный файл .ui в виде смешанного класса, расширяющего виджет Qt в коде Python, например самого главного окна:

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 с модулем Python .py в командной строке. Что еще более важно, шаблон ввода позволяет мне получить доступ ко всем виджетам Qt, определенным в дизайне, через self.name в области виджетов импорта, где 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 не предоставляет эквивалент функции uic.loadUiType() PyQt5, которая, что важно, возвращает класс формы дизайна , который будет использоваться в качестве дополнения.

Есть связанный вопрос, "PyQt5 to PySide2, загрузка UI-файлов в разные классы" , но его предпосылка заключается в том, что загруженные объекты можно использовать из отдельного класса, что не является моей заботой само по себе. Плюс, (в настоящее время) единственный ответ на это не решение, которое я ищу. Другие вопросы и ответы на них ( 1 , 2 ) устанавливают, что файлы дизайна могут динамически загружаться в PySide2 через QtUiTools.QUiLoader().load('design.ui'), но этот метод возвращает виджет объект, не обязательный класс формы.

Последний подход, без смешивания в импортированном классе, потребовал бы от меня изменения многих строк кода для миграции, поскольку это приводит к другой иерархии объектов переменных экземпляра Python. В приведенном выше примере self.label необходимо будет переименовать во что-то вроде self.ui.label по всей базе кода.

То, что я хочу, - это полная замена функции loadUiType(design) PyQt5 из ее модуля uic, который работает с PySide2 и Python 3.6+, где design обозначает путь к файлу .ui.

Этот ответ от 2013 года отлично демонстрирует это, но для PySide (на основе Qt4) и (устаревшего) Python 2. Как мне адаптировать этот код к PySide2 (на основе Qt5), работающему на ( модерн) питон?

1 Ответ

0 голосов
/ 19 июня 2019

Ниже приведена адаптация для PySide2 и Python 3.6+ решения, представленного в приведенном выше предыдущем ответе:

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)

Если сохранить как uic.py рядом с основным модулем Python, толькоНеобходимо изменить операторы import, чтобы перенести пример в вопросе с PySide2 на PyQt5:

from PySide2 import QtWidgets
import uic

Протестировано с Python 3.7.3 и PySide2 в Windows 10 (установлено через pip install pyside2) иManjaro Linux 18.0.4 (через pacman -пакеты pyside2 и pyside2-tools).

(Решение также должно работать со старыми выпусками Python 3, если newfangled f-струны, во всей их красоте, были бесцеремонно заменены их ужасно отсталыми предшественниками.)

...