How can I handle and filter events that are accepted and discarded deep inside some hierarchy without having to subclass every component on the way?

372 views Asked by At

Assume following structure:

MainWindow
--->MySpecialWidget
    |-->QLineEdit
    |-->QSpinBox
    |-->QPushButton
    ---><Basically any other Widget accepting QMouseEvent>

Clicking on any on the above mentioned will cause its respective feature to be activated and the QMouseEvent to be accepted and discarded.

I would like to react inside my MainWindow on any mouseclick in a generic way. More specifically I would like to hinder basic Qt widgets from accepting QMouseEvent while still reacting to it in their usual way. Not accepting should only occur when they are children of MySpecialWidget. Since the QMouseEvent is accepted two layers deep, my MainWindow can not access it by the means of a direct eventFilter.

My current solution (simplified):

void MySpecialWidget::eventFilter(QObject *o, QEvent *e)
{
    if(e->type() == QEvent::MouseButtonPress)
    {
        QMouseEvent *mE = static_cast<QMouseEvent*>(e);
        
        QMouseEvent newMouseEvent = new QMouseEvent(QMouseEvent::MouseButtonPress, mE->pos(), mE->button(), mE->buttons(), mE->modifiers());
        qApp->notify(this, &newMouseEvent);
        // Check how this handled the copied Mouseevent
        return /*result of check*/;
    }
    return Base::eventFilter(o, e);
}

I was also thinking about attaching my MainWindows eventfilter to everything relevant but that sounds like a nightmare to handle in bigger projects.

The question arises: How can I handle and filter events that are accepted and discarded deep inside some hierarchy without having to subclass every component on the way?

Has anyone ever done something similar or do you know if it is even possible in another way? Is my current solution acceptable?

Edit: I noticed for different QWidgets you get different behavior when a QMouseEvent is "eaten". For example MySpecialWidget can filter QMouseEvents of QLineEdit but can not of QSpinBox since it itself contains a QLineEdit. I find this behavior of this library highly irritating.

Edit2 clarification: Although vahanchoos comment would solve the problem, I'm looking for a solution that can work with MySpecialWidget without touching the QApplication or contained classes.

Any comment on my approach or the feasibility of my request would be highly appriciated!

2

There are 2 answers

9
Atmo On BEST ANSWER

I hope I understood the keypoint behind this question.

Basically, if your issue is "How to catch the events for a big number of widgets of various type, including those that are made of sub-widgets (like QSpinbox > QLineEdit) without making it a nightmare to manage", you can make use of QObject::findChildren.

More precisely, add this to your window constructor after the call to ui->setupUi() and that will catch everything under the instance of MySpecialWidget:

auto children = ui->mySpecialWidget->findChildren<QWidget*>(QString(), Qt::FindChildrenRecursively);
for (QWidget* w : children)
    w->installEventFilter(ui->mySpecialWidget);

As you probably already understood, this will explore all its children widget (recursively) and install the instance as a common event filter for all.


Side note:

Something is wrong with your implementation of MySpecialWidget::eventFilter. It is supposed to return a boolean. Overriding this method usually is done like this:

bool MySpecialWidget::eventFilter(QObject *obj, QEvent *event)
{
    if(event->type() == QEvent::MouseButtonRelease)
    {
        QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
        //do something
        return true;
    }
    else // standard event processing
        return QObject::eventFilter(obj, event);
}
1
cbuchart On

I remember doing something similar to simplify massive toggling mouse events. It basically consisted on an "invisible shield" widget hovering the controls I wanted to block. When visible, this view caught all the events; otherwise they were forwarded, "passing through" it.

You may get it implemented using a QStackedLayout, putting the shield on the top of the stack and setting the stacking mode to StackAll. When catching the events, you can both process them and then do not accept the event to let it go through.

The main drawback here is that it will change how your MySpecialWidget is designed due to the stacked layout, but it will depend on your UI architecture.