Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

My Qt eventFilter() doesn't stop events as it should

Something is fundamentally wrong with my eventFilter, as it lets every single event through, while I want to stop everything. I've read lots of documentation on QEvent, eventFilter() and so on, but obviously I'm missing something big. Essentially, I'm trying to create my own modal-functionality for my popup-window class based on QDialog. I want to implement my own since the built-in setModal(true) includes a lot of features, e.g. playing QApplication::Beep(), that I want to exclude. Basically, I want to discard all events going to the QWidget (window) that created my popup. What I have so far is,

// popupdialog.h
#ifndef POPUPDIALOG_H
#define POPUPDIALOG_H

#include <QDialog>
#include <QString>

namespace Ui {class PopupDialog;}

class PopupDialog : public QDialog
{
   Q_OBJECT
public:
    explicit PopupDialog(QWidget *window=0, QString messageText="");
    ~PopupDialog();
private:
    Ui::PopupDialog *ui;
    QString messageText;
    QWidget window; // the window that caused/created the popup
    void mouseReleaseEvent(QMouseEvent*); // popup closes when clicked on
    bool eventFilter(QObject *, QEvent*);
};

...

// popupdialog.cpp
#include "popupdialog.h"
#include "ui_popupdialog.h"

PopupDialog::PopupDialog(QWidget *window, QString messageText) :
    QDialog(NULL), // parentless
    ui(new Ui::PopupDialog),
    messageText(messageText),
    window(window)
{
    ui->setupUi(this);
    setAttribute(Qt::WA_DeleteOnClose, true); // Prevents memory leak
    setWindowFlags(Qt::Window | Qt::FramelessWindowHint);
    ui->message_text_display->setText(messageText);

    window->installEventFilter(this);

    //this->installEventFilter(window); // tried this also, just to be sure ..
}

PopupDialog::~PopupDialog()
{
    window->removeEventFilter(this);
    delete ui;
}

// popup closes when clicked on
void PopupDialog::mouseReleaseEvent(QMouseEvent *e)
{
    close();
}

Here's the problem, the filter doesn't work. Note that if I write a std::cout inside the if(...), I see that it does trigger whenever events are sent to window, it just doesn't stop them.

bool PopupDialog::eventFilter(QObject *obj, QEvent *e)
{
    if( obj == window )
        return true; //should discard the signal (?)
    else
        return false; // I tried setting this to 'true' also without success
}

When the user interacts with the main program, a PopupDialog can be created like this:

PopupDialog *popup_msg = new PopupDialog(ptr_to_source_window, "some text message");
popup_msg->show();
// I understand that naming the source 'window' might be a little confusing.
// I apologise for that. The source can in fact be any 'QWidget'.

Everything else works as expected. Only the event filter fails. I want the filter to remove events sent to the window that created the popup; like mouse clicking and key pressing, until the popup is closed. I'm expecting to be extremely embarrassed when someone points out a trivial fix in my code.

like image 821
Wololo Avatar asked Jan 13 '17 09:01

Wololo


2 Answers

You have to ignore all events that arrive in the widget tree of the window. Therefore, you need to install the eventFilter application-wide and check, if the object you are filtering on is a descendant of window. In other words: Replace

window->installEventFilter(this);

by

QCoreApplication::instance()->installEventFilter(this);

and implement the event filter function this way:

bool PopupDialog::eventFilter(QObject *obj, QEvent *e)
{
    if ( !dynamic_cast<QInputEvent*>( event ) )
        return false;

    while ( obj != NULL )
    {
        if( obj == window )
            return true;
        obj = obj->parent();
    }
    return false;
}

I tried it, tested it and it worked for me.

Note: Using event filters in Qt is a bit messy in my experience, since it is not quite transparent what is happening. Expect bugs to pop up from time to time. You may consider disabling the main window instead, if you and your clients don't have a problem with the grayed-out main window as a consequence.

like image 165
Ralph Tandetzky Avatar answered Sep 20 '22 19:09

Ralph Tandetzky


After the massive amount of responses, feedback, suggestions and time ivested in extensive research I've finally found what I believe to be the optimal, and safest solution. I wish to express my sincere gratidtude to everyone for their aid to what Kuba Ober describes as "(...) not as simple of a problem as you think".

We want to filter out all certain events from a widget, including its children. This is difficult, because events may be caught in the childrens default eventfilters and responded to, before they are caught and filtered by the the parent's custom filter for which the programmer implements. The following code solves this problem by installing the filter on all children upon their creation. This example assumes the use of Qt Creator UI-forms and is based on the following blog post: How to install eventfilters for all children.

// The widget class (based on QMainWindow, but could be anything) for
// which you want to install the event filter on, includings its children

class WidgetClassToBeFiltered : public QMainWindow
{
    Q_OBJECT
public:
    explicit WidgetClassToBeFiltered(QWidget *parent = 0);
    ~WidgetClassToBeFiltered();
private:
    bool eventFilter(QObject*, QEvent*);
    Ui::WidgetClassToBeFiltered *ui;
};

...

WidgetClassToBeFiltered::WidgetClassToBeFiltered(QWidget *parent) :
    QMainWindow(parent), // Base Class constructor
    ui(new Ui::WidgetClassToBeFiltered)
{
    installEventFilter(this); // install filter BEFORE setupUI.
    ui->setupUi(this);
}

...

bool WidgetClassToBeFiltered::eventFilter(QObject *obj, QEvent* e)

{    
    if( e->type() == QEvent::ChildAdded ) // install eventfilter on children
    {
        QChildEvent *ce = static_cast<QChildEvent*>(e);
        ce->child()->installEventFilter(this);
    }
    else if( e->type() == QEvent::ChildRemoved ) // remove eventfilter from children
    {
        QChildEvent *ce = static_cast<QChildEvent*>(e);
        ce->child()->removeEventFilter(this);
    }
    else if( (e->type() == QEvent::MouseButtonRelease) ) // e.g. filter out Mouse Buttons Relases
    {

       // do whatever ..
       return true; // filter these events out
    }

    return QWidget::eventFilter( obj, e ); // apply default filter
}

Note that this works, because the eventfilter installs itself on added children! Hence, it should also work without the use of UI-forms.

like image 41
Wololo Avatar answered Sep 20 '22 19:09

Wololo