关闭应用程序时,错误"QObject :: startTimer:QTimer只能与QThread启动的线程一起使用"很多次

Joh*_*y J 5 python qt pyqt pyqtgraph

我知道以前曾多次询问过这个问题.我阅读了所有这些主题,我的情况似乎有所不同.遇到这种麻烦的每个人都有一些直截了当的原因,我认为我已经排除了这些原因,例如:

  • 启动没有事件循环运行的计时器
  • 从创建计时器之外的线程启动/停止计时器
  • 未能设置窗口小部件的父属性,导致销毁顺序出现问题

下面我有一个演示问题的最小代码示例.请注意,我没有启动任何线程或计时器.我还设置了每个小部件的父级.如果我删除了图形窗口小部件,问题就会消失,所以人们很想责怪pyQtGraph,但是,如果我包含了绘图窗口小部件但排除了所有空白选项卡(即除了tabCatchaTiger之外的每个选项卡),问题也会消失,那就是似乎证明了pyQtGraph.

版本:

  • Windows 7的
  • Python 2.7.8
  • Wing IDE 5.0.9-1
  • PyQt 4.11.1
  • PyQwt 5.2.1
  • PyQtGraph 0.9.8

测试用例:

from PyQt4 import Qt, QtGui, QtCore
import PyQt4.Qwt5 as Qwt
import pyqtgraph as pg

pg.functions.USE_WEAVE = False # Lets pyqtgraph plot without gcc

pg.setConfigOption('background', 'w')
pg.setConfigOption('foreground', 'k')

# GUI for visualizing data from database
class crashyGUI(QtGui.QWidget) :

    def __init__(self) :
        # Make the window
        QtGui.QWidget.__init__(self)
        self.resize(700, QtGui.QDesktopWidget().screenGeometry(self).height()*.85)
        self.setWindowTitle('Data Visualization')

        # Create tab interface
        tabWidget = QtGui.QTabWidget(self)

        # define the tab objects
        self.tabEeny = QtGui.QWidget(tabWidget)
        self.tabMeeny = QtGui.QWidget(tabWidget)
        self.tabMiney = QtGui.QWidget(tabWidget)
        self.tabMoe = QtGui.QWidget(tabWidget)
        self.tabCatchaTiger = QtGui.QWidget(tabWidget)
        self.tabByThe = QtGui.QWidget(tabWidget)
        self.tabToe = QtGui.QWidget(tabWidget)

        # Initialize the tab objects
        self.initTabCatchaTiger()

        ###########################################
        ############### Main Layout ###############
        ###########################################

        tabWidget.addTab(self.tabEeny, 'Eeny')
        tabWidget.addTab(self.tabMeeny, 'Meeny')
        tabWidget.addTab(self.tabMiney, 'Miney')
        tabWidget.addTab(self.tabMoe, 'Moe')
        tabWidget.addTab(self.tabCatchaTiger, 'Catch a Tiger')
        tabWidget.addTab(self.tabByThe, 'By The')
        tabWidget.addTab(self.tabToe, 'Toe')

        self.mainLayout = QtGui.QVBoxLayout(self)
        self.mainLayout.addWidget(tabWidget)

        self.setLayout(self.mainLayout)

    def initTabCatchaTiger(self):
        ###########################################
        ############# ADC Capture Tab #############
        ###########################################
        # define tab layout
        grid = QtGui.QGridLayout(self.tabCatchaTiger)

        # create copy of adc plot and add to row 3 of the grid
        self.catchaTigerPlot1 = pg.PlotWidget(name = 'Catch a Tiger 1', parent = self.tabCatchaTiger)
        self.catchaTigerPlot1.setTitle('Catch a Tiger 1')
        grid.addWidget(self.catchaTigerPlot1, 2, 0, 1, 8)

        self.catchaTigerPlot2 = pg.PlotWidget(name = 'Catch a Tiger 2', parent = self.tabCatchaTiger)
        self.catchaTigerPlot2.setTitle('Catch a Tiger 2')
        grid.addWidget(self.catchaTigerPlot2, 3, 0, 1, 8)

        # set layout for tab
        self.tabCatchaTiger.setLayout(grid)

    def closeEvent(self, event) :
            pass

def main() :
    # open a QApplication and dialog() GUI
    app = QtGui.QApplication([])

    windowCrashy = crashyGUI()
    windowCrashy.show()
    app.exec_()

main()
Run Code Online (Sandbox Code Playgroud)

ekh*_*oro 14

在这个例子中似乎有两个密切相关的问题.

第一个导致Qt QObject::startTimer: QTimer can only be used with threads started with QThread在退出时打印消息.

第二个(可能不会影响所有用户)导致Qt打印QPixmap: Must construct a QApplication before a QPaintDevice,然后在退出时转储核心.

这两个问题都是由python在退出时以不可预测的顺序删除对象引起的.

在该示例中,可以通过将以下行添加到__init__顶级窗口来修复第二个问题:

    self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
Run Code Online (Sandbox Code Playgroud)

除非QApplication.setQuitOnLastWindowClosed已更改为False,否则将确保应用程序在正确的时间退出,并且Qt有机会在python垃圾收集器开始工作之前自动删除顶级窗口的所有子项.

但是,为了使其完全成功,所有相关对象必须在父子层次结构中链接在一起.示例代码在可以的地方执行此操作,但是在PlotWidget类的初始化中似乎存在一些关键位置.

特别是,没有什么可以确保在PlotWidget创建父项时设置父项的中心项.如果代码的相关部分更改为:

class PlotWidget(GraphicsView):
    ...
    def __init__(self, parent=None, background='default', **kargs):
        GraphicsView.__init__(self, parent, background=background)
        ...
        self.plotItem = PlotItem(**kargs)
        # make sure the item gets a parent
        self.plotItem.setParent(self)
        self.setCentralItem(self.plotItem)
Run Code Online (Sandbox Code Playgroud)

那么QTimer消息的第一个问题也就消失了.

  • 使用`WA_DeleteOnClose`已经适用于我的情况.谢谢 :) (2认同)

Luk*_*uke 6

这是一个更好的答案:

你允许QApplication在python退出之前收集它们.这会导致两个不同的问题:

  1. QTimer错误消息是由pyqtgraph在QApplication销毁后试图跟踪其ViewBox引起的.

  2. 崩溃似乎是Qt/PyQt固有的.以下崩溃的方式相同:

    from PyQt4 import Qt, QtGui, QtCore
    
    def main() :
        app = QtGui.QApplication([])
        x = QtGui.QGraphicsView()
        s = QtGui.QGraphicsScene()
        x.setScene(s)
        x.show()
        app.exec_()
    
    main()
    
    Run Code Online (Sandbox Code Playgroud)

您可以通过添加global app到主函数或通过QApplication在模块级别创建来修复它.

  • 我不认为"全球应用程序"修复可能也是可靠的.当我使用OP的原始示例尝试它时,它仍然在退出时转储核心.对我有用的解决方案是在主窗口的`__init__`中添加`self.setAttribute(QtCore.Qt.WA_DeleteOnClose)`,虽然我不知道这一般是否更可靠.但应该指出的是,只有在创建绘图小部件时才需要这些"修复" - 否则原始示例对我来说很好. (2认同)

Luk*_*uke 0

就我个人而言,我不再花任何精力去解决退出崩溃问题——只需使用pg.exit()它就可以了。

(但是如果你碰巧发现 pyqtgraph 中的错误,请毫不犹豫地在 github 上提出问题)