PyQt / Qt Designer - 无需插件即可升级小部件的额外参数

Fer*_*ndr 5 python pyqt qt-designer

假设我有一个自定义 PyQt 小部件,我想在 Qt Designer 中使用它,例如用于 matplotlib 画布:

情节小部件.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)
Run Code Online (Sandbox Code Playgroud)

在 Qt Designer 中,我可以轻松地为此提升 QWidget,将头文件设置为“my_module/plot_widget.h”并将其名称设置为 PlotWidget。然后,我可以通过动态加载 ui 文件来构造一个小部件,如下所示:

示例.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()
Run Code Online (Sandbox Code Playgroud)

这工作得很好,除了我没有看到修改我拥有的默认初始化参数(在本例中为工具栏、figsize 和 dpi)的直接方法。

我知道可以为此创建自定义插件,但我想避免它,因为我认为这会给我的简单需求增加不必要的复杂性。另外,我希望我的代码尽可能与 PySide / PyQt4 / PyQt5 兼容(为此,我实际上直接使用 QtPy 而不是 PyQt5,尽管这有点题外话),而且我并不完全相信如果我开始使用插件,我就可以实现这一目标。

于是,我想到了以下两种办法:

方法 1:以编程方式调用 setupUi()

基本上,我可以简化我的构造函数,以便在那里不调用 setupUi() ,并在加载 ui 文件后执行该调用:

情节小部件.py:

class PlotWidget(QtWidgets.QWidget):

    def __init__(self, parent=None):
        super(PlotWidget, self).__init__(parent)

    ...
Run Code Online (Sandbox Code Playgroud)

示例.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()
Run Code Online (Sandbox Code Playgroud)

方法 2:使用动态属性

我可以在 Qt Designer 中添加所需的任何动态属性,然后使用它们,而不是向 setupUi() 传递额外的参数,如下所示:

figsize = self.property('figsize')
Run Code Online (Sandbox Code Playgroud)

如果没有定义它们,它们就只是 None。问题在于,在将这些动态属性添加到对象之前调用 init 构造函数(这似乎符合逻辑),因此在这段代码中,figsize 始终为 None:

情节小部件.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)
Run Code Online (Sandbox Code Playgroud)

这意味着我仍然需要在 example.py 文件中显式调用 setupUi() ,除非我能找出其他东西。

结论

我更喜欢方法 2 的想法,因为它允许我直接从 Qt Designer 修改所有 ui 参数。但是,我想避免显式调用 setupUi()。因此,是否有任何 QWidget 方法在将动态属性分配给对象后总是被调用(因此我可以重写它以在那里调用 setupUi() )?

另外,您是否发现这种方法有任何其他缺陷,或者您是否知道实现我想要实现的目标的任何其他替代方法?请注意,我使用了 matplotlib 画布作为示例(可能已经有一些特定的方法),但我想将其用于我可能需要的任何自定义小部件。

mus*_*nte 2

我知道距离您自己的回答已经过去了几个月,也许您已经找到了满足您需求的解决方案,也许我还没有完全理解您的情况。

据我所知,我会在设计器中为小部件设置动态属性,然后重载,QObject.setProperty()或者更好的是pyqtProperty 自定义小部件中使用,然后使用@property.setter来设置布局。唯一的问题是必须正确的顺序声明属性。您可以使用不同的方法来规避。

  1. 使用单个 QStringList 动态属性将所有参数设置为(键,值)元组的假字典。

  2. 正如您提到的,使用 resizeEvent (或 showEvent),设置类标志,以避免多次调用 setupUi:

    class Widget(QtWidgets.QWidget):
        shown = False
        [...]
        def showEvent(self, event):
            if not self.shown:
                self.shown = True
                self.setupUi()
    
    Run Code Online (Sandbox Code Playgroud)
  3. 每当设置它们的属性时,使用 insertWidget 根据它们的位置将每个小部件添加到布局中(这只适用于 QBoxLayout 并且如果您知道小部件将具有固定的布局结构)

或者,您可以为自定义小部件使用另一个 ui,添加所有可能的升级的plotlib 小部件,然后在装饰器中使用 setVisible() @property.setter然后您决定是在init中隐藏它们并仅在设置了它们的属性时才显示它们,还是记住始终设置动态属性。