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

问题描述 投票:0回答:2

现在我像这样加载它们:

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_())

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

python user-interface pyside qt-designer uic
2个回答
10
投票

更新

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

  • connectSlotsByName功能要求用适当的QtCore.Slot来装饰相应的插槽。
  • 不会自动处理自定义/升级小部件。在调用 loadUi 之前,必须使用
    registerCustomWidget
    显式导入并注册所需的类。

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

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

screenshot

测试.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_()

main.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>213</width>
    <height>153</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>LoadUi Test</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QVBoxLayout" name="verticalLayout">
    <item>
     <widget class="MyLabel" name="customLabel">
      <property name="text">
       <string/>
      </property>
      <property name="alignment">
       <set>Qt::AlignCenter</set>
      </property>
     </widget>
    </item>
    <item>
     <widget class="QPushButton" name="testButton">
      <property name="text">
       <string>Click Me</string>
      </property>
     </widget>
    </item>
   </layout>
  </widget>
 </widget>
 <customwidgets>
  <customwidget>
   <class>MyLabel</class>
   <extends>QLabel</extends>
   <header>test</header>
  </customwidget>
 </customwidgets>
 <resources/>
 <connections/>
</ui>

原始解决方案

目前,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

然后可以像这样使用:

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)])

为了使其正常工作,

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


0
投票

使用上面的解决方案是有效的,但是当我在 MacO 上使用 PySide6 为 QDialog 实现此方法时(我没有在其他平台上测试过它),窗口始终显示在屏幕的左上角,而不是在父级上方居中.

当父级为 None 时,不只是返回

baseinstance
,而是使用
baseinstance
作为父级创建一个小部件,这对我有用。

class UiLoader(QUiLoader):
    _baseinstance = None

    def createWidget(self, classname, parent=None, name=''):
        if parent is None and self._baseinstance is not None:
            widget = super().createWidget(classname, self._baseinstance, name)
        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)
        QMetaObject.connectSlotsByName(baseinstance)
        return widget
© www.soinside.com 2019 - 2024. All rights reserved.