1

I have two widgets ParentWidget and ChildWidget both being derived from QWidget and both overriding void dragEnterEvent(QDragEnterEvent *event).

Now ChildWidget is contained in the ParentWidget. Now assume that a certain QDragEvent* called event might be valid for ParentWidget, but not for ChildWidget and assume that dragEnterEvent for ChildWidget is called.

Now I can just call event->ignore() in order to ignore the event for ChildWidget, but then dragEnterEvent for ParentWidget is called.

And this is my problem. I don't want, that dragEnterEvent for ParentWidget is getting called, if the event was already discarded in ChildWidget.

Simply speaking I just don't want the event just to be ignored, but moreover the event needs to be completely discarded inside the dragEnterEvent of ChildWidget.

How can achieve such a behavior under the assumption that ParentWidget and ChildWidget are loosely coupled components?

Minimal Example

The following example shows what I'm trying to achieve and also is a workable approach in some sense. In case of more complicated scenarios it would result in overly complicated code.

The ChildWidget accepts drop of filenames ending with txt, whereas the ParentWidget accepts all drops, except the ones already ignored by ChildWidget.

main.cpp

#include <QApplication>
#include "ParentWidget.h"

int main(int argc, char** args) {
    QApplication app(argc, args);
    auto widget=new ParentWidget;
    widget->show();
    app.exec();
}

ParentWidget.h

#pragma once

#include <QWidget>
#include <QDebug>
#include <QDragEnterEvent>
#include <QHBoxLayout>
#include <QLabel>

#include "ChildWidget.h"

class ParentWidget : public QWidget {
    Q_OBJECT
public:
    ParentWidget(QWidget* parent = nullptr) : QWidget(parent) {
        setLayout(new QHBoxLayout);
        setAcceptDrops(true);
        layout()->addWidget(new QLabel("ParentLabel"));
        layout()->addWidget(new ChildWidget);
    }

    void dragEnterEvent(QDragEnterEvent *event) override {
        qDebug() << "Parent";
        // Check if event was already ignored in ChildWidget? 
        if (auto childWidget = qobject_cast<ChildWidget*>(childAt(event->pos()))) {
            event->ignore();
        }
        else {
            event->acceptProposedAction();
        }
    }
};

ChildWidget.h

#pragma once

#include <QWidget>
#include <QUrl>
#include <QMimeData>
#include <QDragEnterEvent>
#include <QLabel>
#include <QDebug>
#include <QByteArray>
#include <QHBoxLayout>

class ChildWidget : public QWidget {
    Q_OBJECT
public:
    ChildWidget(QWidget* parent = nullptr) : QWidget(parent) {
        setAcceptDrops(true);
        setLayout(new QHBoxLayout);
        layout()->addWidget(new QLabel("ChildLabel"));
    }

    void dragEnterEvent(QDragEnterEvent *event) override {
        qDebug() << "Child";
        if (auto mimeData=event->mimeData()) {
            auto url = QUrl(mimeData->text());
            if (!url.isValid()) { event->ignore(); return; }
            if (!url.isLocalFile()) { event->ignore(); return; }
            auto filename = url.fileName();
            if (!filename.endsWith(".txt")) { event->ignore(); return; }
            // ChildWidget can only process txt files.
            qDebug() << url.fileName();         
            event->acceptProposedAction();
        }
        else {
            event->ignore();
        }
    }
};
Aleph0
  • 5,816
  • 4
  • 29
  • 80
  • Can you show your implementation ? You can reimplement ```dragEnterEvent``` for ```ChildWidget``` because it's a virtual – thibsc May 10 '17 at 07:50
  • @ThibautB., I implemented `dragEnterEvent` for both widgets, `ChildWidget` and `ParentWidget`. It's just that `dragEnterEvent` for `ParentWidget` should behave differently, if the event was ignored in `dragEnterEvent` of `ChildWidget`. – Aleph0 May 10 '17 at 07:52
  • @ThibautB. Can `QDragEnterEvent` transport this boolean? – Aleph0 May 10 '17 at 07:57
  • If you use a class variable yes, I think – thibsc May 10 '17 at 08:01
  • @ThibautB., I found a workaround for me problem. In case there is a child widget at the events position I knew that the event was already previously ignored in the child widget. Hence I can also ignore it in the parent widget. Following code goes in `dragEnterEvent` of the `ParentWidget`. `if (auto childWidget = childAt(event->pos())) { event->ignore(); }` – Aleph0 May 10 '17 at 08:03
  • it's weird, your method signature are the same ? But if you have a solution... ;) however ```dragEnterEvent``` of ```ChildWidget``` should be called without call ```dragEnterEvent``` of ```ParentWidget``` – thibsc May 10 '17 at 08:22
  • I really need to strip my code in order to create a minimal example. It's really to confusing. I guess. :-) – Aleph0 May 10 '17 at 08:23
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/143845/discussion-between-thibaut-b-and-aleph0). – thibsc May 10 '17 at 08:38

3 Answers3

3

If you want the event to be discarded, you need to accept it:

void dragEnterEvent(QDragEnterEvent *event) override {
    qDebug() << "Child";
    if (auto mimeData=event->mimeData()) {
        [...]         
        event->acceptProposedAction();
    }
    else {
        event->setAction(Qt::IgnoreAction);
        event->accept();
    }
}

This is how Qt dispatch events to widgets: the event is propagated from child to parent until a widget accepts it.

From Qt code:

