【问题标题】:What is the better way for two-way communication between objects?对象之间双向通信的更好方法是什么?
【发布时间】:2020-11-12 11:07:30
【问题描述】:

我现在正在编写一个 GUI 程序,我有两个需要相互通信的独立 PyQt5 小部件对象。我现在有一些可以工作的东西(我在下面提供了一个简化的示例),但我怀疑有一种更强大的方法可以做到这一点,我希望了解它。我将总结下面的功能,以供那些想要对代码进行一些介绍的人使用。

TL;DR:请帮助我找到一种更好的方法来使用对象 1 中的按钮单击来更改对象 2 中的变量,该变量将对象 2 中的鼠标单击坐标发送到对象 1,这些坐标填充两个旋转框.

第一个 MainWindow 类是定义小部件对象的地方。感兴趣的两个对象是 MainWindow.plotWidget(MplFig 类的一个实例)和 MainWindow.linePt1(LineEndpoint 类的一个实例)。请注意,我可以将 self.plotWidget 作为参数传递给 LineEndpoint 对象,但由于首先定义了 MainWindow.plotWidget,因此我不能将 self.linePt1 作为参数传递。

我使用这些小部件实现的功能是 LineEndpoint (LineEndpoint.chooseBtn) 中的一个按钮,单击该按钮时,将 MplFig (MplFig.waitingForPt) 中的变量从 None 更改为 ptNum 的值,该值作为参数传递LineEndpoint(在 linePt1 的情况下,此值为 1)。 MplFig 具有与方法 MplFig.onClick() 相关联的按钮按下事件,即 MplFig.onClick 不是 None,将鼠标单击的坐标传递给 LineEndpoint.ptXSpin 和 LineEndpoint.ptYSpin 中的两个 QDoubleSpinBox 对象。为了实现这一点,我在创建 MplFig 的 MainWINdow.plotWidget 对象时将 self 作为父参数传递。我将父级设置为 self.parent,这允许我将 LineEndpoint 对象称为 self.parent.linePt1,从那里我可以访问旋转框。

这似乎是一种迂回的做事方式,我想知道是否有人可以建议一种更好的方式来构建此功能?我喜欢将 MplFig 对象作为参数传递给 LineEndpoint 类的方法,因为从类定义中的 init 方法可以清楚地看出 LineEndpoint 类与 MplFig 类进行通信。我知道我不能让两个类以相同的方式相互依赖,但我很想学习一种这样做的方法,它仍然可以在代码中清楚地表明对象正在通信。不过,我仍然愿意接受所有建议!

from PyQt5.QtWidgets import (
    QMainWindow, QApplication, QLabel, QLineEdit, QPushButton, QFileDialog, 
    QWidget, QHBoxLayout, QVBoxLayout, QMessageBox, QListWidget, 
    QAbstractItemView, QDoubleSpinBox
)
from PyQt5.QtCore import Qt
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import (
    FigureCanvasQTAgg, NavigationToolbar2QT as NavigationToolbar
)
import sys  # need sys to pass argv to QApplication


class MplFig(FigureCanvasQTAgg):

    def __init__(self, parent):
        self.fig = Figure()
        super().__init__(self.fig)
        self.parent = parent
        self.waitingForPt = None
        self.fig.canvas.mpl_connect('button_press_event', self.onClick)
        self.ax = self.figure.add_subplot(111)

    def onClick(self, e):
        if self.waitingForPt is not None:
            if self.waitingForPt == 1:
                lineObj = self.parent.linePt1
               
            roundX = round(e.xdata, lineObj.ptPrec)
            roundY = round(e.ydata, lineObj.ptPrec)
            print(f'x{self.waitingForPt}: {roundX}, '
                f'y{self.waitingForPt}: {roundY}'
            )
            lineObj.ptXSpin.setValue(roundX)
            lineObj.ptYSpin.setValue(roundY)
            lineObj.chooseBtn.setStyleSheet(
                'background-color: light gray'
            )
            self.waitingForPt = None

