【问题标题】:PyQtGraph PlotWidget crashes application on closing windowPyQtGraph PlotWidget 在关闭窗口时崩溃应用程序
【发布时间】:2019-07-27 09:50:03
【问题描述】:

我正在尝试构建一个桌面应用程序,该应用程序将在一些流数据到达时绘制图表。我希望能够打开多个窗口来监控不同的流。

在我开始关闭图表窗口之前,它工作得非常好。

我遇到的问题是,关闭一个图表窗口经常会导致所有窗口关闭并且应用程序退出。终端中没有出现错误消息,所有窗口都简单地关闭并且程序终止。关闭图表窗口应该只关闭图表窗口。

我在 Windows 10 上使用 PyQt5、PyQtGraph 0.10.0 和 Python 3.6.1。

下面的代码显示了我的应用的结构。

  • App 类跟踪打开的窗口并存储图表数据。 (实际上,该类收集流数据并为相关打开的窗口调用更新例程。)
  • ButtonWindow 类可用于制作新的图表窗口。 (实际上,有很多按钮取决于用户想要绘制的图表。)
  • ChartWindow 类显示数据。

    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. parent for widgets - nescessary?

这个问题肯定与 PlotWidget 有关。如果将绘图小部件换成按钮,则不会观察到崩溃。

我想到在 1 的 closeEvent 覆盖中添加“cls.chart.close()”行。这有帮助。崩溃变得不那么频繁了,但在图表窗口关闭 20 个左右之后,它仍然会发生。

我还尝试将所有小部件设为它们所在窗口的子级,但这没有任何效果。

有什么想法吗?我不敢相信像打开和关闭一个包含绘图的窗口这样简单的事情就足以炸毁 PyQt,所以我认为我在我的结构中做了一些愚蠢的事情。

【问题讨论】:

  • 你为什么要覆盖 closeEvent ?,如果我消除了覆盖,我没有观察到你指出的问题:删除:def closeEvent(cls,event): #cls.chart.close() cls.app.close_window(cls.window_id)
  • 我覆盖关闭事件的原因是我可以删除对存储在应用程序中的打开窗口的引用。 (当真正的应用程序接收到数据时,它会调用相关窗口的更新例程,因此应用程序需要知道哪些窗口是打开的。)覆盖的目的是在窗口关闭时删除对窗口的引用。删除覆盖可以消除直接的问题,所以我必须在 closeEvent 或 app.close_window 中遗漏一些重要的东西。谢谢。
  • 我想我理解您,您将 windows 引用存储在 cls.windows 中,所以如果窗口关闭,您希望删除该列表中的引用,对吗?
  • 没错,是的。 closeEvent 调用 app.close_window 从 app.windows 字典中删除引用,然后销毁窗口。

标签: python pyqt pyqt5 pyqtgraph


【解决方案1】:

不是从 closeEvent 调用一个 close_window 来消除自身,换句话说,您试图消除同一窗口内的窗口,这就是问题所在,在 Qt 中您必须使用信号来通知更改,在此情况下,实现携带已关闭索引的关闭信号。

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 Qt 的优点之一是允许解耦类的信号和槽,更多信息请阅读doc.qt.io/qt-5/signalsandslots.html
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-06
  • 1970-01-01
  • 1970-01-01
  • 2021-11-24
  • 1970-01-01
相关资源
最近更新 更多