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 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.
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.
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()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With