Reputation: 10291
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
Upvotes: 3
Views: 3158
Reputation: 194
The are three possible ways. For example, you have the module QtCustomWidgets.widgets.mybutton It is a files QtCustomWidgets/widgets/mybutton.py and QtCustomWidgets/python/mybuttonplugin.py in you project with MyButton class in it.
First way define includeFile method from QtCustomWidgets/python/mybuttonplugin.py as:
def includeFile(self):
return "QtCustomWidgets.widgets.mybutton"
Second way is to use uic.loadUi with packadge path: uic.loadUi('my_ui.ui', self, packadge='QtCustomWidgets.widgets')
but you have to use the dot in you module names (includeFile is the method from QtCustomWidgets/python/mybuttonplugin.py ):
def includeFile(self):
return ".mybutton"
, so in the header it must look like this:
<customwidgets>
<customwidget>
<class>MyButton</class>
<extends>QPushButton</extends>
<header>.mybutton</header>
</customwidget>
</customwidgets>
And result way still will be "QtCustomWidgets.widgets" + ".mybutton"
There is the source PyQt code custom widget loader (qobjectcreator.py), you may find it yourself:
class _CustomWidgetLoader(object):
def __init__(self, package):
# should it stay this way?
if '.' not in sys.path:
sys.path.append('.')
self._widgets = {}
self._modules = {}
self._package = package
def addCustomWidget(self, widgetClass, baseClass, module):
assert widgetClass not in self._widgets
self._widgets[widgetClass] = module
def search(self, cls):
module_name = self._widgets.get(cls)
if module_name is None:
return None
module = self._modules.get(module_name)
if module is None:
if module_name.startswith('.'):
if self._package == '':
raise ImportError(
"relative import of %s without base package specified" % module_name)
if self._package.startswith('.'):
raise ImportError(
"base package %s is relative" % self._package)
mname = self._package + module_name
else:
mname = module_name
try:
module = __import__(mname, {}, {}, (cls,))
except ValueError:
# Raise a more helpful exception.
raise ImportError("unable to import module %s" % mname)
self._modules[module_name] = module
return getattr(module, cls)
Third way: To add path to your widgets in sys.path (you have to be import sys):
sys.path.append( "./QtCustomWidgets/widgets" )
uic.loadUi('my_ui.ui', self)
Upvotes: 1
Reputation: 120768
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
.
Upvotes: 2