PyQt:创建pixMap后显示文件对话框导致段错误

1 投票
2 回答
1096 浏览
提问于 2025-04-17 16:44

我正在用PyQt开发一个图形用户界面(GUI),目的是对一些实验中收集的数据进行可视化分析。这个界面会要求用户指定一个文件夹,里面存放着要分析的数据:

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

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

这个界面提供了一个播放功能,用户可以通过这个功能查看实验数据随时间的变化。这是通过一个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还很陌生,觉得我可能没有正确处理定时器。
我使用的是PyQt 4.9,Python 2.7.3,运行在Ubuntu 10.04上。

编辑-1

在看到Luke的回答后,我回去检查了我的代码。
这是nextFrame方法,每次定时器发出timeout信号时都会调用它。这个方法更新了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时做错了什么。

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

如果这个测试程序在你这儿没有崩溃,那说明问题出在你没有给我们看的代码里。

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(也就是需要重新绘制图像的时候),这个指针就会被用到。
如果不保持对ImageQt对象的引用,Python的垃圾回收机制就会把它回收掉。这样一来,Qt就会尝试从一个已经被释放的内存区域读取数据,这就会导致程序崩溃。

撰写回答