Reputation: 6084
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();
}
}
};
Upvotes: 1
Views: 1436
Reputation: 6084
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();
}
}
};
Upvotes: 0
Reputation: 8311
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();
}
Upvotes: 3
Reputation: 98435
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"
Upvotes: 0