[Development] QPushButton: drag and drop

Volker Hilsheimer volker.hilsheimer at qt.io
Fri Jun 3 23:05:28 CEST 2022



> On 3 Jun 2022, at 18:07, Giuseppe D'Angelo via Development <development at qt-project.org> wrote:
> 
> On 03/06/2022 14:20, Laszlo Papp wrote:
>> I do not know the details very well, but from a user point of view, it would be good to either let mouseReleaseEvent get triggered as usual after the dragging has finished or at least not to have artifacts like the above around.
>> I have also noticed several posts on Stack Overflow, Qt Centre, etc, about this issue without a "real solution". E.g. this dates back to 12 years ago: https://www.qtcentre.org/threads/34539-Drag-and-drop-from-QPushButton <https://www.qtcentre.org/threads/34539-Drag-and-drop-from-QPushButton>
>> What do you think about this issue?
> 
> That it's a bug. Did you search for submitted reports or did you submit one yourself?

As discussed with Laszlo on discord, I don’t think it is a bug.

If we were to deliver a mouseReleaseEvent to the widget that initiated the drag via QDrag::exec after the exec returns, then we handle the mouseReleaseEvent twice. In the case of the button, it would mean that we emit clicked() if the drag is dropped inside the button. Check the following, which simulates that Qt would do that. Drag from the button, and drop the drag inside the button. The button now emits clicked(). You most certainly don’t want slot connected to that signal to execute in that interaction.

This is of course a special case, but it shows that we can’t just start delivering MouseButtonRelease events to widgets when a drag operation finishes, because they would suddenly execute mouseReleaseEvent code in a state in which they don’t expect it to. For instance, QAbstractItemView does not expect to get a mouseReleaseEvent when QAbstractItemView::startDrag returns. I didn’t test it, but it start the editor of the item that was dragged, which might crash when that index got removed by the drag!

Widgets that support drag’n’drop need to reset their state when QDrag::exec returns, and must be able to rely that they don’t get a mouseReleaseEvent when that mouse release already was processed by the drag’n’drop system to end the drag.

Volker


#include <QtWidgets>

class DragButton : public QPushButton
{
public:
    using QPushButton::QPushButton;

protected:
    void mouseMoveEvent(QMouseEvent *e)
    {
        QDrag *drag = new QDrag(this);
        QMimeData *mimeData = new QMimeData;

        mimeData->setText(text());
        drag->setMimeData(mimeData);

        Qt::DropAction dropAction = drag->exec();

        // simulate that Qt deliveres a mouseReleaseEvent to the source widget
        // after the drag finished.
        if (e->buttons() == Qt::LeftButton) {
            QMouseEvent *mouseRelease = new QMouseEvent(
                QEvent::MouseButtonRelease, e->position(), e->globalPosition(), Qt::LeftButton, {}, {}
            );
            QApplication::postEvent(this, mouseRelease);
        }
        qDebug() << dropAction;
    }

    void mouseReleaseEvent(QMouseEvent *e)
    {
        qDebug() << "mouseRelease" << e;
        QPushButton::mouseReleaseEvent(e);
    }
};

int main(int argc, char *argv[])
{
    QApplication app(argc,argv);

    QWidget window;
    DragButton button("Drag me");
    QObject::connect(&button, &QPushButton::clicked, &window, []{
        qDebug() << "Clicked!";
    });

    QVBoxLayout layout;
    layout.addWidget(&button);
    window.setLayout(&layout);

    window.show();

    return app.exec();
}



More information about the Development mailing list