Using PyQt4's uic.loadUi, I’d like to load a .ui file and use a custom widget in it. This means using the third package
argument of uic.loadUi
which will import the package with the custom widget's class inside.
However, I wish to define the custom widget's class in the same file as where I'm calling uic.loadUi
. I’m trying to achieve this like so:
class MyCustomClass(QtWidgets.QPushButton):
""" This is my custom class for my custom widget """
def __init__(self, *args):
QtWidgets.QPushButton.__init__(self, *args)
...
sys.modules['mycustompackage'] = MyCustomClass
uic.loadUi('my_ui.ui', self, 'mycustompackage') # Loads .ui file which contains the MyCustomWidget widget
However, this returns the following error:
AttributeError: type object 'MyCustomClass' has no attribute 'MyCustomWidget'
Is there anything I could do to make this actually work? I suspect that MyCustomClass
isn't defined in the manner uic.loadUi
expects it.
In Qt Designer, I've promoted MyCustomWidget
:
<?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>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<widget class="MyCustomWidget" name="customWidget">
<property name="geometry">
<rect>
<x>50</x>
<y>70</y>
<width>113</width>
<height>32</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>22</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<customwidgets>
<customwidget>
<class>MyCustomWidget</class>
<extends>QPushButton</extends>
<header>MyCustomClass</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
I solved it using the above .ui file like this:
class MyCustomClasses(object):
class MyCustomWidget(QtWidgets.QPushButton):
def __init__(self, *args):
QtWidgets.QPushButton.__init__(self, *args)
...
sys.modules['MyCustomClasses'] = MyCustomClasses
uic.loadUi('my_ui.ui', self) # Loads .ui file which contains MyCustomWidget
To quote from the documentation you linked to, the third argument of loadUi
is:
the optional package that is the base package for any relative imports of custom widgets [emphasis added]
The actual module name that the custom class will be imported from must be specified in the ui
file itself. In Qt Designer, this is achieved by setting the "Header file" to the appropriate value, and it will be stored in the <header>
tags inside the ui
file. Note that this value can be the fully qualified package path of the module (e.g. "pkg.mymodule") - in which case, it would not be necessary to use the third argument of loadUi
. There should never be any need for sys.module
hacks.
The loadUi
function is quite simple. It just generates a python module in exactly the same way that the command-line tool does, and then loads it using exec
.