I'm implementing drag and drop QTreeView based on my custom model. All works fine, my tree displays data, drag and drop is enabled and now the last step lies ahead of me - to drop and trasfer dragged data. To do this I need to implement mimeTypes, mimeData and dropMimeData methods in my model. And now my question: Is there any easy standard way how to pass an arbitrary Python object through QMimeData? I'm doing just an internal move within QTreeView which displays hierarchy of my Python classes Person. And I want to reorder them. No drag and drop outside the application, not even outside of control. I have found only single one tutorial: link text. But is it the only way? Cannot it be done without encoding the Python object into ByteArray. I need really simple solution for my only one class Person. Thank you.
-
I am currently trying to implement the exact same functionality (in pyqt) and will let you know how I progress. One useful link I have found is the following (note, it doesn't answer your question, but it does offer some more insight into qt internal mime types - I'm still digesting it myself): http://diotavelli.net/PyQtWiki/Handling%20Qt%27s%20internal%20item%20MIME%20type – bvz Nov 19 '10 at 02:41
-
Hi. Thanks for the info. I'll let a notice here if I found something too. Unfortunately your link doesn't work (they have some server cache problems). – Ondrej Vencovsky Nov 22 '10 at 11:35
-
This is turning out to be quite a struggle so far. Are you using C or Python? I actually used the example in your link to wrap my objects up into a ByteArray and it seems to work (at least that portion seems to work - the rest is still a mystery to me). Another technique might be to wrap up your index information in a ByteArray i.e. store the row and col #'s (but you'd need to recursively capture parents, grandparents, etc. in a format that you could then extract later). But at least that way would be all text and easier/lighter to move around. Re: link. I can't get it to work anymore either. – bvz Nov 22 '10 at 21:34
-
Well maybe there's another solution...? What about to simply store dragged object in a variable "draggedObject" by getting selected object in drag-start event? And in drag-end event first find the target object and then use the draggedObject variable and perform all the actions? QMimeData would be out of business completely... – Ondrej Vencovsky Nov 24 '10 at 12:14
-
I suppose it's possible (haven't thought it through entirely). You would have to capture the QModelIndex of the dragged item, store that, then in the dropMimeData extract that and then the item(s) it(they) point to. It would also require that your QTreeView class collect the information and then store it manually in the model (you don't want the model to know anything about the view's specific implementations). But frankly, that seems like a lot more work than just using simple mime data. The example you linked to works... as does the version I listed below (I'm using it myself in my own app) – bvz Nov 24 '10 at 18:27
-
Yes, you're probably right. I think your solution is OK, it's smart and I like it. Thanks for the inspiration. – Ondrej Vencovsky Nov 25 '10 at 09:31
2 Answers
Do not try to implement drag and drop by reparenting the underlying python object. This won't work if the drag comes from outside your process; nor will it work for a copy operation (your node objects probably cannot exist in multiple places in the tree).
Think of a drag and drop "move" as three operations:
- serialize the data to some byte string
- deserialize into a new index (or new indexes)
- (optional: if "move" rather than "copy") remove the old index(es)
mineData() and dropMimeData() are the serialize and deserialize operations that you provide. Python provides some easy ways to implement them -- check the documentation for the pickle module. If you're lucky, pickle.dumps() and pickle.loads() will work out-of-the-box for you.
Edit: I couldn't figure out how to paste code in comments, so here's the solution my comment refers to. This is safe, in the sense that it will fail by throwing a KeyError instead of causing crashes if you happen to break your rules.
# 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)

- 2,097
- 1
- 20
- 31
-
Ironically, I found myself again in your situation and tried to figure out a way to do this. Of course my first google hit turned up my own advice staring me in the face. Here is one way: – Paul Du Bois Jul 15 '11 at 08:03
Ok, I think I have a possible solution for you.
Keep in mind that I am a complete neophyte in this area so no warranties that his a) works b) is a decent solution c) won't make a "real" programmer toss their lunch.
What I did was convert the entire ancestor tree of a particular item into a text list of row column pairs. (i.e. list the row and column of the dragged item, the row and column of its parent, the row and column of its parent's parent, etc... till we get to an invalid index - i.e. the root)
This looks something like this (this example shows that the dragged item is four levels deep):
2;0,1;0,5;0,1,0
^ ^ ^ ^
| | | |
| | | great grandparent (and child of the root item)
| | |
| | grandparent
| |
| parent
|
item being dragged
Later, in the dropMimeData function, I reverse the list (so that it reads from the root back down to the item being dragged) and build the indexes one at a time till I get back to the originally dragged item.
Here are the snippets of code that make that all work. Again, I can't warrantee that this is a good idea, just that it appears to work and does not require that you serialize your python objects into a ByteArray.
Hope this helps.
#---------------------------------------------------------------------------
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

- 869
- 3
- 13
- 25
-
Incidentally, I have an almost working complete example here: http://stackoverflow.com/questions/4252114/qt-pyqt-qtreeview-internal-drag-and-drop-almost-working-dragged-item-disappe – bvz Nov 23 '10 at 02:14
-
Hey Ben; I just checked out that other question. Now that you've learned that this is not the way to approach things, don't you think you should edit this answer with your findings? It's misleading as it stands. – Paul Du Bois Dec 11 '10 at 10:14