如何在 PySide 中从 .ui 文件加载子项?

Nec*_*ron 4 python user-interface qt-designer pyside uic

现在我像这样加载它们:

if __name__ == '__main__':
    app = QApplication(sys.argv)
    loader = QUiLoader()
    file = QFile('main.ui')
    file.open(QFile.ReadOnly)
    window = loader.load(file)
    file.close()
    window.show()
    # Here:
    window.centralwidget.findChild(QListWidget, 'listWidget').addItems(['Item {0}'.format(x) for x in range(100)])
    sys.exit(app.exec_())
Run Code Online (Sandbox Code Playgroud)

但我认为这很不舒服,有没有其他方法,可能加载整个命名空间或其他什么?

ekh*_*oro 8

更新

下面的原始解决方案是为 PySide (Qt4) 编写的。它仍然适用于 PySide2 (Qt5) 和 PySide6 (Qt6),但有一些附带条件:

(此外,应该提到的是,PySide2和PySide6现在有一个loadUiType函数,乍一看似乎提供了一个更简单的解决方案。但是,当前实现的一个主要缺点是需要在系统和用户路径中的可执行文件。这当然不能保证总是如此,因此它是否适合在生产环境中使用是有争议的)。

下面是一个更新的演示,说明了上述两个功能:

截屏

测试.py

from PySide2 import QtWidgets, QtCore, QtUiTools
# from PySide6 import QtWidgets, QtCore, QtUiTools

class UiLoader(QtUiTools.QUiLoader):
    _baseinstance = None

    def createWidget(self, classname, parent=None, name=''):
        if parent is None and self._baseinstance is not None:
            widget = self._baseinstance
        else:
            widget = super().createWidget(classname, parent, name)
            if self._baseinstance is not None:
                setattr(self._baseinstance, name, widget)
        return widget

    def loadUi(self, uifile, baseinstance=None):
        self._baseinstance = baseinstance
        widget = self.load(uifile)
        QtCore.QMetaObject.connectSlotsByName(baseinstance)
        return widget


class MyLabel(QtWidgets.QLabel):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setStyleSheet('background: plum')

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        loader = UiLoader()
        loader.registerCustomWidget(MyLabel)
        loader.loadUi('main.ui', self)

    @QtCore.Slot()
    def on_testButton_clicked(self):
        self.customLabel.setText(
            '' if self.customLabel.text() else 'Hello World')

if __name__ == '__main__':

    app = QtWidgets.QApplication(['Test'])
    window = MainWindow()
    window.show()
    try:
        app.exec()
    except AttributeError:
        app.exec_()
Run Code Online (Sandbox Code Playgroud)

主.ui

from PySide2 import QtWidgets, QtCore, QtUiTools
# from PySide6 import QtWidgets, QtCore, QtUiTools

class UiLoader(QtUiTools.QUiLoader):
    _baseinstance = None

    def createWidget(self, classname, parent=None, name=''):
        if parent is None and self._baseinstance is not None:
            widget = self._baseinstance
        else:
            widget = super().createWidget(classname, parent, name)
            if self._baseinstance is not None:
                setattr(self._baseinstance, name, widget)
        return widget

    def loadUi(self, uifile, baseinstance=None):
        self._baseinstance = baseinstance
        widget = self.load(uifile)
        QtCore.QMetaObject.connectSlotsByName(baseinstance)
        return widget


class MyLabel(QtWidgets.QLabel):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setStyleSheet('background: plum')

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        loader = UiLoader()
        loader.registerCustomWidget(MyLabel)
        loader.loadUi('main.ui', self)

    @QtCore.Slot()
    def on_testButton_clicked(self):
        self.customLabel.setText(
            '' if self.customLabel.text() else 'Hello World')

if __name__ == '__main__':

    app = QtWidgets.QApplication(['Test'])
    window = MainWindow()
    window.show()
    try:
        app.exec()
    except AttributeError:
        app.exec_()
Run Code Online (Sandbox Code Playgroud)

原始解决方案

目前,PySide QUiLoader 类没有像PyQt uic 模块那样方便的方法将小部件加载到顶级类的实例中。

然而,添加等价的东西相当容易:

from PySide import QtGui, QtCore, QtUiTools

class UiLoader(QtUiTools.QUiLoader):
    _baseinstance = None

    def createWidget(self, classname, parent=None, name=''):
        if parent is None and self._baseinstance is not None:
            widget = self._baseinstance
        else:
            widget = super(UiLoader, self).createWidget(classname, parent, name)
            if self._baseinstance is not None:
                setattr(self._baseinstance, name, widget)
        return widget

    def loadUi(self, uifile, baseinstance=None):
        self._baseinstance = baseinstance
        widget = self.load(uifile)
        QtCore.QMetaObject.connectSlotsByName(widget)
        return widget
Run Code Online (Sandbox Code Playgroud)

然后可以像这样使用:

class MainWindow(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(self, parent)
        UiLoader().loadUi('main.ui', self)
        self.listWidget.addItems(['Item {0}'.format(x) for x in range(100)])
Run Code Online (Sandbox Code Playgroud)

为了使其正常工作,baseinstance的参数loadUi必须是 Qt Designer 文件中顶级类的实例。然后,所有其他小部件将作为实例属性添加到其中。