QMimeData中的Python对象
我正在根据自己的模型实现一个可以拖放的QTreeView。现在一切都正常,我的树形结构显示了数据,拖放功能也启用了。接下来我需要做的最后一步就是处理拖放的数据。为此,我需要在我的模型中实现mimeTypes、mimeData和dropMimeData这几个方法。我的问题是:有没有简单的标准方法可以通过QMimeData传递任意的Python对象?我只是想在QTreeView内部移动数据,这个树形结构展示的是我的Python类Person的层级关系。我想重新排列这些对象。并不需要在应用程序外部进行拖放,甚至不需要在控件外部进行。我只找到一个教程:链接文本。但这真的是唯一的方法吗?难道不能不把Python对象编码成ByteArray吗?我只需要一个非常简单的解决方案,针对我唯一的这个Person类。谢谢。
2 个回答
好的,我想我有一个可能的解决办法给你。
请记住,我在这个领域还是个新手,所以不能保证这个方法 a) 有效 b) 是个好办法 c) 不会让真正的程序员感到恶心。
我做的事情是把某个特定项目的整个祖先树转换成一个文本列表,里面包含行和列的配对。也就是说,列出被拖动项目的行和列、它的父项的行和列、父项的父项的行和列,等等,一直到遇到无效的索引为止——也就是根节点。
这个列表看起来像这样(这个例子显示被拖动的项目有四层深):
2;0,1;0,5;0,1,0
^ ^ ^ ^
| | | |
| | | great grandparent (and child of the root item)
| | |
| | grandparent
| |
| parent
|
item being dragged
然后,在 dropMimeData 函数中,我把这个列表反转(这样就能从根节点开始,一直到被拖动的项目),然后一个一个地构建索引,直到回到最初被拖动的项目。
这里是让这一切运作的代码片段。再次强调,我不能保证这是个好主意,只是看起来有效,并且不需要把你的 Python 对象序列化成 ByteArray。
希望这能帮到你。
#---------------------------------------------------------------------------
def mimeTypes(self):
"""
Only accept the internal custom drop type which is plain text
"""
types = QtCore.QStringList()
types.append('text/plain')
return types
#---------------------------------------------------------------------------
def mimeData(self, index):
"""
Wrap the index up as a list of rows and columns of each
parent/grandparent/etc
"""
rc = ""
theIndex = index[0] #<- for testing purposes we only deal with 1st item
while theIndex.isValid():
rc = rc + str(theIndex.row()) + ";" + str(theIndex.column())
theIndex = self.parent(theIndex)
if theIndex.isValid():
rc = rc + ","
mimeData = QtCore.QMimeData()
mimeData.setText(rc)
return mimeData
#---------------------------------------------------------------------------
def dropMimeData(self, data, action, row, column, parentIndex):
"""
Extract the whole ancestor list of rows and columns and rebuild the
index item that was originally dragged
"""
if action == QtCore.Qt.IgnoreAction:
return True
if data.hasText():
ancestorL = str(data.text()).split(",")
ancestorL.reverse() #<- stored from the child up, we read from ancestor down
pIndex = QtCore.QModelIndex()
for ancestor in ancestorL:
srcRow = int(ancestor.split(";")[0])
srcCol = int(ancestor.split(";")[1])
itemIndex = self.index(srcRow, srcCol, pIndex)
pIndex = itemIndex
print itemIndex.internalPointer().get_name()
return True
不要试图通过改变底层的Python对象来实现拖放功能。如果拖动操作来自你程序外部,这种方法是行不通的;而且对于复制操作来说也不适用(你的节点对象可能不能在树中同时存在多个位置)。
可以把拖放“移动”看作三个步骤:
- 把数据转换成一些字节字符串
- 把这些字节字符串转换回新的索引(或者新的索引们)
- (可选:如果是“移动”而不是“复制”)删除旧的索引
mineData() 和 dropMimeData() 就是你需要提供的序列化和反序列化操作。Python提供了一些简单的方法来实现这些操作——你可以查看pickle模块的文档。如果运气好的话,pickle.dumps() 和 pickle.loads() 可以直接用。
补充:我不知道怎么在评论中粘贴代码,所以这里是我评论中提到的解决方案。这是安全的,因为如果你不小心违反了规则,它会抛出一个KeyError,而不是导致程序崩溃。
# drag: store off the data in a safe place, and serialize a cooky
# that the drop target can use to retrieve the data.
self.__tmp_storage_dct = { self.__tmp_storage_cooky: stuff }
m.setData(self.rowlistptr_mime_type, QByteArray(pickle.dumps(self.__tmp_storage_cooky)))
self.__tmp_storage_cooky += 1
# drop:
if mime.hasFormat(self.rowlistptr_mime_type):
print "got tmpstorage"
cooky = pickle.loads(mime.data(self.rowlistptr_mime_type).data())
nodes = self.__tmp_storage_dct.pop(cooky)