如何在不导入自定义小部件 class 包的情况下使用自定义小部件和 uic.loadUi?
How to use custom widget and uic.loadUi without importing custom widget class package?
使用 PyQt4 的 uic.loadUi,我想加载一个 .ui 文件并在其中使用自定义小部件。这意味着使用 uic.loadUi
的第三个 package
参数,它将导入包含自定义小部件 class 的包。
但是,我希望在调用 uic.loadUi
的同一文件中定义自定义小部件的 class。我正在努力实现这一目标:
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
但是,这个returns出现如下错误:
AttributeError: type object 'MyCustomClass' has no attribute 'MyCustomWidget'
我能做些什么来让它真正起作用吗? 我怀疑 MyCustomClass
没有按照 uic.loadUi
期望的方式定义。
在 Qt Designer 中,我提升了 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>
解决方案
我使用上面的 .ui 文件解决了这个问题:
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
引用您链接到的文档,loadUi
的第三个参数是:
the optional package that is the base package for any relative imports of custom widgets [emphasis added]
自定义 class 将从中导入的实际模块名称必须在 ui
文件本身中指定。在 Qt Designer 中,这是通过将 "Header file" 设置为适当的值来实现的,它将存储在 ui
文件内的 <header>
标记中。请注意,此值可以是模块的完全限定包路径(例如 "pkg.mymodule")——在这种情况下,没有必要使用 loadUi
的第三个参数。永远不需要 sys.module
黑客。
loadUi
函数非常简单。它只是以与命令行工具完全相同的方式生成一个 python 模块,然后使用 exec
.
加载它
共有三种可能的方式。
例如,您有模块 QtCustomWidgets.widgets.mybutton
这是一个文件 QtCustomWidgets/widgets/mybutton.py 和 QtCustomWidgets/python/mybuttonplugin.py 在你的项目中 MyButtonclass里面.
第一种方式 将 QtCustomWidgets/python/mybuttonplugin.py 中的 includeFile 方法定义为:
def includeFile(self):
return "QtCustomWidgets.widgets.mybutton"
第二种方法 是使用 uic.loadUi 和 packadge 路径:
uic.loadUi('my_ui.ui', 自我, packadge='QtCustomWidgets.widgets')
但是你必须在模块名称中使用点(includeFile 是 QtCustomWidgets/python/mybuttonplugin.py 中的方法) :
def includeFile(self):
return ".mybutton"
,所以在 header 中它必须是这样的:
<customwidgets>
<customwidget>
<class>MyButton</class>
<extends>QPushButton</extends>
<header>.mybutton</header>
</customwidget>
</customwidgets>
并且结果方式仍然是 "QtCustomWidgets.widgets" + ".mybutton"
这里有源码PyQt自定义widget loader(qobjectcreator.py),你可以自己找:
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)
第三种方式:
要在 sys.path 中添加小部件的路径(您必须是 import sys):
sys.path.append( "./QtCustomWidgets/widgets" )
uic.loadUi('my_ui.ui', self)
使用 PyQt4 的 uic.loadUi,我想加载一个 .ui 文件并在其中使用自定义小部件。这意味着使用 uic.loadUi
的第三个 package
参数,它将导入包含自定义小部件 class 的包。
但是,我希望在调用 uic.loadUi
的同一文件中定义自定义小部件的 class。我正在努力实现这一目标:
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
但是,这个returns出现如下错误:
AttributeError: type object 'MyCustomClass' has no attribute 'MyCustomWidget'
我能做些什么来让它真正起作用吗? 我怀疑 MyCustomClass
没有按照 uic.loadUi
期望的方式定义。
在 Qt Designer 中,我提升了 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>
解决方案
我使用上面的 .ui 文件解决了这个问题:
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
引用您链接到的文档,loadUi
的第三个参数是:
the optional package that is the base package for any relative imports of custom widgets [emphasis added]
自定义 class 将从中导入的实际模块名称必须在 ui
文件本身中指定。在 Qt Designer 中,这是通过将 "Header file" 设置为适当的值来实现的,它将存储在 ui
文件内的 <header>
标记中。请注意,此值可以是模块的完全限定包路径(例如 "pkg.mymodule")——在这种情况下,没有必要使用 loadUi
的第三个参数。永远不需要 sys.module
黑客。
loadUi
函数非常简单。它只是以与命令行工具完全相同的方式生成一个 python 模块,然后使用 exec
.
共有三种可能的方式。 例如,您有模块 QtCustomWidgets.widgets.mybutton 这是一个文件 QtCustomWidgets/widgets/mybutton.py 和 QtCustomWidgets/python/mybuttonplugin.py 在你的项目中 MyButtonclass里面.
第一种方式 将 QtCustomWidgets/python/mybuttonplugin.py 中的 includeFile 方法定义为:
def includeFile(self):
return "QtCustomWidgets.widgets.mybutton"
第二种方法 是使用 uic.loadUi 和 packadge 路径: uic.loadUi('my_ui.ui', 自我, packadge='QtCustomWidgets.widgets')
但是你必须在模块名称中使用点(includeFile 是 QtCustomWidgets/python/mybuttonplugin.py 中的方法) :
def includeFile(self):
return ".mybutton"
,所以在 header 中它必须是这样的:
<customwidgets>
<customwidget>
<class>MyButton</class>
<extends>QPushButton</extends>
<header>.mybutton</header>
</customwidget>
</customwidgets>
并且结果方式仍然是 "QtCustomWidgets.widgets" + ".mybutton"
这里有源码PyQt自定义widget loader(qobjectcreator.py),你可以自己找:
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)
第三种方式: 要在 sys.path 中添加小部件的路径(您必须是 import sys):
sys.path.append( "./QtCustomWidgets/widgets" )
uic.loadUi('my_ui.ui', self)