class LineEndpoint(QWidget):

    def __init__(self, parent, mplObject, ptNum, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.parent = parent
        self.mpl = mplObject
        self.layout = QVBoxLayout()

        row0Layout = QHBoxLayout()
        ptXLabel = QLabel(f'X{ptNum}:')
        row0Layout.addWidget(ptXLabel)
        ptMin = 0
        ptMax = 1000
        ptStep = 1
        self.ptPrec = 2
        self.ptXSpin = QDoubleSpinBox()
        self.ptXSpin.setSingleStep(ptStep)
        self.ptXSpin.setMinimum(ptMin)
        self.ptXSpin.setMaximum(ptMax)
        self.ptXSpin.setDecimals(self.ptPrec)
        row0Layout.addWidget(self.ptXSpin)
        ptYLabel = QLabel(f'Y{ptNum}:')
        row0Layout.addWidget(ptYLabel)
        self.ptYSpin = QDoubleSpinBox()
        self.ptYSpin.setMinimum(ptMin)
        self.ptYSpin.setMaximum(ptMax)
        self.ptYSpin.setSingleStep(ptStep)
        self.ptYSpin.setDecimals(self.ptPrec)
        row0Layout.addWidget(self.ptYSpin)
        self.layout.addLayout(row0Layout)

        row1Layout = QHBoxLayout()
        self.chooseBtn = QPushButton('Choose on Plot')
        self.chooseBtn.clicked.connect(lambda: self.chooseBtnClicked(ptNum))
        row1Layout.addWidget(self.chooseBtn)
        self.layout.addLayout(row1Layout)


    def chooseBtnClicked(self, endpointNum):
        print(f'Choosing point {endpointNum}...')
        self.chooseBtn.setStyleSheet('background-color: red')
        self.mpl.waitingForPt = endpointNum

class MainWindow(QMainWindow):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setLayouts()

    def setLayouts(self):
        self.sideBySideLayout = QHBoxLayout()

        self.plotWidget = MplFig(self)
        self.sideBySideLayout.addWidget(self.plotWidget)

        self.linePt1 = LineEndpoint(self, self.plotWidget, 1)
        self.sideBySideLayout.addLayout(self.linePt1.layout)

        mainContainer = QWidget()
        mainContainer.setLayout(self.sideBySideLayout)
        self.setCentralWidget(mainContainer)

QApp = QApplication(sys.argv)
win = MainWindow()
win.show()

sys.exit(QApp.exec_())

【问题讨论】:

    标签: python matplotlib pyqt pyqt5


    【解决方案1】:

    如果你想在对象之间传递信息(记住类只是抽象)那么你必须使用信号:

    import sys
    
    from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
    from PyQt5.QtWidgets import (
        QApplication,
        QDoubleSpinBox,
        QGridLayout,
        QHBoxLayout,
        QLabel,
        QMainWindow,
        QPushButton,
        QWidget,
    )
    
    from matplotlib.figure import Figure
    from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
    
    
    class MplFig(FigureCanvasQTAgg):
        clicked = pyqtSignal(float, float)
    
        def __init__(self, parent=None):
            super().__init__(Figure())
            self.setParent(parent)
            self.figure.canvas.mpl_connect("button_press_event", self.onClick)
            self.ax = self.figure.add_subplot(111)
    
        def onClick(self, e):
            self.clicked.emit(e.xdata, e.ydata)
    
    
    class LineEndpoint(QWidget):
        def __init__(self, ptNum, parent=None):
            super().__init__(parent)
            ptMin = 0
            ptMax = 1000
            ptStep = 1
            ptPrec = 2
    
            self.ptXSpin = QDoubleSpinBox(
                singleStep=ptStep, minimum=ptMin, maximum=ptMax, decimals=ptPrec
            )
            self.ptYSpin = QDoubleSpinBox(
                singleStep=ptStep, minimum=ptMin, maximum=ptMax, decimals=ptPrec
            )
            self.chooseBtn = QPushButton("Choose on Plot", checkable=True)
            self.chooseBtn.setStyleSheet(
                """
                QPushButton{
                    background-color: light gray
                } 
                QPushButton:checked{
                    background-color: red
                }"""
            )
    
            lay = QGridLayout(self)
            lay.addWidget(QLabel(f"X{ptNum}"), 0, 0)
            lay.addWidget(self.ptXSpin, 0, 1)
            lay.addWidget(QLabel(f"Y{ptNum}"), 0, 2)
            lay.addWidget(self.ptYSpin, 0, 3)
            lay.addWidget(self.chooseBtn, 1, 0, 1, 4)
            lay.setRowStretch(lay.rowCount(), 1)
    
        @pyqtSlot(float, float)
        def update_point(self, x, y):
            if self.chooseBtn.isChecked():
                self.ptXSpin.setValue(x)
                self.ptYSpin.setValue(y)
                self.chooseBtn.setChecked(False)
    
    
    class MainWindow(QMainWindow):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            self.setLayouts()
    
        def setLayouts(self):
            self.plotWidget = MplFig()
            self.linePt1 = LineEndpoint(1)
    
            self.plotWidget.clicked.connect(self.linePt1.update_point)
    
            mainContainer = QWidget()
            lay = QHBoxLayout(mainContainer)
            lay.addWidget(self.plotWidget)
            lay.addWidget(self.linePt1)
    
            self.setCentralWidget(mainContainer)
    
    
    QApp = QApplication(sys.argv)
    win = MainWindow()
    win.show()
    
    sys.exit(QApp.exec_())
    

    【讨论】:

    • 很好的答案,谢谢!我特别感谢您花时间重新格式化我的代码、整合 QDoubleSpinBox 参数和调整我的布局(setCentralWidget 直到现在都令人困惑)。在学习过程中获得这种反馈意义重大。我阅读了装饰器,我相信我了解发生了什么。似乎@pyqtSlot(float, float) 用于捕获来自 MplFig.clicked.emit(e.xdata, e.ydata) 的值并将它们传递给 LineEndpoint.update_point,这是正确的吗?你能评论一下为什么“浮动”参数是必要的吗?我不清楚这些文档。
    猜你喜欢
    • 2012-07-27
    • 1970-01-01
    • 2023-03-06
    • 1970-01-01
    • 2011-09-17
    • 1970-01-01
    • 2017-03-23
    • 1970-01-01
    • 2012-05-14
    相关资源
    最近更新 更多