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. GUI event blocking
Forum Updated to NodeBB v4.3 + New Features

GUI event blocking

Scheduled Pinned Locked Moved Solved General and Desktop
40 Posts 5 Posters 14.3k Views 5 Watching
  • 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.
  • sierdzioS sierdzio

    Normally in such situation your GUI will be blocked, so this has something to do with the code in that slot your are calling.

    See if the slot is not connected by Qt::QueuedConnection by any chance. Or perhaps the slot is using some async operations (like using QNetworkAccessManager, QProcess), then methods return immediately and results are communicated via signals.

    Or see if the code calls processEvents() anywhere in that slot. Or maybe it fires off some QThread.

    There are many possibilities here, without more info I'm just guessing. But definitely, by default, GUI would freeze if slot connected to a button is doing something CPU intensive on the same thread.

    JonBJ Offline
    JonBJ Offline
    JonB
    wrote on last edited by
    #4

    @sierdzio

    Normally in such situation your GUI will be blocked, so this has something to do with the code in that slot your are calling.

    Ah, that is indeed what I needed to know. So the normal behaviour is indeed "block other UI events till slot handler returns in response to original signal", and in that respect Qt behaves to what i am used to from elsewhere.

    I have no idea what the mass of code it calls is doing in detail. I don't think the code explicitly has any of the Qt code you mention, but I don't know for sure, and it's going to be hard to track down.

    As I mentioned, one thing it will be doing is database access, potentially via QSql... calls. Do these count for any kind of processEvents() or QThread in themselves?.

    @mrjj

    You can use the Qt::BlockingQueuedConnection
    as not to allow overlapping runs for same signal.
    Dialogs can be used for modal input blocking the whole parent.
    The enable property can be used to control other elements for non modal designs.

    Hmm. "Same signal" won't do me: it might be invoked by QPushButton::clicked, but potentially user could do any kind of other interaction which invokes a slot, I can't rely on "clicked" only. Dialogs may block parent, but that doesn't help, it's other inputs on same dialog I would be worried about. Unless that's what you mean. It turns out the "window" is actually a stacked tab widget thing which has buttons, table view/widgets etc. Are you saying I can set enabled property of that (the stacked widget) to false to stop any other input controls responding?

    A 1 Reply Last reply
    0
    • mrjjM mrjj

      Hi
      You can use the Qt::BlockingQueuedConnection
      as not to allow overlapping runs for same signal.
      Dialogs can be used for modal input blocking the whole parent.
      The enable property can be used to control other elements for non modal designs.

      kshegunovK Offline
      kshegunovK Offline
      kshegunov
      Moderators
      wrote on last edited by
      #5

      @mrjj said in GUI event blocking:

      You can use the Qt::BlockingQueuedConnection

      If this is not directed to a separate thread (i.e. the receiver object isn't outside the main thread), you'd deadlock the GUI thread, so use at your own discretion!

      Read and abide by the Qt Code of Conduct

      1 Reply Last reply
      1
      • JonBJ JonB

        @sierdzio

        Normally in such situation your GUI will be blocked, so this has something to do with the code in that slot your are calling.

        Ah, that is indeed what I needed to know. So the normal behaviour is indeed "block other UI events till slot handler returns in response to original signal", and in that respect Qt behaves to what i am used to from elsewhere.

        I have no idea what the mass of code it calls is doing in detail. I don't think the code explicitly has any of the Qt code you mention, but I don't know for sure, and it's going to be hard to track down.

        As I mentioned, one thing it will be doing is database access, potentially via QSql... calls. Do these count for any kind of processEvents() or QThread in themselves?.

        @mrjj

        You can use the Qt::BlockingQueuedConnection
        as not to allow overlapping runs for same signal.
        Dialogs can be used for modal input blocking the whole parent.
        The enable property can be used to control other elements for non modal designs.

        Hmm. "Same signal" won't do me: it might be invoked by QPushButton::clicked, but potentially user could do any kind of other interaction which invokes a slot, I can't rely on "clicked" only. Dialogs may block parent, but that doesn't help, it's other inputs on same dialog I would be worried about. Unless that's what you mean. It turns out the "window" is actually a stacked tab widget thing which has buttons, table view/widgets etc. Are you saying I can set enabled property of that (the stacked widget) to false to stop any other input controls responding?

        A Offline
        A Offline
        ambershark
        wrote on last edited by ambershark
        #6

        @JonB Yea it sounds like you are doing a lot in a single GUI click. You should probably have some indication to the user that processing is occurring. Database access, etc, can take a while. In this case maybe pop up a progress bar or message or something.

        The GUI should be locked while that single click is being processed unless it's multithreaded. For example if your click handler does something like this:

        void SomeWidget::onClick()
        {
           // do something
           // do something else
           // done
        }
        

        No events on the GUI should be processed until onClick exits. So once onClick returns it could be clicked again even if it is technically still processing or waiting on a database or something. So if you are using any sort of asynchronous call or a separate thread it should definitely lock the GUI.

        You could also use a flag to denote processing and if that button is clicked a second time ignore the click. However that only protects that single button and not any action in the GUI.

        My L-GPL'd C++ Logger github.com/ambershark-mike/sharklog

        JonBJ 1 Reply Last reply
        0
        • A ambershark

          @JonB Yea it sounds like you are doing a lot in a single GUI click. You should probably have some indication to the user that processing is occurring. Database access, etc, can take a while. In this case maybe pop up a progress bar or message or something.

          The GUI should be locked while that single click is being processed unless it's multithreaded. For example if your click handler does something like this:

          void SomeWidget::onClick()
          {
             // do something
             // do something else
             // done
          }
          

          No events on the GUI should be processed until onClick exits. So once onClick returns it could be clicked again even if it is technically still processing or waiting on a database or something. So if you are using any sort of asynchronous call or a separate thread it should definitely lock the GUI.

          You could also use a flag to denote processing and if that button is clicked a second time ignore the click. However that only protects that single button and not any action in the GUI.

          JonBJ Offline
          JonBJ Offline
          JonB
          wrote on last edited by JonB
          #7

          @ambershark , and others

          I now understand that "normal" Qt processing should be "block-until-return-from-click-handler". (Which is a relief to my general handling of other aspects of UI!)

          However, in the case of the code (large, spaghetti, and completely uncommented) I am looking at, here is what I experience:

          1. Click button to set off large "computation" of some kind. It's going to take, say, 20 seconds to complete.

          2. Wait, say, a couple of seconds for it to "get going". (Don't know if this is required; there's obviously some delay between clicks anyway.)

          3. Click that button again, or any other button/widget on the "stacked widget page".

          4. Code immediately starts executing in response to this new UI action. Verified in debugger and/or messages.

          The question for me is: what might it be in the original code processing which is allowing the second UI interaction to proceed? I need to track it down. To the best of my knowledge the code does not explicitly do things like create threads or process an event loop. It does, however, do database querying, which is why I keep asking if that's enough to cause the re-entrancy?

          I know all about progress bars and blocking flags. Indeed, with a blocking flag, the blocking flag immediately gets hit in #4, proving the point, but not robust enough as you say, e.g. does not cover clicking any other widget in UI.

          The code is so monolithic & interdependent that, say, commenting out sections to see if behaviour altered is difficult/impossible. So I don't know how to approach debugging to discover what this massive block of unintelligible code is doing which allows this re-entrancy, so that I can deal with it properly.

          Can anyone suggest a technique (Python debugging, I'm afraid, not native C++) to aid me in tracking down where the re-entrancy is coming from?

          kshegunovK A 2 Replies Last reply
          0
          • JonBJ JonB

            @ambershark , and others

            I now understand that "normal" Qt processing should be "block-until-return-from-click-handler". (Which is a relief to my general handling of other aspects of UI!)

            However, in the case of the code (large, spaghetti, and completely uncommented) I am looking at, here is what I experience:

            1. Click button to set off large "computation" of some kind. It's going to take, say, 20 seconds to complete.

            2. Wait, say, a couple of seconds for it to "get going". (Don't know if this is required; there's obviously some delay between clicks anyway.)

            3. Click that button again, or any other button/widget on the "stacked widget page".

            4. Code immediately starts executing in response to this new UI action. Verified in debugger and/or messages.

            The question for me is: what might it be in the original code processing which is allowing the second UI interaction to proceed? I need to track it down. To the best of my knowledge the code does not explicitly do things like create threads or process an event loop. It does, however, do database querying, which is why I keep asking if that's enough to cause the re-entrancy?

            I know all about progress bars and blocking flags. Indeed, with a blocking flag, the blocking flag immediately gets hit in #4, proving the point, but not robust enough as you say, e.g. does not cover clicking any other widget in UI.

            The code is so monolithic & interdependent that, say, commenting out sections to see if behaviour altered is difficult/impossible. So I don't know how to approach debugging to discover what this massive block of unintelligible code is doing which allows this re-entrancy, so that I can deal with it properly.

            Can anyone suggest a technique (Python debugging, I'm afraid, not native C++) to aid me in tracking down where the re-entrancy is coming from?

            kshegunovK Offline
            kshegunovK Offline
            kshegunov
            Moderators
            wrote on last edited by
            #8

            @JonB said in GUI event blocking:

            Can anyone suggest a technique (Python debugging, I'm afraid, not native C++) to aid me in tracking down where the re-entrancy is coming from?

            Look for a thread being started in the debugger whenever that piece of code's executed, if that's so then you have your answer.

            Read and abide by the Qt Code of Conduct

            JonBJ 1 Reply Last reply
            0
            • kshegunovK kshegunov

              @JonB said in GUI event blocking:

              Can anyone suggest a technique (Python debugging, I'm afraid, not native C++) to aid me in tracking down where the re-entrancy is coming from?

              Look for a thread being started in the debugger whenever that piece of code's executed, if that's so then you have your answer.

              JonBJ Offline
              JonBJ Offline
              JonB
              wrote on last edited by JonB
              #9

              @kshegunov
              Yeah, thanks, but my knowledge of the PyCharm debugger is insufficient to "Look for a thread being started in the debugger"! Not your problem, but mine :)

              What about stack traceback when the second click causes "re-entrance" to the function, which I can break on? I'm wondering whether that might show me enough to get back, presumably through an event processing loop invocation, to where the code is allowing that to happen?

              kshegunovK 1 Reply Last reply
              0
              • JonBJ JonB

                @kshegunov
                Yeah, thanks, but my knowledge of the PyCharm debugger is insufficient to "Look for a thread being started in the debugger"! Not your problem, but mine :)

                What about stack traceback when the second click causes "re-entrance" to the function, which I can break on? I'm wondering whether that might show me enough to get back, presumably through an event processing loop invocation, to where the code is allowing that to happen?

                kshegunovK Offline
                kshegunovK Offline
                kshegunov
                Moderators
                wrote on last edited by kshegunov
                #10

                @JonB said in GUI event blocking:

                What about stack traceback when the second click causes "re-entrance" to the function, which I can break on? I'm wondering whether that might show me enough to get back, presumably through an event processing loop invocation, to where the code is allowing that to happen?

                Yes, it should be possible, but I imagine it'd land you in the slot handling the actual button click signal. I'd start investigating from that slot anyway - there should be some hint in it. Probably a class is starting a thread internally or something like this. The other option is that someone is calling QCoreAppliction::processEvents intermittently to process the pending events, as already mentioned by @sierdzio, then you will actually need to find the code doing it and act accordingly. My best advice is to get into step-by-step debugging and inspect what's called where in that signal handler, it may be quite a lot of work, but I don't know of any quick and easy way.

                As a "workaround", you could disable the button after it being clicked, and reenable it whenever the long operation has completed, so you don't get the problem with calling the heavy code twice.

                Read and abide by the Qt Code of Conduct

                JonBJ 1 Reply Last reply
                1
                • kshegunovK kshegunov

                  @JonB said in GUI event blocking:

                  What about stack traceback when the second click causes "re-entrance" to the function, which I can break on? I'm wondering whether that might show me enough to get back, presumably through an event processing loop invocation, to where the code is allowing that to happen?

                  Yes, it should be possible, but I imagine it'd land you in the slot handling the actual button click signal. I'd start investigating from that slot anyway - there should be some hint in it. Probably a class is starting a thread internally or something like this. The other option is that someone is calling QCoreAppliction::processEvents intermittently to process the pending events, as already mentioned by @sierdzio, then you will actually need to find the code doing it and act accordingly. My best advice is to get into step-by-step debugging and inspect what's called where in that signal handler, it may be quite a lot of work, but I don't know of any quick and easy way.

                  As a "workaround", you could disable the button after it being clicked, and reenable it whenever the long operation has completed, so you don't get the problem with calling the heavy code twice.

                  JonBJ Offline
                  JonBJ Offline
                  JonB
                  wrote on last edited by JonB
                  #11

                  @kshegunov
                  Thanks, I will investigate.

                  One more time, as nobody has said "yay" or "nay", do you think making one of the QSql... calls to access the database spins a thread/event loop which could be the cause???

                  As a "workaround", you could disable the button after it being clicked, and reenable it whenever the long operation has completed, so you don't get the problem with calling the heavy code twice.

                  As I said, I have already done this. Better than nothing. But not robust enough, as there are many other widgets on the window which user could interact with... And even is they do not re-invoke the long-running code, I cannot allow any other code to execute while one piece is already executing, for all the obvious reasons...

                  kshegunovK 1 Reply Last reply
                  0
                  • JonBJ JonB

                    @kshegunov
                    Thanks, I will investigate.

                    One more time, as nobody has said "yay" or "nay", do you think making one of the QSql... calls to access the database spins a thread/event loop which could be the cause???

                    As a "workaround", you could disable the button after it being clicked, and reenable it whenever the long operation has completed, so you don't get the problem with calling the heavy code twice.

                    As I said, I have already done this. Better than nothing. But not robust enough, as there are many other widgets on the window which user could interact with... And even is they do not re-invoke the long-running code, I cannot allow any other code to execute while one piece is already executing, for all the obvious reasons...

                    kshegunovK Offline
                    kshegunovK Offline
                    kshegunov
                    Moderators
                    wrote on last edited by
                    #12

                    @JonB said in GUI event blocking:

                    do you think making one of the QSql... calls to access the database spins a thread/event loop which could be the cause???

                    Nope, I don't. As far as I have worked and looked at the SQL classes none of them (drivers) does use threading and/or event loops.

                    I cannot allow any other code to execute while one piece is already executing, for all the obvious reasons...

                    Then another option is to "quick-fix" it by setting a boolean flag just before the long operation starts, if it's raised then just quick return and do nothing, otherwise raise it and do the crunching. Whenever the operation's done, just lower the flag.

                    Read and abide by the Qt Code of Conduct

                    JonBJ 1 Reply Last reply
                    1
                    • kshegunovK kshegunov

                      @JonB said in GUI event blocking:

                      do you think making one of the QSql... calls to access the database spins a thread/event loop which could be the cause???

                      Nope, I don't. As far as I have worked and looked at the SQL classes none of them (drivers) does use threading and/or event loops.

                      I cannot allow any other code to execute while one piece is already executing, for all the obvious reasons...

                      Then another option is to "quick-fix" it by setting a boolean flag just before the long operation starts, if it's raised then just quick return and do nothing, otherwise raise it and do the crunching. Whenever the operation's done, just lower the flag.

                      JonBJ Offline
                      JonBJ Offline
                      JonB
                      wrote on last edited by JonB
                      #13

                      @kshegunov

                      Thanks for the heads-up on QSql calls not being the cause.

                      I cannot allow any other code to execute while one piece is already executing, for all the obvious reasons...

                      Then another option is to "quick-fix" it by setting a boolean flag just before the long operation starts, if it's raised then just quick return and do nothing, otherwise raise it and do the crunching. Whenever the operation's done, just lower the flag.

                      That's what I'm saying I've done. But I think (politely) you're missing the point on re-entrancy. You're concentrating only on preventing the long-runner from being re-invoked. Yes, I do that. But you need to think the other way round as well. While something is running, no other action is safe to perform. You mustn't, for example, close the dialog, or even request something from the database, or anything else you can think of, in response to a new UI action (let's pretend the window has 100 other buttons/views/widgets/tooltips/menus which do goodness knows what; I don't want to go through 100 functions testing for flag), because we don't know what state the application, or the database, or anything else, is in, until long-runner completes. Which is what we would get if clicking to run long-runner in the first place blocked, as it's "supposed" to.... And that's what I want to get back to.

                      kshegunovK 1 Reply Last reply
                      0
                      • JonBJ JonB

                        @kshegunov

                        Thanks for the heads-up on QSql calls not being the cause.

                        I cannot allow any other code to execute while one piece is already executing, for all the obvious reasons...

                        Then another option is to "quick-fix" it by setting a boolean flag just before the long operation starts, if it's raised then just quick return and do nothing, otherwise raise it and do the crunching. Whenever the operation's done, just lower the flag.

                        That's what I'm saying I've done. But I think (politely) you're missing the point on re-entrancy. You're concentrating only on preventing the long-runner from being re-invoked. Yes, I do that. But you need to think the other way round as well. While something is running, no other action is safe to perform. You mustn't, for example, close the dialog, or even request something from the database, or anything else you can think of, in response to a new UI action (let's pretend the window has 100 other buttons/views/widgets/tooltips/menus which do goodness knows what; I don't want to go through 100 functions testing for flag), because we don't know what state the application, or the database, or anything else, is in, until long-runner completes. Which is what we would get if clicking to run long-runner in the first place blocked, as it's "supposed" to.... And that's what I want to get back to.

                        kshegunovK Offline
                        kshegunovK Offline
                        kshegunov
                        Moderators
                        wrote on last edited by
                        #14

                        @JonB said in GUI event blocking:

                        But I think (politely) you're missing the point on re-entrancy.

                        Probably, but then again I don't have the code at hand, so I'm just more or less guessing. Your point seems warranted though, so the only thing I can add here is: have a light and easy debugging. :)

                        Read and abide by the Qt Code of Conduct

                        JonBJ 1 Reply Last reply
                        1
                        • kshegunovK kshegunov

                          @JonB said in GUI event blocking:

                          But I think (politely) you're missing the point on re-entrancy.

                          Probably, but then again I don't have the code at hand, so I'm just more or less guessing. Your point seems warranted though, so the only thing I can add here is: have a light and easy debugging. :)

                          JonBJ Offline
                          JonBJ Offline
                          JonB
                          wrote on last edited by JonB
                          #15

                          @kshegunov
                          Thanks; as I said, my comment was intended "politely". I have tried to clarify the situation in previous post. Once a single UI operation is underway, it's not safe to allow any other one to proceed, unless you've written code in a very particular way. And I didn't even write this code, let alone know just what it does!

                          1 Reply Last reply
                          1
                          • JonBJ JonB

                            @ambershark , and others

                            I now understand that "normal" Qt processing should be "block-until-return-from-click-handler". (Which is a relief to my general handling of other aspects of UI!)

                            However, in the case of the code (large, spaghetti, and completely uncommented) I am looking at, here is what I experience:

                            1. Click button to set off large "computation" of some kind. It's going to take, say, 20 seconds to complete.

                            2. Wait, say, a couple of seconds for it to "get going". (Don't know if this is required; there's obviously some delay between clicks anyway.)

                            3. Click that button again, or any other button/widget on the "stacked widget page".

                            4. Code immediately starts executing in response to this new UI action. Verified in debugger and/or messages.

                            The question for me is: what might it be in the original code processing which is allowing the second UI interaction to proceed? I need to track it down. To the best of my knowledge the code does not explicitly do things like create threads or process an event loop. It does, however, do database querying, which is why I keep asking if that's enough to cause the re-entrancy?

                            I know all about progress bars and blocking flags. Indeed, with a blocking flag, the blocking flag immediately gets hit in #4, proving the point, but not robust enough as you say, e.g. does not cover clicking any other widget in UI.

                            The code is so monolithic & interdependent that, say, commenting out sections to see if behaviour altered is difficult/impossible. So I don't know how to approach debugging to discover what this massive block of unintelligible code is doing which allows this re-entrancy, so that I can deal with it properly.

                            Can anyone suggest a technique (Python debugging, I'm afraid, not native C++) to aid me in tracking down where the re-entrancy is coming from?

                            A Offline
                            A Offline
                            ambershark
                            wrote on last edited by ambershark
                            #16

                            @JonB said in GUI event blocking:

                            The question for me is: what might it be in the original code processing which is allowing the second UI interaction to proceed?

                            Well there are really only 2 things that can do this. Threading and events. So either the signal handler for the click is exiting after sending events or it's exiting after signalling a thread.

                            To be honest it should be pretty easy to debug. Just point a breakpoint in the signal handler for that click event, then step through it. Step over function calls and test how long they take. If everything exits quickly (what I'm guessing will happen) and the signal handler exits, before your data is processed, then you just start stepping in to each of the functions in that click handler until you see what they are doing.

                            At some point you will find one that signals something to do some work and exits (via thread or event). That is your problem. Address that area and you should be good to go. I would just use something easy like a modal progress dialog that locks up the gui automatically until you signal it to quit when you get back from the processing of the "click".

                            Oh and it always sucks working on someone else's code.. Especially confusing/bad code. When I get in messy code I use a tool called SourceTrail to navigate code.

                            https://www.sourcetrail.com

                            My L-GPL'd C++ Logger github.com/ambershark-mike/sharklog

                            JonBJ 1 Reply Last reply
                            3
                            • A ambershark

                              @JonB said in GUI event blocking:

                              The question for me is: what might it be in the original code processing which is allowing the second UI interaction to proceed?

                              Well there are really only 2 things that can do this. Threading and events. So either the signal handler for the click is exiting after sending events or it's exiting after signalling a thread.

                              To be honest it should be pretty easy to debug. Just point a breakpoint in the signal handler for that click event, then step through it. Step over function calls and test how long they take. If everything exits quickly (what I'm guessing will happen) and the signal handler exits, before your data is processed, then you just start stepping in to each of the functions in that click handler until you see what they are doing.

                              At some point you will find one that signals something to do some work and exits (via thread or event). That is your problem. Address that area and you should be good to go. I would just use something easy like a modal progress dialog that locks up the gui automatically until you signal it to quit when you get back from the processing of the "click".

                              Oh and it always sucks working on someone else's code.. Especially confusing/bad code. When I get in messy code I use a tool called SourceTrail to navigate code.

                              https://www.sourcetrail.com

                              JonBJ Offline
                              JonBJ Offline
                              JonB
                              wrote on last edited by JonB
                              #17

                              @ambershark
                              OK, now had a chance to break and examine stack.

                              Nobody said this code would be easy...

                              The "long running operation" involves producing HTML, and saving to PDF file (think of it like a report). I do this via QWebEngineView (partly because this is in shared code, at other times the generated HTML is displayed to the user and is editable prior to producing the PDF). In this case the view is never displayed interactively to the user, but it still follows that route.

                              Because QWebEngineView is asynchronous (its own thread, I believe), and here we need the HTML/PDF synchronously, before we can save the PDF to file we must await QWebEngineView finished loading (else we get empty PDF saved to file). I have code there like:

                                  def synchronousRenderHtml(self):
                                      # see synchronousWebViewLoaded() below
                                      self.rendered = False
                                      self.webView.loadFinished.connect(self.synchronousWebViewLoaded)
                                      #
                                      # see http://doc.qt.io/qt-5/qtwebenginewidgets-qtwebkitportingguide.html
                                      # because setHtml() is asynchronous, the call to make editable has to be moved into synchronousWebViewLoaded()
                                      htmlFile = filesystemfunctions.findFileInDataOrCodeDirectory(Paths.s166Html())
                                      self.webView.setHtml(self.html, QUrl.fromLocalFile(str(htmlFile)))
                                      #
                                      # see synchronousWebViewLoaded() below
                                      if not self.rendered:
                                          self.renderLoop.exec()
                              
                                  def synchronousWebViewLoaded(self):
                                      # This function is called on self.webView.loadFinished() from synchronousRenderHtml() above
                                      # The code uses this because self.webView.setHtml() is asynchronous
                                      # so in effect this implements a "blocking" call (with event processing) in synchronousRenderHtml()
                                      # This is the only place in code where a QEventLoop is used explicitly
                                      # It's all very complicated, and I did wish this behaviour was not needed
                                      # but it seems, at minimum in the case where this dialog is used non-interactively, it is :(
                                      self.qWebEngineView.page().runJavaScript("document.documentElement.contentEditable = true")
                                      self.rendered = True
                                      # cause the self.renderLoop.exec() in synchronousRenderHtml() above to exit now
                                      self.renderLoop.quit()
                              

                              Turns out, the re-entrant call for the top-level button being pressed a second time is coming from the self.renderLoop.exec() (which is awaiting the self.renderLoop.quit()).

                              Cutting a long story short, unless someone can suggest a better way than this self.renderLoop.exec() principle(?), I think I have to stick with that. Hence the button re-entrancy, and I'll have to live with the need to using a re-entrancy blocking flag....

                              kshegunovK 1 Reply Last reply
                              0
                              • JonBJ JonB

                                @ambershark
                                OK, now had a chance to break and examine stack.

                                Nobody said this code would be easy...

                                The "long running operation" involves producing HTML, and saving to PDF file (think of it like a report). I do this via QWebEngineView (partly because this is in shared code, at other times the generated HTML is displayed to the user and is editable prior to producing the PDF). In this case the view is never displayed interactively to the user, but it still follows that route.

                                Because QWebEngineView is asynchronous (its own thread, I believe), and here we need the HTML/PDF synchronously, before we can save the PDF to file we must await QWebEngineView finished loading (else we get empty PDF saved to file). I have code there like:

                                    def synchronousRenderHtml(self):
                                        # see synchronousWebViewLoaded() below
                                        self.rendered = False
                                        self.webView.loadFinished.connect(self.synchronousWebViewLoaded)
                                        #
                                        # see http://doc.qt.io/qt-5/qtwebenginewidgets-qtwebkitportingguide.html
                                        # because setHtml() is asynchronous, the call to make editable has to be moved into synchronousWebViewLoaded()
                                        htmlFile = filesystemfunctions.findFileInDataOrCodeDirectory(Paths.s166Html())
                                        self.webView.setHtml(self.html, QUrl.fromLocalFile(str(htmlFile)))
                                        #
                                        # see synchronousWebViewLoaded() below
                                        if not self.rendered:
                                            self.renderLoop.exec()
                                
                                    def synchronousWebViewLoaded(self):
                                        # This function is called on self.webView.loadFinished() from synchronousRenderHtml() above
                                        # The code uses this because self.webView.setHtml() is asynchronous
                                        # so in effect this implements a "blocking" call (with event processing) in synchronousRenderHtml()
                                        # This is the only place in code where a QEventLoop is used explicitly
                                        # It's all very complicated, and I did wish this behaviour was not needed
                                        # but it seems, at minimum in the case where this dialog is used non-interactively, it is :(
                                        self.qWebEngineView.page().runJavaScript("document.documentElement.contentEditable = true")
                                        self.rendered = True
                                        # cause the self.renderLoop.exec() in synchronousRenderHtml() above to exit now
                                        self.renderLoop.quit()
                                

                                Turns out, the re-entrant call for the top-level button being pressed a second time is coming from the self.renderLoop.exec() (which is awaiting the self.renderLoop.quit()).

                                Cutting a long story short, unless someone can suggest a better way than this self.renderLoop.exec() principle(?), I think I have to stick with that. Hence the button re-entrancy, and I'll have to live with the need to using a re-entrancy blocking flag....

                                kshegunovK Offline
                                kshegunovK Offline
                                kshegunov
                                Moderators
                                wrote on last edited by kshegunov
                                #18

                                @JonB said in GUI event blocking:

                                Cutting a long story short, unless someone can suggest a better way than this self.renderLoop.exec() principle(?),

                                Not in this particular case. But I have a suggestion:
                                Grab the mouse and keyboard in synchronousRenderHtml and discard those events in your event() override while self.rendered is false and you're owning the user input. In synchronousWebViewLoaded release back the mouse and keyboard. Tread carefully though, as grabbing the mouse and keyboard can make your system unusable (i.e. use -nograb with gdb to prevent you from cursing). ;)

                                Read and abide by the Qt Code of Conduct

                                JonBJ 1 Reply Last reply
                                4
                                • kshegunovK kshegunov

                                  @JonB said in GUI event blocking:

                                  Cutting a long story short, unless someone can suggest a better way than this self.renderLoop.exec() principle(?),

                                  Not in this particular case. But I have a suggestion:
                                  Grab the mouse and keyboard in synchronousRenderHtml and discard those events in your event() override while self.rendered is false and you're owning the user input. In synchronousWebViewLoaded release back the mouse and keyboard. Tread carefully though, as grabbing the mouse and keyboard can make your system unusable (i.e. use -nograb with gdb to prevent you from cursing). ;)

                                  JonBJ Offline
                                  JonBJ Offline
                                  JonB
                                  wrote on last edited by JonB
                                  #19

                                  @kshegunov
                                  I did think about that "dirty" way of handling things.

                                  However (apart from the debugging issue, I use Python + PyCharm debugger, not gdb, and I wouldn't hold your breath that it will have a -nograb functionality!), the problem will be which "event() override" you have in mind.

                                  Because, as I said, the QWebEngineView, which produces the HTML/PDF asynchronously and has the synchronousRenderHtml() call, is not displayed to the user in this case, the UI events are being received to the original dialog where a button is pressed to produce the output. This in itself has no connection to the ("invisible") QWebEngineView --- it doesn't even know one is being used, the implementation is invisible to it. I would have to modify its event handling. And since that would break encapsulation, I really don't feel it would be a good idea to implement!

                                  So I will soldier on with the "blocking flag" I currently have to prevent "re-entrancy", which at least works reasonably well in practice. At least I now understand why this whole behaviour is caused.

                                  kshegunovK 1 Reply Last reply
                                  0
                                  • JonBJ JonB

                                    @kshegunov
                                    I did think about that "dirty" way of handling things.

                                    However (apart from the debugging issue, I use Python + PyCharm debugger, not gdb, and I wouldn't hold your breath that it will have a -nograb functionality!), the problem will be which "event() override" you have in mind.

                                    Because, as I said, the QWebEngineView, which produces the HTML/PDF asynchronously and has the synchronousRenderHtml() call, is not displayed to the user in this case, the UI events are being received to the original dialog where a button is pressed to produce the output. This in itself has no connection to the ("invisible") QWebEngineView --- it doesn't even know one is being used, the implementation is invisible to it. I would have to modify its event handling. And since that would break encapsulation, I really don't feel it would be a good idea to implement!

                                    So I will soldier on with the "blocking flag" I currently have to prevent "re-entrancy", which at least works reasonably well in practice. At least I now understand why this whole behaviour is caused.

                                    kshegunovK Offline
                                    kshegunovK Offline
                                    kshegunov
                                    Moderators
                                    wrote on last edited by
                                    #20

                                    @JonB said in GUI event blocking:

                                    the problem will be which "event() override" you have in mind.

                                    That would be the event() override of the class (widget) in which you grab the input. When you grab the input events all of them are redirected to the widget that called the mentioned functions, so you'd need to filter them in that same class. Basically, you grab the input in the widget/dialog/w/e and filter the input there. Whenever the operation's done (i.e. you connect the finished signal to a slot in that same dialog you release the mouse/keyboard.

                                    Read and abide by the Qt Code of Conduct

                                    JonBJ 1 Reply Last reply
                                    1
                                    • kshegunovK kshegunov

                                      @JonB said in GUI event blocking:

                                      the problem will be which "event() override" you have in mind.

                                      That would be the event() override of the class (widget) in which you grab the input. When you grab the input events all of them are redirected to the widget that called the mentioned functions, so you'd need to filter them in that same class. Basically, you grab the input in the widget/dialog/w/e and filter the input there. Whenever the operation's done (i.e. you connect the finished signal to a slot in that same dialog you release the mouse/keyboard.

                                      JonBJ Offline
                                      JonBJ Offline
                                      JonB
                                      wrote on last edited by JonB
                                      #21

                                      @kshegunov
                                      Sorry, my friend, but as per the explanation I wrote above, I still don't understand which widget's events you mean.

                                      Here is the architecture of how the code works:

                                      • The user starts from a dialog, which has buttons on it, like Run long-running report.
                                      • That button on that dialog makes call produceReportAndWaitForFinish().
                                      • Behind the scenes, produceReportAndWaitForFinish() goes: create invisible QWebEnginePage and await final HTML/PDF returned, invisible QWebEnginePage calls synchronousRenderHtml(), which in turn has the self.renderLoop.exec() & self.renderLoop.quit() inside itself (the QWebEnginePage), in order to produce the HTML/PDF.
                                      • You want me to place the "event filter" there (inside the QWebEnginePage), when I call self.renderLoop.exec().
                                      • However, I don't think that (the QWebEnginePage) receives any inputs. The inputs (which would cause re-entrancy) are directed to buttons on the original top-level dialog where the user originally clicked the Run long-running report button, which I don't want him to click again.

                                      If that is correct(?), that is why I don't want to fiddle inside QWebEnginePage with the events directed to the top-level dialog, because of encapsulation/separation of code. The QWebEnginePage does not know if it was invoked from a dialog in the first place, and the dialog has no idea that a QWebEnginePage (which will do threads/events) is involved in the production of the HTML/PDF.

                                      kshegunovK 1 Reply Last reply
                                      0
                                      • JonBJ JonB

                                        @kshegunov
                                        Sorry, my friend, but as per the explanation I wrote above, I still don't understand which widget's events you mean.

                                        Here is the architecture of how the code works:

                                        • The user starts from a dialog, which has buttons on it, like Run long-running report.
                                        • That button on that dialog makes call produceReportAndWaitForFinish().
                                        • Behind the scenes, produceReportAndWaitForFinish() goes: create invisible QWebEnginePage and await final HTML/PDF returned, invisible QWebEnginePage calls synchronousRenderHtml(), which in turn has the self.renderLoop.exec() & self.renderLoop.quit() inside itself (the QWebEnginePage), in order to produce the HTML/PDF.
                                        • You want me to place the "event filter" there (inside the QWebEnginePage), when I call self.renderLoop.exec().
                                        • However, I don't think that (the QWebEnginePage) receives any inputs. The inputs (which would cause re-entrancy) are directed to buttons on the original top-level dialog where the user originally clicked the Run long-running report button, which I don't want him to click again.

                                        If that is correct(?), that is why I don't want to fiddle inside QWebEnginePage with the events directed to the top-level dialog, because of encapsulation/separation of code. The QWebEnginePage does not know if it was invoked from a dialog in the first place, and the dialog has no idea that a QWebEnginePage (which will do threads/events) is involved in the production of the HTML/PDF.

                                        kshegunovK Offline
                                        kshegunovK Offline
                                        kshegunov
                                        Moderators
                                        wrote on last edited by
                                        #22

                                        @JonB said in GUI event blocking:

                                        If that is correct

                                        Nope. I mean exactly the dialog with the buttons. See (untested) code below, which I hope will clear this up:

                                        class DialogWithButtons : public QDialog
                                        {
                                        public:
                                            DialogWithButtons(QWidget * parent);
                                        
                                        public slots:
                                            void produceReportAndWaitForFinish();
                                            void reportProduced();
                                        
                                        protected:
                                            bool event(QEvent *) override;
                                        private:
                                            QPushButton startLongRunningOpButton;
                                            bool inputBlocked;
                                        }
                                        
                                        DialogWithButtons::DialogWithButtons(QWidget * parent)
                                            : QWidget(parent), startLongRunningOpButton(this), inputBlocked(false)
                                        {
                                            // Just connect the button
                                            QObject::connect(&startLongRunningOpButton, &QPushButton::clicked, this, &DialogWithButtons::produceReportAndWaitForFinish);
                                        }
                                        
                                        void DialogWithButtons::produceReportAndWaitForFinish()
                                        {
                                            inputBlocked = true;
                                            grabMouse();
                                            grabKeyboard();
                                        
                                            // Create/Init the webengine and so on, put to render and all that goodness. 
                                            QWebEngineView webEngine;
                                        
                                            // Connect the handlers & QEventLoop blocking
                                            QObject::connect(&webEngine, &QWebEngineView::loadFinished, this, &DialogWithButtons::reportProduced);
                                        
                                            QEventLoop loop;
                                            QObject::connect(&webEngine, &QWebEngineView::loadFinished, &loop, &QEventLoop::quit);
                                            loop.exec();   // Wait for processing to finish
                                        }
                                        
                                        void DialogWithButtons::reportProduced()
                                        {
                                             releaseMouse();
                                             releaseKeyboard();
                                             inputBlocked = false;
                                        }
                                        
                                        bool DialogWithButtons::event(QEvent * e)
                                        {
                                            if (inputBlocked && dynamic_cast<QInputEvent *>(e))
                                                return true;   // Filter out input events - we receive all of them if `inputBlocked` is true
                                         
                                            return QDialog::event(e);  // Input was not blocked or event was not UI input - delegate to the default implementation
                                        }
                                        

                                        Read and abide by the Qt Code of Conduct

                                        JonBJ 1 Reply Last reply
                                        3
                                        • kshegunovK kshegunov

                                          @JonB said in GUI event blocking:

                                          If that is correct

                                          Nope. I mean exactly the dialog with the buttons. See (untested) code below, which I hope will clear this up:

                                          class DialogWithButtons : public QDialog
                                          {
                                          public:
                                              DialogWithButtons(QWidget * parent);
                                          
                                          public slots:
                                              void produceReportAndWaitForFinish();
                                              void reportProduced();
                                          
                                          protected:
                                              bool event(QEvent *) override;
                                          private:
                                              QPushButton startLongRunningOpButton;
                                              bool inputBlocked;
                                          }
                                          
                                          DialogWithButtons::DialogWithButtons(QWidget * parent)
                                              : QWidget(parent), startLongRunningOpButton(this), inputBlocked(false)
                                          {
                                              // Just connect the button
                                              QObject::connect(&startLongRunningOpButton, &QPushButton::clicked, this, &DialogWithButtons::produceReportAndWaitForFinish);
                                          }
                                          
                                          void DialogWithButtons::produceReportAndWaitForFinish()
                                          {
                                              inputBlocked = true;
                                              grabMouse();
                                              grabKeyboard();
                                          
                                              // Create/Init the webengine and so on, put to render and all that goodness. 
                                              QWebEngineView webEngine;
                                          
                                              // Connect the handlers & QEventLoop blocking
                                              QObject::connect(&webEngine, &QWebEngineView::loadFinished, this, &DialogWithButtons::reportProduced);
                                          
                                              QEventLoop loop;
                                              QObject::connect(&webEngine, &QWebEngineView::loadFinished, &loop, &QEventLoop::quit);
                                              loop.exec();   // Wait for processing to finish
                                          }
                                          
                                          void DialogWithButtons::reportProduced()
                                          {
                                               releaseMouse();
                                               releaseKeyboard();
                                               inputBlocked = false;
                                          }
                                          
                                          bool DialogWithButtons::event(QEvent * e)
                                          {
                                              if (inputBlocked && dynamic_cast<QInputEvent *>(e))
                                                  return true;   // Filter out input events - we receive all of them if `inputBlocked` is true
                                           
                                              return QDialog::event(e);  // Input was not blocked or event was not UI input - delegate to the default implementation
                                          }
                                          
                                          JonBJ Offline
                                          JonBJ Offline
                                          JonB
                                          wrote on last edited by JonB
                                          #23

                                          @kshegunov
                                          Yes, OK, I thought so.

                                          The problem is, my produceReportAndWaitForFinish() in the top-level dialog does not have any kind of

                                              // Create/Init the webengine and so on, put to render and all that goodness. 
                                              QWebEngineView webEngine;
                                          

                                          inside it (and I don't want it to). It has more like:

                                              //  Call *completely opaque* ReportProducer to generate HTML/PDF
                                              //  Here we have *no knowledge* of how ReportProducer functions
                                              //  and to maintain code separation/encapsulation we do not want to know here
                                              //  We might completely change how ReportProducer works at any time
                                              //  without affecting any code/behaviour here in this dialog.
                                              ReportProducer rp;
                                              rp.doWhateverToProduceHtmlAndPdf()
                                          

                                          So I do understand what your approach requires, but I hope you can see why I do not wish to go down that route. But thanks for your suggestion anyway!

                                          kshegunovK 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