PyQtGraph PlotWidget在关闭窗口时导致应用程序崩溃

3
我正在尝试构建一个桌面应用程序,可以在流数据到达时绘制一些图表。我希望能够打开多个窗口来监视不同的流。
它工作得很好,直到我开始关闭图表窗口。
我的问题是关闭其中一个图表窗口经常会导致所有窗口关闭并退出应用程序。终端中没有出现错误消息,所有窗口都简单地关闭并终止程序。关闭图表窗口应该只关闭图表窗口。
我在Windows 10上使用PyQt5、PyQtGraph 0.10.0和Python 3.6.1。
下面的代码显示了我的应用程序结构。
  • The App class keeps track of the open windows and stores chart data. (In reality this class collects the streaming data and calls an update routine for the relevant open windows.)
  • The ButtonWindow class can be used to make new chart windows. (In reality, there are many buttons depending on what the user wishes to chart.)
  • The ChartWindow class displays the data.

    import PyQt5.QtWidgets as qt
    import pyqtgraph as pg
    
    class App(qt.QApplication):
        def __init__(self,args):
            qt.QApplication.__init__(self,args)
    
            #window tracking
            self.last_idx = 0
            self.windows = {}
    
            #chart data
            self.x = [1,2,3,4,5]
            self.y = [1,2,3,4,5]
    
            #create button window
            self.button_window = ButtonWindow(self)
    
            #enter event loop
            self.exec_()
    
        def new_window(cls):
            cls.windows[cls.last_idx] = ChartWindow(cls, cls.last_idx)
            cls.last_idx += 1
    
        def close_window(cls, window_id):
            cls.windows[window_id].destroy()
            del cls.windows[window_id]
    
    class ButtonWindow(qt.QWidget):
        def __init__(self, app):
            qt.QWidget.__init__(self)
            self.grid = qt.QGridLayout()
            self.app = app
    
            #add a button
            self.btn = qt.QPushButton('+1 Chart Window')
            self.btn.clicked.connect(self.app.new_window)
            self.grid.addWidget(self.btn,0,0)
            self.setLayout(self.grid)
    
            #show window
            self.show()
    
    class ChartWindow(qt.QWidget):
        def __init__(self, app, window_id):
            qt.QWidget.__init__(self)
            self.grid = qt.QGridLayout()
            self.app = app
            self.window_id = window_id
            self.setWindowTitle('Chart Window '+str(self.window_id))
    
            #add a chart
            self.chart = pg.PlotWidget()
            self.chart.plot(app.x,app.y)
            self.grid.addWidget(self.chart,0,0)
            self.setLayout(self.grid)
    
            #show window
            self.show()
    
        def closeEvent(cls,event):
            #cls.chart.close()
            cls.app.close_window(cls.window_id)
    
    def main():
        app = App([])
    if __name__ == '__main__':
        main()
    
阅读后,我认为这是Python垃圾回收器和底层C++对象的生存引用之间的冲突导致的。
1. https://github.com/pyqtgraph/pyqtgraph/issues/55 2. 小部件的父级 - 必要吗? 问题明显与PlotWidget有关。如果将绘图小部件替换为按钮,则不会观察到任何崩溃。
我从1中得到了添加“cls.chart.close()”行来覆盖closeEvent的想法。这有所帮助。崩溃变得不那么频繁,但在关闭约20个图表窗口后,仍然会发生崩溃。
我还尝试使所有小部件成为它们所驻留的窗口的子级,但这没有效果。
有什么想法吗?我无法相信打开和关闭包含绘图的窗口就足以使PyQt崩溃,因此我认为我在结构上做了一些愚蠢的事情。

为什么要覆盖closeEvent()函数?如果我删除该覆盖,我就不会观察到你所指出的问题:删除如下代码: def closeEvent(cls,event): #cls.chart.close() cls.app.close_window(cls.window_id) - eyllanesc
我覆盖关闭事件的原因是为了删除存储在应用程序中的打开窗口的引用。当真正的应用程序接收到数据时,它会调用相关窗口的更新例程,因此应用程序需要知道哪些窗口是打开的。覆盖旨在在关闭窗口时删除对窗口的引用。尽管删除覆盖可以解决直接问题,但我必须在closeEvent或app.close_window中遗漏了一些重要内容。谢谢。 - rho
我想我明白你的意思,你正在将窗口引用存储在cls.windows中,因此如果窗口关闭,你希望该列表中的引用被删除,是吗? - eyllanesc
没错。closeEvent 调用 app.close_window,该函数从 app.windows 字典中删除引用,然后销毁窗口。 - rho
1个回答

2

不要在closeEvent中调用一个消除自身的close_window,换句话说,你正在试图在同一个窗口中消除窗口,这是问题所在,在Qt中,你必须使用信号来通知变化,在这种情况下,实现携带被关闭的索引的closed信号。


from PyQt5 import QtCore, QtWidgets
import pyqtgraph as pg

class App(QtWidgets.QApplication):
    def __init__(self, args):
        super(App, self).__init__(args)
        #window tracking
        self.last_idx = 0
        self.windows = {}

        #chart data
        self.x = [1,2,3,4,5]
        self.y = [1,2,3,4,5]

        #create button window
        self.button_window = ButtonWindow()

        #enter event loop
        self.exec_()

    @QtCore.pyqtSlot()
    def new_window(self):
        window = ChartWindow(self, self.last_idx)
        window.closed.connect(self.remove_window)
        self.windows[self.last_idx] = window
        self.last_idx += 1

    @QtCore.pyqtSlot(int)
    def remove_window(self, idx):
        w = self.windows[idx]
        w.deleteLater()
        del self.windows[idx]
        print(self.windows)

class ButtonWindow(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(ButtonWindow, self).__init__(parent)
        grid = QtWidgets.QGridLayout(self)
        self.btn = QtWidgets.QPushButton('+1 Chart Window')
        self.btn.clicked.connect(QtWidgets.QApplication.instance().new_window)
        grid.addWidget(self.btn, 0, 0)
        self.show()

class ChartWindow(QtWidgets.QWidget):
    closed = QtCore.pyqtSignal(int)

    def __init__(self, app, window_id):
        super(ChartWindow, self).__init__()
        grid = QtWidgets.QGridLayout(self)
        self.window_id = window_id
        self.setWindowTitle('Chart Window '+str(self.window_id))
        #add a chart
        self.chart = pg.PlotWidget()
        self.chart.plot(app.x,app.y)
        grid.addWidget(self.chart,0,0)
        #show window
        self.show()

    def closeEvent(self, event):
        self.closed.emit(self.window_id)
        super(ChartWindow, self).closeEvent(event)

def main():
    app = App([])
if __name__ == '__main__':
    main()

好的,太棒了。它完美地工作了,谢谢你。 我对Qt和槽口和信号一窍不通,所以需要做一些功课! - rho
@rho Qt的一个优点是信号和槽机制,它允许解耦类之间的关系。欲了解更多信息,请阅读https://doc.qt.io/qt-5/signalsandslots.html。 - eyllanesc

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接