2

Using PySide, I construct a draggable label that works exactly how I want:

class DraggableLabel(QtGui.QLabel):
    def __init__(self, txt, parent):
        QtGui.QLabel.__init__(self, txt, parent)
        self.setStyleSheet("QLabel { background-color: rgb(255, 255, 0)}")
    def mouseMoveEvent(self, event):
        drag=QtGui.QDrag(self)
        dragMimeData=QtCore.QMimeData()  
        drag.setMimeData(dragMimeData) 
        drag.exec_(QtCore.Qt.MoveAction)

(Note a full example that uses DraggableLabel is pasted below). Unfortunately, I do not understand what is going on with QMimeData, and fear I am going to run into big problems when I use similar code in real-world examples.

In particular, I am worried that my reimplementation of mouseMoveEvent creates an instance of QMimeData without any argument passed: QtCore.QMimeData(). Is this normal? Within more complex widgets will I be OK if I keep doing that within the relevant event handler: will the program automatically create the right type of MIME data for dragging and dropping?

The reason I fear I am missing something is because at the Qt Drag and Drop documentation, it has lines of code like:

mimeData -> setText(commentEdit->toPlainText());

which seems decidedly not like just letting the program take care of things within a reimplementation of an event handler.

Also, the QMimeData Documentation discusses convenience functions to test, get, and set data, but those are for standard data types (e.g., text, urls). I have found no clear way to define such convenience functions for widgets like my draggable QLabel. Am I missing it? Is there a simple way to find out if I am dragging around a widget of type X?

Edit: I've tried the same code above with much more complicated widgets than QLabels, and it does not work.

Potentially relevant posts:

Dragging a QWidget in QT 5
How to Drag and Drop Custom Widgets?
https://stackoverflow.com/questions/18272650/fill-the-system-clipboard-with-data-of-custom-mime-type Python object in QMimeData


Important Caveat: if you just want to move a widget in a window, you do not need to invoke esoteric drag-drop mechanisms, but more vanilla event handling. See this: Dragging/Moving a QPushButton in PyQt.


Full working self-contained code example that incorporates the above:

# -*- coding: utf-8 -*-
from PySide import QtGui, QtCore

class LabelDrag(QtGui.QWidget):
    def __init__(self):
        QtGui.QWidget.__init__(self)    
        self.initUI()
    def initUI(self):
        self.lbl=DraggableLabel("Drag me", self)  
        self.setAcceptDrops(True)
        self.setGeometry(40,50,200,200)
        self.show()     
    def dragEnterEvent(self,event):
        event.accept()        
    def dropEvent(self, event):  
        self.lbl.move(event.pos())  #moves label to position once the movement finishes (dropped)
        event.accept()  

class DraggableLabel(QtGui.QLabel):
    def __init__(self, txt, parent):
        QtGui.QLabel.__init__(self, txt, parent)
        self.setStyleSheet("QLabel { background-color: rgb(255, 255, 0)}")
    def mouseMoveEvent(self, event):
        drag=QtGui.QDrag(self)
        dragMimeData=QtCore.QMimeData()  
        drag.setMimeData(dragMimeData) 
        drag.exec_(QtCore.Qt.MoveAction)

def main():
    import sys
    qt_app=QtGui.QApplication(sys.argv)
    myMover=LabelDrag()
    sys.exit(qt_app.exec_())

if __name__=="__main__":
    main()

Note I'm posting this at QtCentre as well, will post anything useful from there.

Community
  • 1
  • 1
eric
  • 7,142
  • 12
  • 72
  • 138
  • Important caveat I just added to post: if you just want to move a widget in a window, you do not need to invoke esoteric drag-drop mechanisms, but more vanilla event handling. See this: http://stackoverflow.com/questions/12219727/dragging-moving-a-qpushbutton-in-pyqt – eric Jul 08 '14 at 14:04

1 Answers1

4

In my experience, MimeData is used to filter drag/drop operations so that the action actually makes sense. For instance, you shouldn't be able to drag your QLabel into the middle of a QTextEdit or your browsers address bar, or the desktop of your computer.

From the docs:

QMimeData is used to describe information that can be stored in the clipboard, and transferred via the drag and drop mechanism. QMimeData objects associate the data that they hold with the corresponding MIME types to ensure that information can be safely transferred between applications, and copied around within the same application.

