Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PyQtGraph PlotWidget crashes application on closing window

I'm attempting to build a desktop app which will chart some streaming data as and when it arrives. I want to be able to open multiple windows to monitor different streams.

It works quite nicely until I start closing chart windows.

The problem I have is that closing one of my chart windows frequently causes all windows to close and the application to exit. No error messages appear in the terminal, all of the windows simply close and the program terminates. Closing a chart window should only close the chart window.

I'm using PyQt5, PyQtGraph 0.10.0 and Python 3.6.1 on Windows 10.

The code below shows how my app is structured.

  • 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()
    

After reading around I think this is due to some conflict between the Python garbage collector and surviving references to the underlying c++ objects.

  1. https://github.com/pyqtgraph/pyqtgraph/issues/55
  2. parent for widgets - nescessary?

The issue is definitely related to the PlotWidget. No crashes are observed if the plot widget is swapped for a button.

I got the idea to add the 'cls.chart.close()' line in the closeEvent override from 1. This helps. Crashing becomes less frequent, but after 20 or so chart window closes, it will still occur.

I've also tried making all widgets children of the window in which they reside, but this had no effect.

Any ideas? I can't believe something as simple as opening and closing a window containing a plot is enough to blow up PyQt so I assume I'm doing something stupid in my structure.

like image 339
rho Avatar asked Dec 09 '25 19:12

rho


1 Answers

Instead of calling from closeEvent a close_window that eliminates itself, in other words you are trying to eliminate the window within the same window and that is the problem, in Qt you have to use the signals to notify the change, in In this case, implement the closed signal that carries the index that was 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()
like image 159
eyllanesc Avatar answered Dec 11 '25 09:12

eyllanesc