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();
}
}
};
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();
}