1

我正在使用 PyQt 开发一个 GUI,对一些实验中收集的数据进行可视化分析。GUI 要求用户指明要分析的数据所在的目录:

class ExperimentAnalyzer(QtGui.QMainWindow):
    ## other stuff here

    def loadExperiment(self):
        directory = QtGui.QFileDialog.getExistingDirectory(self,
                                                           "Select Directory")
        ## load data from directory here

GUI 提供了一种播放功能,通过该功能,用户可以看到实验数据如何随时间变化。这是通过QTimer实现的:

  def playOrPause(self):
      ## play
      if self.appStatus.timer is None:
          self.appStatus.timer = QtCore.QTimer(self)
          self.appStatus.timer.connect(self.appStatus.timer,
                                       QtCore.SIGNAL("timeout()"),
                                       self.nextFrame)

          self.appStatus.timer.start(40)

       ## pause
       else:
          self.appStatus.timer.stop()
          self.appStatus.timer = None

如果我播放数据的时间序列,然后暂停,然后尝试更改目录以加载新实验中的数据,我会遇到分段错误

使用一些调试打印,我发现应用程序在我调用时崩溃

    QtGui.QFileDialog.getExistingDirectory(self, "Select Directory")

loadExperiment方法中。

我对 Qt 很陌生,我认为我没有正确处理计时器。
我在 Ubuntu 10.04 上使用 PyQt 4.9、Python 2.7.3。

编辑1

在卢克的回答之后,我回到了我的代码。
这是nextFrame方法,每次定时器发出超时信号时都会调用。它更新包含在 GUI 中的 QGraphicsScene 元素:

def nextFrame(self):
    image = Image.open("<some jpg>")
    w, h = image.size
    imageQt = ImageQt.ImageQt(image)
    pixMap = QtGui.QPixmap.fromImage(imageQt)

    self.scene.clear()
    self.scene.addPixmap(pixMap)
    self.view.fitInView(QtCore.QRectF(0, 0, w, h),
                        QtCore.Qt.KeepAspectRatio)

其中 self.scene 和 self.view 对象先前已在 GUI 构造函数中实例化为

self.view = QtGui.QGraphicsView(self)
self.scene = QtGui.QGraphicsScene()
self.view.setScene(self.scene)

我发现评论这一行:

    # self.scene.addPixmap(pixMap)

并重复上面报告的相同操作序列,不再发生分段错误。

编辑2

使用 gdb(使用 python-dbg)运行应用程序,结果是在调用 QPainter::drawPixmap 之后发生分段错误。

(gdb) bt
#0  0xb6861f1d in ?? () from /usr/lib/i386-linux-gnu/libQtGui.so.4
#1  0xb685d491 in ?? () from /usr/lib/i386-linux-gnu/libQtGui.so.4
#2  0xb693bcd3 in ?? () from /usr/lib/i386-linux-gnu/libQtGui.so.4
#3  0xb69390bc in ?? () from /usr/lib/i386-linux-gnu/libQtGui.so.4
#4  0xb6945c77 in ?? () from /usr/lib/i386-linux-gnu/libQtGui.so.4
#5  0xb68bd424 in QPainter::drawPixmap(QPointF const&, QPixmap const&) () from   /usr/lib/i386-linux-gnu/libQtGui.so.4

因此,这不是与计时器处理有关的问题,正如我在第一个实例中所相信的那样。
发生分段错误是因为我对 pixMap 做错了什么。

4

2 回答 2

0

抱歉,我无法重现您看到的段错误。这是我试图重现您的崩溃的应用程序的完整源代码(Qt 4.8.1、PyQt 4.9.1、Python 2.7.3、Kubuntu Precise):

#!/usr/bin/env python

import sys
from PyQt4 import QtCore, QtGui

class AppStatus(object):
    def __init__(self):
        self.timer = None

class ExperimentAnalyzer(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(ExperimentAnalyzer, self).__init__(parent)
        elayout = QtGui.QVBoxLayout()

        self.count = 0
        self.appStatus = AppStatus()

        everything = QtGui.QWidget(self)
        everything.setLayout(elayout)

        button = QtGui.QPushButton("Start/stop timer", self)
        self.connect(button, QtCore.SIGNAL("clicked()"), self.playOrPause)
        elayout.addWidget(button)

        button = QtGui.QPushButton("Load experiment", self)
        self.connect(button, QtCore.SIGNAL("clicked()"), self.loadExperiment)
        elayout.addWidget(button)

        self.setCentralWidget(everything)

    def loadExperiment(self):
        directory = QtGui.QFileDialog.getExistingDirectory(self, "Select Directory")

    def nextFrame(self):
        self.count += 1
        print self.count

    def playOrPause(self):
        if self.appStatus.timer is None:
            self.appStatus.timer = QtCore.QTimer(self)
            self.appStatus.timer.connect(self.appStatus.timer,
                                       QtCore.SIGNAL("timeout()"),
                                       self.nextFrame)

            self.appStatus.timer.start(40)

        else:
            self.appStatus.timer.stop()
            self.appStatus.timer = None

if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    mainwin = ExperimentAnalyzer(None)
    mainwin.show()
    app.exec_()

如果此测试应用程序没有为您崩溃,那么问题出在您没有向我们展示的代码中。

于 2013-02-22T10:59:07.780 回答
0

好的,我最终发现了问题所在。
我修改了nextFrame方法以保持对ImageQt对象的引用,即:

def nextFrame(self):
    ## load the image from file

    self.appStatus.imageQt = ImageQt.ImageQt(image)
    pixMap = QtGui.QPixmap.fromImage(self.appStatus.imageQt)

    ## update the QGraphicsView

并且分段错误消失了。

我对此的解释如下:Qt在内部保留一个指向ImageQt对象的指针,每次触发paintEvent时都会使用该指针(因此,必须重新绘制 pixMap)。
不保留对ImageQt对象的引用允许 Python 的 GC 收集它。结果,Qt将尝试从已释放的内存区域中读取,从而产生分段错误。

于 2013-02-23T11:30:41.737 回答