PyQt:创建pixMap后显示文件对话框导致段错误
我正在用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 个回答
抱歉,我无法重现你遇到的程序崩溃问题。这里是我用来尝试复现你崩溃情况的完整源代码(使用的是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_()
如果这个测试程序在你这儿没有崩溃,那说明问题出在你没有给我们看的代码里。
好的,我最后找到了问题所在。
我修改了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就会尝试从一个已经被释放的内存区域读取数据,这就会导致程序崩溃。