Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. QApplication::processEvents() not handling all mouse click events, have to call it twice in a row
Forum Updated to NodeBB v4.3 + New Features

QApplication::processEvents() not handling all mouse click events, have to call it twice in a row

Scheduled Pinned Locked Moved Solved General and Desktop
9 Posts 4 Posters 1.6k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • R Offline
    R Offline
    rebus_x
    wrote on last edited by rebus_x
    #1

    Hi. I have a QPushButton that triggers a long-running function. My problem is if the button is clicked during that function running, additional mouse-click events are added to event queue.

    So I use an event filter to get rid of those, and that is done by calling processEvents() in function itself. But for some reason, if processEvents() is called once, it doesn't get rid of all the click events, and I have to call it once more. That seems to fix the problem, but I'd like to know why it's happening or if I'm doing something wrong; I read about calling processEvents() twice here, and it's briefly mentioned about some bug being the reason of this, which I sadly haven't found any information about.

    I did a small example to illustrate what I'm talking about.

    widget.h

    #ifndef WIDGET_H
    #define WIDGET_H
    
    #include <QPushButton>
    #include <QWidget>
    
    class Widget : public QWidget
    {
        Q_OBJECT
    
    public:
        Widget(QWidget *parent = nullptr);
        ~Widget() override;
    
        QPushButton *button;
    
        bool working = false;
        void dumbFunction();
        virtual bool eventFilter(QObject *watched, QEvent *event) override;
    };
    #endif // WIDGET_H
    
    

    widget.cpp

    #include "widget.h"
    #include <QApplication>
    #include <QDebug>
    #include <QThread>
    
    Widget::Widget(QWidget *parent)
        : QWidget(parent)
    {
        button = new QPushButton("pressme", this);
        //connect(button, &QPushButton::clicked, this, &Widget::dumbFunction);  //<- happens less if call function through signal
    
        button->installEventFilter(this);
    }
    
    Widget::~Widget()
    {
    }
    
    
    void Widget::dumbFunction()
    {
        static int count = 0;
    
        working = true;
        button->setDisabled(true);
        button->repaint();
    
        qInfo() << "Me, useless" << ++count;
        QThread::sleep(2);
        QApplication::instance()->processEvents();
        //QApplication::instance()->processEvents();   //<- uncomment for proper behavior
    
        button->setEnabled(true);
        working = false;
    }
    
    
    bool Widget::eventFilter([[maybe_unused]] QObject *watched, QEvent *event)
    {
        if (event->type() == QEvent::Type::MouseButtonPress || event->type() == QEvent::Type::MouseButtonDblClick)
        {
            if (working)
                qInfo() << "...filtered!";
            else
                dumbFunction();
            return true;
        }
        return false;
    }
    
    

    main.h

    #include "widget.h"
    
    #include <QApplication>
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        Widget w;
        w.show();
        return a.exec();
    }
    
    

    To reproduce:

    1. click the button
    2. click one more time while function is running
    3. in the output, you'll see that the additional click was not filtered but triggered the function again (only 1st additional click is not filtered)
    4. uncomment second processEvents() call, now it works as it should

    I use Qt 5.13.1 64-bit, both the example and my main application have only main thread.

    1 Reply Last reply
    0
    • J.HilkJ Offline
      J.HilkJ Offline
      J.Hilk
      Moderators
      wrote on last edited by J.Hilk
      #2

      You're using processEvents() in the first place, there is always a better way to do what one (actually) wants. And it has almost always unforeseeable consequences, like reentering a function while it's executed!

      Don't use it.

      See the details section of QThread for two examples
      https://doc.qt.io/qt-5/qthread.html#details

      or my GitHub-Example project:
      https://github.com/DeiVadder/QtThreadExample

      for examples of all Qt-ways to do threading/parallelization.


      Be aware of the Qt Code of Conduct, when posting : https://forum.qt.io/topic/113070/qt-code-of-conduct


      Q: What's that?
      A: It's blue light.
      Q: What does it do?
      A: It turns blue.

      R 1 Reply Last reply
      2
      • J.HilkJ J.Hilk

        You're using processEvents() in the first place, there is always a better way to do what one (actually) wants. And it has almost always unforeseeable consequences, like reentering a function while it's executed!

        Don't use it.

        See the details section of QThread for two examples
        https://doc.qt.io/qt-5/qthread.html#details

        or my GitHub-Example project:
        https://github.com/DeiVadder/QtThreadExample

        for examples of all Qt-ways to do threading/parallelization.

        R Offline
        R Offline
        rebus_x
        wrote on last edited by
        #3

        @J-Hilk Thanks for the reply, so you're suggesting the use of threads to solve the main problem (unwanted LMB clicks)?

        J.HilkJ KroMignonK B 3 Replies Last reply
        0
        • R rebus_x

          @J-Hilk Thanks for the reply, so you're suggesting the use of threads to solve the main problem (unwanted LMB clicks)?

          J.HilkJ Offline
          J.HilkJ Offline
          J.Hilk
          Moderators
          wrote on last edited by
          #4

          @rebus_x If you have a long running function - that doesn't access UI elements!!! - that is blocking your GUI, than yes, using threads is usually the way to go


          Be aware of the Qt Code of Conduct, when posting : https://forum.qt.io/topic/113070/qt-code-of-conduct


          Q: What's that?
          A: It's blue light.
          Q: What does it do?
          A: It turns blue.

          1 Reply Last reply
          1
          • R rebus_x

            @J-Hilk Thanks for the reply, so you're suggesting the use of threads to solve the main problem (unwanted LMB clicks)?

            KroMignonK Offline
            KroMignonK Offline
            KroMignon
            wrote on last edited by KroMignon
            #5

            @rebus_x said in QApplication::processEvents() not handling all mouse click events, have to call it twice in a row:

            Thanks for the reply, so you're suggesting the use of threads to solve the main problem (unwanted LMB clicks)?

            As @J-Hilk already told you, locking the main thread is never the best way to do.
            If you only want to wait a long processing function to finish, and this function do not interact with UI elements, you can considere to use QtConcurrent::run(). In combination with QFutureWatcher() you can also be informed when concurrent function is finished and got returned value.

            Take a look at the documentation ==> https://doc.qt.io/qt-5/qtconcurrentrun.html

            It is an old maxim of mine that when you have excluded the impossible, whatever remains, however improbable, must be the truth. (Sherlock Holmes)

            1 Reply Last reply
            1
            • R rebus_x

              @J-Hilk Thanks for the reply, so you're suggesting the use of threads to solve the main problem (unwanted LMB clicks)?

              B Offline
              B Offline
              Bonnie
              wrote on last edited by
              #6

              @rebus_x said in QApplication::processEvents() not handling all mouse click events, have to call it twice in a row:

              (unwanted LMB clicks)

              If you don't mind the function blocking the ui, I usually simply do

              button->setEnabled(false);
              //call the long-running function
              button->setEnabled(true);
              
              R 1 Reply Last reply
              0
              • B Bonnie

                @rebus_x said in QApplication::processEvents() not handling all mouse click events, have to call it twice in a row:

                (unwanted LMB clicks)

                If you don't mind the function blocking the ui, I usually simply do

                button->setEnabled(false);
                //call the long-running function
                button->setEnabled(true);
                
                R Offline
                R Offline
                rebus_x
                wrote on last edited by
                #7

                @Bonnie Yes, but this unfortunately doesn't prevent click events from happening, which is mentioned in QAbstractButton documentation:

                Note: As opposed to other widgets, buttons derived from QAbstractButton accept mouse and context menu events when disabled.
                

                In my example I do the same thing with disabling and enabling

                B 1 Reply Last reply
                0
                • R rebus_x

                  @Bonnie Yes, but this unfortunately doesn't prevent click events from happening, which is mentioned in QAbstractButton documentation:

                  Note: As opposed to other widgets, buttons derived from QAbstractButton accept mouse and context menu events when disabled.
                  

                  In my example I do the same thing with disabling and enabling

                  B Offline
                  B Offline
                  Bonnie
                  wrote on last edited by Bonnie
                  #8

                  @rebus_x
                  Yes, I know. But disabling the button will make it not pressable, which is also mentioned in the doc:

                  isEnabled() indicates whether the button can be pressed by the user.

                  Actually if you look into the source code of QAbstractButton, you can see it will filter out the mouse press / release / dblclick events in its own event function when disabled.

                  bool QAbstractButton::event(QEvent *e)
                  {
                      // as opposed to other widgets, disabled buttons accept mouse
                      // events. This avoids surprising click-through scenarios
                      if (!isEnabled()) {
                          switch(e->type()) {
                          case QEvent::TabletPress:
                          case QEvent::TabletRelease:
                          case QEvent::TabletMove:
                          case QEvent::MouseButtonPress:
                          case QEvent::MouseButtonRelease:
                          case QEvent::MouseButtonDblClick:
                          case QEvent::MouseMove:
                          case QEvent::HoverMove:
                          case QEvent::HoverEnter:
                          case QEvent::HoverLeave:
                          case QEvent::ContextMenu:
                  #if QT_CONFIG(wheelevent)
                          case QEvent::Wheel:
                  #endif
                              return true;
                          default:
                              break;
                          }
                      }
                  ...
                  }
                  

                  So I don't really understand why do you add those eventFilter and processEvents.
                  [ADDED]
                  To make the button keep disabled when event is called right after the function finishes, I think we should call setEnabled(true) asynchronously. Something like

                  button->setEnabled(false);
                  //call the long-running function
                  QMetaObject::invokeMethod(button, "setEnabled", Qt::QueuedConnection, Q_ARG(bool, true));
                  
                  R 1 Reply Last reply
                  0
                  • B Bonnie

                    @rebus_x
                    Yes, I know. But disabling the button will make it not pressable, which is also mentioned in the doc:

                    isEnabled() indicates whether the button can be pressed by the user.

                    Actually if you look into the source code of QAbstractButton, you can see it will filter out the mouse press / release / dblclick events in its own event function when disabled.

                    bool QAbstractButton::event(QEvent *e)
                    {
                        // as opposed to other widgets, disabled buttons accept mouse
                        // events. This avoids surprising click-through scenarios
                        if (!isEnabled()) {
                            switch(e->type()) {
                            case QEvent::TabletPress:
                            case QEvent::TabletRelease:
                            case QEvent::TabletMove:
                            case QEvent::MouseButtonPress:
                            case QEvent::MouseButtonRelease:
                            case QEvent::MouseButtonDblClick:
                            case QEvent::MouseMove:
                            case QEvent::HoverMove:
                            case QEvent::HoverEnter:
                            case QEvent::HoverLeave:
                            case QEvent::ContextMenu:
                    #if QT_CONFIG(wheelevent)
                            case QEvent::Wheel:
                    #endif
                                return true;
                            default:
                                break;
                            }
                        }
                    ...
                    }
                    

                    So I don't really understand why do you add those eventFilter and processEvents.
                    [ADDED]
                    To make the button keep disabled when event is called right after the function finishes, I think we should call setEnabled(true) asynchronously. Something like

                    button->setEnabled(false);
                    //call the long-running function
                    QMetaObject::invokeMethod(button, "setEnabled", Qt::QueuedConnection, Q_ARG(bool, true));
                    
                    R Offline
                    R Offline
                    rebus_x
                    wrote on last edited by
                    #9

                    @Bonnie Concerning disabled buttons behavior - you are correct, turns out that in the docs quote that I posted "accept" actually means "accept()".

                    I tinkered a little more with the problem and looks like I got the answer.

                    • As the main thread is blocked, a button can't work on events, so they go somewhere in OS event queue
                    • first call of processEvents() right after long action(s) won't see any events. But it seems that it triggers something that grabs events from OS and moves them to thread event queue
                    • because of that, future calls of processEvents() deal with those
                    1 Reply Last reply
                    0

                    • Login

                    • Login or register to search.
                    • First post
                      Last post
                    0
                    • Categories
                    • Recent
                    • Tags
                    • Popular
                    • Users
                    • Groups
                    • Search
                    • Get Qt Extensions
                    • Unsolved