拖放 QTreeView 中包含 QImage 的项目失败
Drag and drop in QTreeView fails with items that hold a QImage
我在 QTreeView
中有一个项目列表。每个项目都包含一个 QImage
对象。如果我尝试拖放项目,程序会冻结。但是当我注释掉行 objMod._Image = QImage(flags = Qt.AutoColor)
时,程序运行正常。
如何使用 QImage
对象拖放项目? QImage
包含渲染的图像。渲染过程需要一段时间,所以最好保留 QImage 对象。
import sys
import os
from PySide.QtCore import *
from PySide.QtGui import *
from PySide.QtUiTools import *
from PIL import Image, ImageCms, ImageQt
class ObjModel:
def __init__(self):
self._Image = None
class DragMoveTest(QMainWindow):
def __init__(self):
super(DragMoveTest,self).__init__()
self.initGUI()
self.show()
def initGUI(self):
self.treeView = QTreeView()
modelTreeView = QStandardItemModel()
self.treeView.setModel(modelTreeView)
for i in range(0, 4):
objMod = ObjModel()
objMod._Image = None
objMod._Image = QImage(flags = Qt.AutoColor)
item = QStandardItem('Test: %s' % str(i))
item.setData(objMod, Qt.UserRole + 1)
modelTreeView.invisibleRootItem().appendRow(item)
self.treeView.setDragDropMode(QAbstractItemView.InternalMove)
self.setCentralWidget(self.treeView)
def main(args):
app = QApplication(sys.argv)
qt_main_wnd = DragMoveTest()
ret = app.exec_()
sys.exit(ret)
if __name__ == "__main__":
main(sys.argv)
这是由 PySide 中的错误引起的。在拖放操作中,被拖项目中的数据必须是serialized。对于大多数数据类型,这将由 Qt 处理,但对于特定于 Python 的类型,需要特殊处理。这种特殊处理似乎在 PySide 中被打破了。如果您的示例转换为 PyQt,则在尝试拖动项目时会引发 TypeError
,但程序不会冻结。
问题的根源在于您正在使用自定义 Python class 存储数据。 PyQt 使用 pickle 序列化自定义数据类型,但不可能同时 pickle 存储在其 __dict__
中的 QImage
,因此操作失败。我假设 PySide 必须尝试类似的事情,但出于某种原因,它在失败时不会引发错误。 Qt在拖动的同时抓取鼠标,所以如果操作异常失败,不会再松开,程序会出现卡死的现象。
解决此问题的最简单方法是避免使用自定义 class 来保存 QImage
,而是将图像直接存储在项目中:
image = QImage()
item = QStandardItem('Test: %s' % i)
item.setData(image, Qt.UserRole + 1)
要存储更多数据项,您可以为每个数据项使用不同的数据角色,或者使用字典来保存所有数据项:
data = {'image': QImage(), 'title': 'foo', 'timestamp': 1756790}
item.setData(data, Qt.UserRole + 1)
但是,如果您这样做,您必须始终使用字符串键 dict,否则你会面临和以前一样的问题。 (使用字符串键意味着 dict
可以转换为 Qt 知道如何序列化的 QMap
。
(注意:如果您想知道 Qt class 是否可以序列化,请查看文档以查看它是否定义了 datastream operators)。
我想出了一个不同的解决方案。拥有一个包含 io.BytesIO 的对象更容易。您将 ImageData 存储到 bytesIO 变量中。在您的图像库上,您可以从 bytesIO 变量打开图像。
在演示中,class ObjModel 可以处理来自 Pillow/PIL 的 QImage 和图像。如果您使用 set 方法,图像对象将被转换为 bytesIO。
简而言之,这里有一个工作示例:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import os
import io
from PySide.QtCore import *
from PySide.QtGui import *
from PySide.QtUiTools import *
from PIL import Image, ImageCms, ImageQt
########################################################################
class ObjModel:
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
self._ImageByteIO = None
#----------------------------------------------------------------------
def getObjByte(self):
""""""
return self._ImageByteIO
#----------------------------------------------------------------------
def getQImage(self):
""""""
try:
self._ImageByteIO.seek(0)
qImg = QImage.fromData(self._ImageByteIO.getvalue())
return qImg
except:
return None
#----------------------------------------------------------------------
def getPILImage(self):
""""""
try:
self._ImageByteIO.seek(0)
img = Image.open(tBytesIO)
return img
except:
return None
#----------------------------------------------------------------------
def setObjByte(self, fileName):
""""""
try:
tBytesIO = io.BytesIO()
f = open (fileName, 'rb')
tBytesIO.write(f.read())
f.close()
self._ImageByteIO = tBytesIO
except:
self._ImageByteIO = None
#----------------------------------------------------------------------
def setQImage(self, qImg):
""""""
try:
tBytesIO = io.BytesIO()
qByteArray = QByteArray()
qBuf = QBuffer(qByteArray)
qBuf.open(QIODevice.ReadWrite)
qImg.save(qBuf, 'PNG')
tBytesIO = io.BytesIO()
tBytesIO.write(qByteArray.data())
self._ImageByteIO = tBytesIO
except:
self._ImageByteIO = None
#----------------------------------------------------------------------
def setPILImage(self, pImg):
""""""
tBytesIO = io.BytesIO()
pImg.save(tBytesIO, 'png')
self._ImageByteIO = tBytesIO
#----------------------------------------------------------------------
class DragMoveTest(QMainWindow):
def __init__(self):
""""""
super(DragMoveTest,self).__init__()
self.initGUI()
self.show()
#----------------------------------------------------------------------
def initGUI(self):
""""""
self.treeView = QTreeView()
modelTreeView = QStandardItemModel()
self.treeView.setModel(modelTreeView)
for i in range(0, 4):
objMod = ObjModel()
objMod.setQImage(QImage(flags = Qt.AutoColor))
item = QStandardItem('Test: %s' % str(i))
item.setData(objMod, Qt.UserRole + 1)
modelTreeView.invisibleRootItem().appendRow(item)
self.treeView.setDragDropMode(QAbstractItemView.InternalMove)
self.setCentralWidget(self.treeView)
#----------------------------------------------------------------------
def main(args):
app = QApplication(sys.argv)
qt_main_wnd = DragMoveTest()
ret = app.exec_()
sys.exit(ret)
#----------------------------------------------------------------------
if __name__ == "__main__":
main(sys.argv)
我在 QTreeView
中有一个项目列表。每个项目都包含一个 QImage
对象。如果我尝试拖放项目,程序会冻结。但是当我注释掉行 objMod._Image = QImage(flags = Qt.AutoColor)
时,程序运行正常。
如何使用 QImage
对象拖放项目? QImage
包含渲染的图像。渲染过程需要一段时间,所以最好保留 QImage 对象。
import sys
import os
from PySide.QtCore import *
from PySide.QtGui import *
from PySide.QtUiTools import *
from PIL import Image, ImageCms, ImageQt
class ObjModel:
def __init__(self):
self._Image = None
class DragMoveTest(QMainWindow):
def __init__(self):
super(DragMoveTest,self).__init__()
self.initGUI()
self.show()
def initGUI(self):
self.treeView = QTreeView()
modelTreeView = QStandardItemModel()
self.treeView.setModel(modelTreeView)
for i in range(0, 4):
objMod = ObjModel()
objMod._Image = None
objMod._Image = QImage(flags = Qt.AutoColor)
item = QStandardItem('Test: %s' % str(i))
item.setData(objMod, Qt.UserRole + 1)
modelTreeView.invisibleRootItem().appendRow(item)
self.treeView.setDragDropMode(QAbstractItemView.InternalMove)
self.setCentralWidget(self.treeView)
def main(args):
app = QApplication(sys.argv)
qt_main_wnd = DragMoveTest()
ret = app.exec_()
sys.exit(ret)
if __name__ == "__main__":
main(sys.argv)
这是由 PySide 中的错误引起的。在拖放操作中,被拖项目中的数据必须是serialized。对于大多数数据类型,这将由 Qt 处理,但对于特定于 Python 的类型,需要特殊处理。这种特殊处理似乎在 PySide 中被打破了。如果您的示例转换为 PyQt,则在尝试拖动项目时会引发 TypeError
,但程序不会冻结。
问题的根源在于您正在使用自定义 Python class 存储数据。 PyQt 使用 pickle 序列化自定义数据类型,但不可能同时 pickle 存储在其 __dict__
中的 QImage
,因此操作失败。我假设 PySide 必须尝试类似的事情,但出于某种原因,它在失败时不会引发错误。 Qt在拖动的同时抓取鼠标,所以如果操作异常失败,不会再松开,程序会出现卡死的现象。
解决此问题的最简单方法是避免使用自定义 class 来保存 QImage
,而是将图像直接存储在项目中:
image = QImage()
item = QStandardItem('Test: %s' % i)
item.setData(image, Qt.UserRole + 1)
要存储更多数据项,您可以为每个数据项使用不同的数据角色,或者使用字典来保存所有数据项:
data = {'image': QImage(), 'title': 'foo', 'timestamp': 1756790}
item.setData(data, Qt.UserRole + 1)
但是,如果您这样做,您必须始终使用字符串键 dict,否则你会面临和以前一样的问题。 (使用字符串键意味着 dict
可以转换为 Qt 知道如何序列化的 QMap
。
(注意:如果您想知道 Qt class 是否可以序列化,请查看文档以查看它是否定义了 datastream operators)。
我想出了一个不同的解决方案。拥有一个包含 io.BytesIO 的对象更容易。您将 ImageData 存储到 bytesIO 变量中。在您的图像库上,您可以从 bytesIO 变量打开图像。 在演示中,class ObjModel 可以处理来自 Pillow/PIL 的 QImage 和图像。如果您使用 set 方法,图像对象将被转换为 bytesIO。 简而言之,这里有一个工作示例:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import os
import io
from PySide.QtCore import *
from PySide.QtGui import *
from PySide.QtUiTools import *
from PIL import Image, ImageCms, ImageQt
########################################################################
class ObjModel:
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
self._ImageByteIO = None
#----------------------------------------------------------------------
def getObjByte(self):
""""""
return self._ImageByteIO
#----------------------------------------------------------------------
def getQImage(self):
""""""
try:
self._ImageByteIO.seek(0)
qImg = QImage.fromData(self._ImageByteIO.getvalue())
return qImg
except:
return None
#----------------------------------------------------------------------
def getPILImage(self):
""""""
try:
self._ImageByteIO.seek(0)
img = Image.open(tBytesIO)
return img
except:
return None
#----------------------------------------------------------------------
def setObjByte(self, fileName):
""""""
try:
tBytesIO = io.BytesIO()
f = open (fileName, 'rb')
tBytesIO.write(f.read())
f.close()
self._ImageByteIO = tBytesIO
except:
self._ImageByteIO = None
#----------------------------------------------------------------------
def setQImage(self, qImg):
""""""
try:
tBytesIO = io.BytesIO()
qByteArray = QByteArray()
qBuf = QBuffer(qByteArray)
qBuf.open(QIODevice.ReadWrite)
qImg.save(qBuf, 'PNG')
tBytesIO = io.BytesIO()
tBytesIO.write(qByteArray.data())
self._ImageByteIO = tBytesIO
except:
self._ImageByteIO = None
#----------------------------------------------------------------------
def setPILImage(self, pImg):
""""""
tBytesIO = io.BytesIO()
pImg.save(tBytesIO, 'png')
self._ImageByteIO = tBytesIO
#----------------------------------------------------------------------
class DragMoveTest(QMainWindow):
def __init__(self):
""""""
super(DragMoveTest,self).__init__()
self.initGUI()
self.show()
#----------------------------------------------------------------------
def initGUI(self):
""""""
self.treeView = QTreeView()
modelTreeView = QStandardItemModel()
self.treeView.setModel(modelTreeView)
for i in range(0, 4):
objMod = ObjModel()
objMod.setQImage(QImage(flags = Qt.AutoColor))
item = QStandardItem('Test: %s' % str(i))
item.setData(objMod, Qt.UserRole + 1)
modelTreeView.invisibleRootItem().appendRow(item)
self.treeView.setDragDropMode(QAbstractItemView.InternalMove)
self.setCentralWidget(self.treeView)
#----------------------------------------------------------------------
def main(args):
app = QApplication(sys.argv)
qt_main_wnd = DragMoveTest()
ret = app.exec_()
sys.exit(ret)
#----------------------------------------------------------------------
if __name__ == "__main__":
main(sys.argv)