while (w) {
    if (w->isEnabled() && w->acceptDrops()) {
        res = d->notify_helper(w, dragEvent); // calls dragEnterEvent() on w
        if (res && dragEvent->isAccepted()) {
            QDragManager::self()->setCurrentTarget(w);
             break; // The event was accepted, we break, the event will not propagate to the parent 
        }
    }
    if (w->isWindow())
        break;
    dragEvent->p = w->mapToParent(dragEvent->p.toPoint());
    w = w->parentWidget();
}
Benjamin T
  • 8,120
  • 20
  • 37
  • Many thanks to this cool solution. I also found a quite similar solution yesterday. I'll post it here. – Aleph0 May 11 '17 at 06:18
0

Your solution is a decent workaround.

Alternatively, you can change the event type to a non-drag event. Since the event ceases to be a QDragEnterEvent, it won't get dispatched to the parent. There are two ways to implement it: one is to change the t (type) member of QEvent. Another is to destruct the event in-place and re-create a plain null event there.

// https://github.com/KubaO/stackoverflown/tree/master/questions/event-discard-43885834
#include <QtWidgets>

void wipeEvent(QEvent * event) {
   struct Helper : QEvent {
      static void wipe(QEvent * e) {
         static_cast<Helper*>(e)->t = QEvent::None;
      }
   };
   Helper::wipe(event);
}

void wipeEvent2(QEvent *event) {
   event->~QEvent(); // OK since the destructor is virtual.
   new (event) QEvent(QEvent::None);
}

class ChildWidget : public QWidget {
   Q_OBJECT
   QHBoxLayout m_layout{this};
   QLabel m_label{"ChildLabel"};
public:
   ChildWidget() {
      setAcceptDrops(true);
      m_layout.addWidget(&m_label);
   }
   void dragEnterEvent(QDragEnterEvent *event) override {
      qDebug() << "Child";
      while (auto mimeData=event->mimeData()) {
         auto url = QUrl(mimeData->text());
         if (!url.isValid()) break;
         if (!url.isLocalFile()) break;
         auto filename = url.fileName();
         if (!filename.endsWith(".txt")) break;
         // ChildWidget can only process txt files.
         qDebug() << url.fileName();
         return event->acceptProposedAction();
      }
      wipeEvent(event);
   }
};

class ParentWidget : public QWidget {
   Q_OBJECT
   QHBoxLayout m_layout{this};
   QLabel m_label{"ParentLabel"};
   ChildWidget m_child;
public:
   ParentWidget() {
      setAcceptDrops(true);
      m_layout.addWidget(&m_label);
      m_layout.addWidget(&m_child);
   }
   void dragEnterEvent(QDragEnterEvent *event) override {
      qDebug() << "Parent";
      event->acceptProposedAction();
   }
};

int main(int argc, char** args) {
   QApplication app{argc, args};
   ParentWidget widget;
   widget.show();
   app.exec();
}
#include "main.moc"
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
0

After a long chat yesterday I found the following better solution to my problem. The solution is similar to the one of Benjamin T. Many thanks again to ThibautB. for the fruitful discussion.

Here goes my working code.

main.cpp

#include <QApplication>
#include "ParentWidget.h"

int main(int argc, char** args) {
    QApplication app(argc, args);
    auto widget=new ParentWidget;
    widget->show();
    app.exec();
}

ChildWidget.h

#pragma once

#include <QWidget>
#include <QUrl>
#include <QMimeData>
#include <QDragEnterEvent>
#include <QLabel>
#include <QDebug>
#include <QByteArray>
#include <QHBoxLayout>
//#include "MyDragEnterEvent.h"

class ChildWidget : public QWidget {
    Q_OBJECT
public:
    ChildWidget(QWidget* parent = nullptr) : QWidget(parent) {
        setAcceptDrops(true);
        setLayout(new QHBoxLayout);
        layout()->addWidget(new QLabel("ChildLabel"));
    }

    void dragEnterEvent(QDragEnterEvent *event) {
        qDebug() << "Child";
        if (auto mimeData=event->mimeData()) {
            auto url = QUrl(mimeData->text());
            if (!url.isValid()) { event->setDropAction(Qt::DropAction::IgnoreAction); event->ignore(); return; }
            if (!url.isLocalFile()) { event->setDropAction(Qt::DropAction::IgnoreAction); event->ignore(); return; }
            auto filename = url.fileName();
            if (!filename.endsWith(".txt")) { event->setDropAction(Qt::DropAction::IgnoreAction); event->ignore(); return; }
            // ChildWidget can only process txt files.
            qDebug() << url.fileName();     
            event->acceptProposedAction();
        }
        else {
            qDebug() << "Ignored in Child";
            event->setDropAction(Qt::DropAction::IgnoreAction);
            event->ignore();
        }
    }
};

ParentWidget.h

#pragma once

#include <QWidget>
#include <QDebug>
#include <QDragEnterEvent>
#include <QHBoxLayout>
#include <QLabel>

#include "ChildWidget.h"

class ParentWidget : public QWidget {
    Q_OBJECT
public:
    ParentWidget(QWidget* parent = nullptr) : QWidget(parent) {
        setLayout(new QHBoxLayout);
        setAcceptDrops(true);
        layout()->addWidget(new QLabel("ParentLabel"));
        layout()->addWidget(new ChildWidget);
    }

    void dragEnterEvent(QDragEnterEvent *event) override {
        if (event->dropAction() == Qt::IgnoreAction) {
            qDebug() << "Ignored in Parent";
            event->ignore();
        }
        else {
            qDebug() << "Accepted in Parent";
            event->acceptProposedAction();
        }
    }
};
Aleph0
  • 5,816
  • 4
  • 29
  • 80