pythonuser-interfacepysideqt-designeruic

How do I load children from .ui file in PySide?


For now I'm loading them like this:

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

But I think it's uncomfortably, is there any other way, probably to load whole namespace or whatever?


Solution

  • UPDATE:

    The original solution below was written for PySide (Qt4). It still works with both PySide2 (Qt5) and PySide6 (Qt6), but with a couple of provisos:

    (In addition, it should be mentioned that PySide2 and PySide6 now have a loadUiType function, which at first glance seems to provide a much simpler solution. However, the current implementation has the major drawback of requiring the Qt uic tool to be installed on the system and executable from the user's PATH. This certainly isn't always guaranteed to be the case, so it's debateable whether it's suitable for use in a production environment).

    Below is an updated demo that illustrates the two features noted above:

    screenshot

    test.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>
    

    Original Solution:

    At the moment, the PySide QUiLoader class doesn't have a convenient way to load widgets into to an instance of the top-level class like the PyQt uic module has.

    However, it's fairly easy to add something equivalent:

    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
    

    Which could then used like this:

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

    For this to work properly, the baseinstance argument of loadUi has to be an instance of the top-level class from Qt Designer file. All the other widgets will then be added to it as instance attributes.