У меня есть два виджета ParentWidget
а также ChildWidget
оба получены из QWidget
и оба имеют приоритет void dragEnterEvent(QDragEnterEvent *event)
,
Сейчас ChildWidget
содержится в ParentWidget
, Теперь предположим, что определенный QDragEvent*
называется event
может быть действительным для ParentWidget
, но не для ChildWidget
и предположим, что dragEnterEvent
за ChildWidget
называется.
Теперь я могу просто позвонить event->ignore()
чтобы игнорировать событие для ChildWidget
, но потом dragEnterEvent
за ParentWidget
называется.
И это моя проблема. Я не хочу, чтобы dragEnterEvent
за ParentWidget
вызывается, если событие уже было удалено в ChildWidget
,
Проще говоря, я просто не хочу, чтобы событие просто игнорировалось, но, кроме того, событие должно быть полностью отброшено внутри dragEnterEvent
из ChildWidget
,
Как можно добиться такого поведения в предположении, что ParentWidget
а также ChildWidget
такое слабосвязанные компоненты?
Минимальный пример
Следующий пример показывает, чего я пытаюсь достичь, и в некотором смысле это также осуществимый подход. В случае более сложных сценариев это приведет к чрезмерно сложному коду.
ChildWidget
принимает отбрасывание имен файлов, заканчивающихся на txt
тогда как ParentWidget
принимает все капли, кроме тех, которые уже проигнорированы 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();
}
}
};
Если вы хотите, чтобы событие было отменено, вам нужно принимать Это:
void dragEnterEvent(QDragEnterEvent *event) override {
qDebug() << "Child";
if (auto mimeData=event->mimeData()) {
[...]
event->acceptProposedAction();
}
else {
event->setAction(Qt::IgnoreAction);
event->accept();
}
}
Вот как Qt отправляет события в виджеты: событие распространяется от дочернего элемента к родительскому, пока виджет не примет его.
Из кода Qt:
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();
}
Ваше решение — достойный обходной путь.
Кроме того, вы можете изменить тип события на событие без перетаскивания. Поскольку событие перестает быть QDragEnterEvent
, он не будет отправлен родителю. Есть два способа реализовать это: один — изменить t
(тип) член QEvent
, Другой способ — уничтожить событие на месте и воссоздать обычное нулевое событие.
// 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"
После вчерашнего долгого разговора я нашел лучшее решение моей проблемы. Решение похоже на решение Бенджамина Т. Еще раз большое спасибо ThibautB. для плодотворного обсуждения.
Вот мой рабочий код.
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();
}
}
};