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

3.1k views Asked by At

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?

1

There are 1 answers

0
ekhumoro On BEST ANSWER

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:

  • The connectSlotsByName feature requires that the corresponding slots are decorated with an appropriate QtCore.Slot.
  • Custom/Promoted widgets aren't handled automatically. The required classes must be explicily imported and registered with registerCustomWidget before loadUi is called.

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