PyQt / Qt Designer - Extra parameters for promoted widget without a plugin
假设我有一个要在Qt Designer中使用的自定义PyQt小部件,例如用于matplotlib画布:
plot_widget.py:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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设计器中,我可以轻松地为此升级QWidget,将头文件设置为" my_module / plot_widget.h",并将其名称设置为PlotWidget。然后,我可以通过如下方式动态加载ui文件,从ui文件中构建一个小部件:
example.py:
1 2 3 4 5 6 7 8 | 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() |
除我看不到修改我具有的默认初始化参数(在这种情况下为工具栏,figsize和dpi)的简单方法外,此方法工作正常。
我知道可以为此创建自定义插件,但是我想避免这样做,因为我认为这会给我的简单需求增加不必要的复杂性。另外,我希望我的代码尽可能与PySide / PyQt4 / PyQt5兼容(为此,尽管实际上这有点偏离主题,但我实际上是直接使用QtPy而不是PyQt5),我并不完全相信如果我开始使用插件,我可以实现这一目标。
因此,我想到了以下两种方法:
方法1:以编程方式调用setupUi()
基本上,我可以简化构造函数,以便在那里不调用setupUi(),并在加载ui文件后执行该调用:
plot_widget.py:
1 2 3 4 5 6 | class PlotWidget(QtWidgets.QWidget): def __init__(self, parent=None): super(PlotWidget, self).__init__(parent) ... |
example.py:
1 2 3 4 5 6 7 8 9 | 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() |
方法2:使用动态属性
无需将额外的参数传递给setupUi(),我可以在Qt设计器中添加所需的任何动态属性,然后像这样使用它们:
1 | figsize = self.property('figsize') |
如果未定义,则它们将为"无"。问题在于,在将那些动态属性添加到对象之前,会调用init构造函数(这似乎是逻辑上的),因此在此代码中,figsize始终为None:
plot_widget.py:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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设计器修改所有ui参数。但是,我想避免对setupUi()的显式调用。因此,在将动态属性分配给对象之后,是否有任何QWidget方法总是被调用(因此我可以重写此方法以在那里调用setupUi())?
另外,您是否看到此方法有任何其他缺陷,或者您知道实现我所要实现的目标的任何其他替代方法?请注意,我以matplotlib画布为例(可能已经有一些特定的方法),但是我想将其用于我可能需要的任何自定义小部件。
我知道自您回答以来已经过去了几个月,也许您已经找到了可以满足您需求的解决方案,也许我还没有完全了解您的情况。
据我所知,我将在Designer中为窗口小部件设置动态属性,然后重载
唯一的问题是必须按正确的顺序声明属性。您可以使用不同的方法来规避。
使用单个QStringList动态属性将所有参数设置为(键,值)元组的伪字典。
如前所述,使用resizeEvent(或showEvent)来设置类标志,以避免多次调用setupUi:
1 2 3 4 5 6 7 | class Widget(QtWidgets.QWidget): shown = False [...] def showEvent(self, event): if not self.shown: self.shown = True self.setupUi() |
只要设置了属性,就可以使用insertWidget根据其位置将每个小部件添加到布局中(这仅适用于QBoxLayout,并且如果您知道这些小部件将具有固定的布局结构)
或者,您可以仅对自定义窗口小部件使用另一个ui,添加所有可能的提升的plotlib小窗口,然后在
原始答案:
我发现我可以为此使用resizeEvent,但是我不知道它在任何情况下是否都可以工作(即ui加载程序是否在所有情况下都将始终触发resizeEvent):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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) ... |
因此,除非我发现这种方法存在问题,或者我无法弄清楚(或者其他人可以),否则我将认为这是我问题的答案。
编辑:请注意,如果窗口小部件从不可见,则不会调用resizeEvent。通常这不是问题,但是在某些情况下(例如在单元测试期间或在显示小部件之前进行大量初始化)可能会发生。
替代方法:
延迟初始化也可以用于此,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | 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) |
uic模块在加载.ui文件时没有任何理由访问这些属性,因此只有程序员才能方便地这样做。但是,这也会带来一些不便,例如:
- 您将需要在某些时候访问至少一个属性以使内容可用。例如,在用户按下按钮之前,可能不会绘制该图,直到它看起来像一个空的小部件为止
- 除非使用一些巧妙的描述符,否则您可能需要为每个使用的惰性属性生成一个Python属性,这最终可能会很冗长