PyQtGraph PlotWidget завершает работу приложения при закрытии окна - PullRequest
3 голосов
/ 05 марта 2019

Я пытаюсь создать настольное приложение, которое будет отображать некоторые потоковые данные по мере их поступления. Я хочу иметь возможность открывать несколько окон для мониторинга разных потоков.

Это работает довольно хорошо, пока я не начну закрывать окна графиков.

Проблема, с которой я столкнулся, заключается в том, что закрытие одного из окон моего графика часто приводит к закрытию всех окон и закрытию приложения. В терминале не появляются сообщения об ошибках, все окна просто закрываются и программа завершается. Закрытие окна графика должно закрывать только окно графика.

Я использую PyQt5, PyQtGraph 0.10.0 и Python 3.6.1 в Windows 10.

Приведенный ниже код показывает структуру моего приложения.

  • Класс 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. родитель для виджетов - необязательно?

Эта проблема определенно связана с PlotWidget. Сбоев не наблюдается, если виджет графика заменен на кнопку.

Мне пришла в голову идея добавить строку 'cls.chart.close ()' в переопределении closeEvent из 1 . Это помогает. Сбои становятся менее частыми, но через 20 или около того окно графика закрывается, но все равно происходит.

Я также пытался сделать все виджеты дочерними по отношению к окну, в котором они находятся, но это не имело никакого эффекта.

Есть идеи? Я не могу поверить, что что-то настолько простое, что открытия и закрытия окна, содержащего график, достаточно, чтобы взорвать PyQt, поэтому я предполагаю, что я делаю что-то глупое в своей структуре.

1 Ответ

1 голос
/ 06 марта 2019

Вместо того, чтобы вызывать из 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()
...