如何丢弃 QEvent 而不是忽略它

Posted

技术标签:

【中文标题】如何丢弃 QEvent 而不是忽略它【英文标题】:How to discard an QEvent instead of just ignoring it 【发布时间】:2017-10-08 17:02:41 【问题描述】:

我有两个小部件ParentWidgetChildWidget 都派生自QWidget 并且都覆盖void dragEnterEvent(QDragEnterEvent *event)

现在ChildWidget 包含在ParentWidget 中。现在假设某个名为eventQDragEvent* 可能对ParentWidget 有效,但对ChildWidget 无效,并假设调用ChildWidgetdragEnterEvent

现在我可以只调用event->ignore() 以忽略ChildWidget 的事件,但随后会调用dragEnterEvent for ParentWidget

这是我的问题。如果事件已经在ChildWidget 中被丢弃,我不希望ParentWidgetdragEnterEvent 被调用。

简单地说,我只是不希望事件被忽略,而且事件需要在dragEnterEventChildWidget 中完全丢弃。

ParentWidgetChildWidget 是松散耦合组件的假设下,如何实现这样的行为?

最小示例

以下示例显示了我想要实现的目标,并且在某种意义上也是一种可行的方法。在更复杂的场景下会导致代码过于复杂。

ChildWidget 接受删除以 txt 结尾的文件名,而 ParentWidget 接受所有删除,除了已经被 ChildWidget 忽略的文件名。

ma​​in.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();
        
    
;

【问题讨论】:

你能展示你的实现吗?您可以为ChildWidget 重新实现dragEnterEvent,因为它是虚拟的 @ThibautB.,我为两个小部件实现了dragEnterEventChildWidgetParentWidget。只是 dragEnterEventParentWidget 应该表现不同,如果事件在 dragEnterEventChildWidget 中被忽略。 @ThibautB。 QDragEnterEvent 可以传输这个布尔值吗? 如果你使用类变量是的,我认为 @ThibautB.,我为我的问题找到了解决方法。如果事件位置有一个子小部件,我知道该事件之前已经在子小部件中被忽略了。因此我也可以在父小部件中忽略它。以下代码进入ParentWidgetdragEnterEventif (auto childWidget = childAt(event-&gt;pos())) event-&gt;ignore(); 【参考方案1】:

如果您希望该事件被丢弃,您需要接受它:

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

【讨论】:

非常感谢这个很酷的解决方案。我昨天也找到了一个非常相似的解决方案。我会在这里发布。【参考方案2】:

您的解决方案是一个不错的解决方法。

或者,您可以将事件类型更改为非拖动事件。由于事件不再是QDragEnterEvent,它不会被分派给父级。有两种实现方式:一种是更改QEventt(类型)成员。另一种是就地破坏事件并在那里重新创建一个纯空事件。

// https://github.com/KubaO/***n/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_layoutthis;
   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_layoutthis;
   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 appargc, args;
   ParentWidget widget;
   widget.show();
   app.exec();

#include "main.moc"

【讨论】:

【参考方案3】:

昨天经过长时间的交谈,我找到了以下更好的解决我的问题的方法。该解决方案类似于 Benjamin T 的解决方案。再次感谢 ThibautB。进行富有成效的讨论。

这是我的工作代码。

ma​​in.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();
        
    
;

【讨论】:

以上是关于如何丢弃 QEvent 而不是忽略它的主要内容,如果未能解决你的问题,请参考以下文章

✎Qt-doc—QEvent事件系统

✎Qt-doc—QEvent事件系统

✎Qt-doc—QEvent事件系统

如何编写一个丢弃其参数的通用可变参数 lambda?

Rx Swift 丢弃 .do(onNext: ) 我如何让它触发?

如何制作拖放jquery?