If you were doing something standard, like dragging/dropping text from one place to another, you would use one of the standard MIME types (like setting the MIME data of your drag using dragMimeData.setText('your text') or equivalently dragMimeData.setData('text/plain', 'your text')). However, since you are doing something completely custom, you should probably specify a custom MIME type so that you can't accidentally do things that don't make sense.

So I would set the MIME data to something like dragMimeData.setData('MoveQLabel', QByteArray('any string you want')) which stores an arbitrary string for the MIME type MoveQLabel. This arbitrary string could be used to look up which widget you want to move at the end of the drag (maybe by storing it's position?).

You should modify your LabelDrag class above to check the MIME type of the event (use event.mimeData() to get the QMimeData object you set when you started the drag), and accept or reject the event depending on whether the MIME type matches MoveQLabel (or whatever you call your custom MIME type). This should be done in both the dragEnterEvent and dropEvent methods. You code would look something like:

def dragEnterEvent(self, event):
    # you may also need to check that event.mimeData() returns a valid QMimeData object
    if event.mimeData().hasFormat('MoveQLabel'):
        event.accept()
    else:
        event.reject()

If you also store some useful MIME data with the MIME type (aka something else instead of 'any string you want' above), you could use it to dynamically select, within LabelDrag.dropEvent, which widget it is that you want to move. You can access it through event.mimeData().data('MoveQLabel'). This means that your QWidget can handle moving multiple labels as it will always move the one that is being dropped.

three_pineapples
  • 11,579
  • 5
  • 38
  • 75
  • @ three_pinapples: thanks, great stuff! Unfortunately, I cannot just use `mimedat.setData('newtype', self)`. Something like http://doc.qt.digia.com/qt-maemo/draganddrop-fridgemagnets.html#dragging is needed, which uses `QByteArray` to define the new data type. I will look into this (I do not yet know about QByteArrays or many other things at that example). I will hack at it, edit your answer appropriately, until we've got a self-contained working example for future coders that stumble upon this. I'll also just go ahead and add my full code to my question to save people time. :) – eric Jul 06 '14 at 17:51
  • Something like the following is needed, I think, but I do not understand the first three lines at all. It looks like C++ more than Python to me :O `itemData = QtCore.QByteArray(); dataStream = QtCore.QDataStream(itemData, QtCore.QIODevice.WriteOnly); dataStream << QtCore.QPoint(event.pos() - self.rect().topLeft()); mimeData = QtCore.QMimeData(); mimeData.setData('x-QLabel', itemData)` Anyone care to edit in such lines, with some annotation? I cannot find good documentation of this anywhere. You'd think it would be a common and popular thing. – eric Jul 06 '14 at 18:36
  • 1
    Ah, yes you are right. That of course makes sense, because you shouldn't be able to pass an object between programs! I'll update the answer with a better solution. – three_pineapples Jul 07 '14 at 00:28
  • Re: the string argument to QByteArray ('some text'): what could that string used for/how bad is it to leave it out? Is that the string you were referring to in the last couple of sentences of your answer (which I frankly didn't grok), or were those sentences referring to 'x-QLabel' ? – eric Jul 07 '14 at 02:55
  • You can use the string for anything. It can be empty if you want, or it could contain the x,y coorrdinates of the label, or some other relevant data. And yes, that is the string I mentioned at the end of the post. If you store useful information in it, you can use it to select the `QLabel` to move, or do something else (like what you originally did). I think maybe you didn't understand the last few sentences because you got confused between a `QMimeData` object which contains both a MIME type and MIME data? – three_pineapples Jul 07 '14 at 10:56
  • P.S. Your edits were rejected by several reviewers who thought it changed too much of the original post. I'll make a small change, but as it stands, I think my answer addresses your question. – three_pineapples Jul 07 '14 at 10:57
  • how you would *access* information fed as an argument to QByteArray? Isn't the MIME data just the QLabel you have picked out? Does the argument to QByteArray just uniquely identify the Qlabel type, if you want to have many instances of this MIME type? Is that what you meant? – eric Jul 07 '14 at 11:48
  • 1
    Yes, just use something (eg x,y position in string form) to uniquely identify the QLabel, so that when the drop happens, you can move the correct QLabel. You can't store the QLabel itself as mentioned in an earlier comment. To access the data, use `event.mimeData().data('MoveQLabel').data()` (the last data() is a method of QByteArray which converts it to a string again) – three_pineapples Jul 07 '14 at 12:05