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

2

6

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?

Drucilladrucy answered 22/12, 2014 at 13:7 Comment(1)
Sorry that I misread your question, guess the default way to use .ui with PySide is generation of modules with pyside-uic. As for dynamic approach QUiLoader is fairly limited.Ply
D
10

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.

Delbert answered 22/12, 2014 at 21:38 Comment(0)
P
0

Using the solution above works, however, when I implement this for a QDialog with PySide6 on macOS (I haven't tested it on other platforms) the window always shows up on the top-left of my screen, instead of centered above the parent.

Instead of just returning the baseinstance when the parent is None, creating a widget with the baseinstance as parent worked for me.

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
Pupiparous answered 22/7 at 13:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.