QMimeData中的Python对象

2 投票
2 回答
4679 浏览
提问于 2025-04-16 07:17

我正在根据自己的模型实现一个可以拖放的QTreeView。现在一切都正常,我的树形结构显示了数据,拖放功能也启用了。接下来我需要做的最后一步就是处理拖放的数据。为此,我需要在我的模型中实现mimeTypes、mimeData和dropMimeData这几个方法。我的问题是:有没有简单的标准方法可以通过QMimeData传递任意的Python对象?我只是想在QTreeView内部移动数据,这个树形结构展示的是我的Python类Person的层级关系。我想重新排列这些对象。并不需要在应用程序外部进行拖放,甚至不需要在控件外部进行。我只找到一个教程:链接文本。但这真的是唯一的方法吗?难道不能不把Python对象编码成ByteArray吗?我只需要一个非常简单的解决方案,针对我唯一的这个Person类。谢谢。

2 个回答

1

好的,我想我有一个可能的解决办法给你。

请记住,我在这个领域还是个新手,所以不能保证这个方法 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
2

不要试图通过改变底层的Python对象来实现拖放功能。如果拖动操作来自你程序外部,这种方法是行不通的;而且对于复制操作来说也不适用(你的节点对象可能不能在树中同时存在多个位置)。

可以把拖放“移动”看作三个步骤:

  1. 把数据转换成一些字节字符串
  2. 把这些字节字符串转换回新的索引(或者新的索引们)
  3. (可选:如果是“移动”而不是“复制”)删除旧的索引

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)

撰写回答