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:
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.
.ui
with PySide is generation of modules withpyside-uic
. As for dynamic approachQUiLoader
is fairly limited. – Ply