假设我有一个我想在Qt Designer中使用的自定义PyQt小部件,例如matplotlib画布:
plot_widget.py:
from PyQt5 import QtWidgets
import matplotlib
matplotlib.use('Qt5Agg')
from matplotlib.backends.backend_qt5agg import (
FigureCanvasQTAgg, NavigationToolbar2QT
)
class PlotWidget(QtWidgets.QWidget):
def __init__(self, parent=None, toolbar=True, figsize=None, dpi=100):
super(PlotWidget, self).__init__(parent)
self.setupUi(toolbar, figsize, dpi)
def setupUi(self, toolbar=True, figsize=None, dpi=100):
layout = QtWidgets.QVBoxLayout(self)
self.figure = Figure(figsize=figsize, dpi=dpi)
self.canvas = FigureCanvasQTAgg(self.figure)
if toolbar:
self.toolbar = NavigationToolbar2QT(self.canvas, self)
layout.addWidget(self.toolbar, 0)
layout.addWidget(self.canvas, 1)
在Qt Designer中,我可以轻松地为此推广QWidget,并将头文件设置为" my_module / plot_widget.h"并将其名称设置为PlotWidget。然后,我可以通过动态加载来构建我的ui文件中的小部件,如下所示:
example.py:
from PyQt5 import QtWidgets, uic
class MyWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(MyWidget, self).__init__(parent)
uic.loadUi('example.ui', self)
self.show()
这样做很好,除了我没有看到修改我拥有的默认init参数的简单方法(在这种情况下,工具栏,figsize和dpi)。 / p>
我知道有可能为此创建自定义插件,但我想避免它,因为我认为它会为我的简单需求增加不必要的复杂性。另外,我希望我的代码尽可能与PySide / PyQt4 / PyQt5兼容(为此,我实际上直接使用QtPy代替PyQt5,虽然这有点偏离主题),我并不完全相信如果我开始使用插件,我可以实现这一点。
因此,我想到了以下两种方法:
基本上,我可以简化我的构造函数,以便不在那里调用setupUi(),并在加载ui文件后执行该调用:
plot_widget.py:
class PlotWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(PlotWidget, self).__init__(parent)
...
example.py:
from PyQt5 import QtWidgets, uic
class MyWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(MyWidget, self).__init__(parent)
uic.loadUi('example.ui', self)
self.myPlotWidget.setupUi(figsize=(4, 4), dpi=200)
self.show()
我可以在Qt Designer中添加我需要的任何动态属性,而不是将额外的参数传递给setupUi(),然后使用它们:
figsize = self.property('figsize')
如果没有定义,它们将只是无。问题是在将这些动态属性添加到对象(似乎是逻辑)之前调用init构造函数,因此在此代码中figsize将始终为None:
plot_widget.py:
class PlotWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(PlotWidget, self).__init__(parent)
def setupUi(self):
layout = QtWidgets.QVBoxLayout(self)
toolbar = self.property('toolbar') # Always None
figsize = self.property('figsize') # Always None
dpi = self.property('dpi') # Always None
self.figure = Figure(figsize=figsize, dpi=dpi)
self.canvas = FigureCanvasQTAgg(self.figure)
if toolbar:
self.toolbar = NavigationToolbar2QT(self.canvas, self)
layout.addWidget(self.toolbar, 0)
layout.addWidget(self.canvas, 1)
这意味着我仍然需要在我的example.py文件中显式调用setupUi(),除非我能找到其他内容。
我更喜欢方法2的想法,因为它允许我直接从Qt Designer修改所有ui参数。但是,我想避免显式调用setupUi()。因此,是否有任何QWidget方法在动态属性被分配给对象后总是被调用(因此我可以覆盖它来调用setupUi())?
另外,您是否看到此方法存在任何其他缺陷,或者您是否知道实现我想要达到的目标的其他任何替代方法?请注意,我使用matplotlib画布作为示例(可能已经有一些特定的方法),但我想将此用于我可能需要的任何自定义小部件。
答案 0 :(得分:1)
我知道自您回答以来已经过去了几个月,也许您已经找到了满足您需求的解决方案,也许我还没有完全了解您的情况。
据我了解,我将在Designer中为窗口小部件设置动态属性,然后重载QObject.setProperty()
,或者最好在自定义窗口小部件中使用pyqtProperty
,然后使用@property.setter
来设置布局。
唯一的问题是属性必须以正确的顺序声明。您可以使用不同的方法来规避。
使用单个QStringList动态属性将所有参数设置为(键,值)元组的伪字典。
使用您提到的resizeEvent(或showEvent),设置一个类标志,以避免多次调用setupUi:
class Widget(QtWidgets.QWidget):
shown = False
[...]
def showEvent(self, event):
if not self.shown:
self.shown = True
self.setupUi()
每当设置其属性时,使用insertWidget根据其位置将每个窗口小部件添加到布局中(这仅适用于QBoxLayout,并且如果您知道窗口小部件将具有固定的布局结构)
或者,您可以仅对自定义窗口小部件使用另一个ui,添加所有可能的提升后的plotlib小窗口,然后在@property.setter
装饰器中使用setVisible();那么您决定将其隐藏在 init 上,并仅在设置了它们的属性时才显示它们,或者记住始终设置动态属性。
答案 1 :(得分:0)
I found out that I may be able to use resizeEvent for this, but I don't know if it will work in any situation (i.e. whether the ui loader will always trigger the resizeEvent under all circumstances):
class PlotWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(PlotWidget, self).__init__(parent)
self._alreadySetup = False
def resizeEvent(self, event):
# This call can actually be after the super() call and still work
self.setupUi()
super(PlotWidget, self).resizeEvent(event)
def setupUi(self):
if self._alreadySetup:
return
self._alreadySetup = True
layout = QtWidgets.QVBoxLayout(self)
...
Hence, unless I detect issues with this approach or I can figure out something more clever (or somebody else can), I will consider this the answer to my question.
Edit: Just be aware that, if the widget is never visible, the resizeEvent will never be called. This is normally not a problem, but on certain occasions (such as during unit tests or doing heavy initialization before showing a widget) it may happen.
Lazy initialization can also be used for this as follows:
class PlotWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(PlotWidget, self).__init__(parent)
self._figure = None
self._canvas = None
self._toolbar = None
self._initialized = False
@property
def figure(self):
self.setupUi()
return self._figure
@property
def canvas(self):
self.setupUi()
return self._canvas
@property
def toolbar(self):
self.setupUi()
return self._toolbar
def setupUi(self):
if self._initialized:
return
self._initialized = True
layout = QtWidgets.QVBoxLayout(self)
toolbar = self.property('toolbar')
figsize = self.property('figsize')
dpi = self.property('dpi')
self._figure = Figure(figsize=figsize, dpi=dpi)
self._canvas = FigureCanvasQTAgg(self._figure)
if toolbar:
self._toolbar = NavigationToolbar2QT(self._canvas, self)
layout.addWidget(self._toolbar, 0)
layout.addWidget(self._canvas, 1)
The uic module does not have any reason to access these properties while loading the .ui file, and hence only the programmer can do so at his convenience. Nevertheless, this can also have a few inconveniences, such as: