QML Button onClicked() - cannot open Dialog/PopUp until function finishes?
-
Hi!
I have a fairly simple question that I surprisingly have not been able to find a lot of online help for.
Basically, I have a Button in QML that onClicked(), I would like it to:
- open a Dialog or PopUp and then
- run a fairly CPU-intensive function in Python.
However, I can never get the Dialog or PopUp to display until after the function finishes. I have tried moving the function to a new thread, but to no avail. Has anyone else run into this problem before, and have an idea how to solve? My code is as below:
**main.qml
RoundButton{...//Button design details onClicked: { progressBarDialog.open() //this is the name of the Dialog window I'm trying to open as soon as the button is pressed var checkedTuple=['algoOne', 'algoTwo', 'algoThree'] bridge.start_run_function(checkedTuple) //this calls my threaded function under the Bridge class mainLoader.source = "pages/report.qml"; // Load new page after function runs } }
**main.py #this file houses all my python functions under the Bridge class
@QmlElement class Bridge(QObject): @Slot(str, result=str) //creates a new thread to run the function def start_run_function(self,checkedTuple): self.running = False if not self.running: self.running = True thread = threading.Thread(target=self.runFunction(checkedTuple)) thread.start() @Slot(str, result=str) def runFunction(self, checkedTuple): //complicated algorithm that takes a minute to run, but works fine. Ultimately I want this function to run after the Dialog/PopUp is first displayed/visible.
This issue occurs whether or not I use a new thread when running
runFunction()
. Please note that I was able to get the pop-up window to display correctly first using QT Widgets, but that is a very different system from QML.Thanks in advance,
-
Hi!
I have a fairly simple question that I surprisingly have not been able to find a lot of online help for.
Basically, I have a Button in QML that onClicked(), I would like it to:
- open a Dialog or PopUp and then
- run a fairly CPU-intensive function in Python.
However, I can never get the Dialog or PopUp to display until after the function finishes. I have tried moving the function to a new thread, but to no avail. Has anyone else run into this problem before, and have an idea how to solve? My code is as below:
**main.qml
RoundButton{...//Button design details onClicked: { progressBarDialog.open() //this is the name of the Dialog window I'm trying to open as soon as the button is pressed var checkedTuple=['algoOne', 'algoTwo', 'algoThree'] bridge.start_run_function(checkedTuple) //this calls my threaded function under the Bridge class mainLoader.source = "pages/report.qml"; // Load new page after function runs } }
**main.py #this file houses all my python functions under the Bridge class
@QmlElement class Bridge(QObject): @Slot(str, result=str) //creates a new thread to run the function def start_run_function(self,checkedTuple): self.running = False if not self.running: self.running = True thread = threading.Thread(target=self.runFunction(checkedTuple)) thread.start() @Slot(str, result=str) def runFunction(self, checkedTuple): //complicated algorithm that takes a minute to run, but works fine. Ultimately I want this function to run after the Dialog/PopUp is first displayed/visible.
This issue occurs whether or not I use a new thread when running
runFunction()
. Please note that I was able to get the pop-up window to display correctly first using QT Widgets, but that is a very different system from QML.Thanks in advance,
@robliou said in QML Button onClicked() - cannot open Dialog/PopUp until function finishes?:
Hi!
I have a fairly simple question that I surprisingly have not been able to find a lot of online help for.
Basically, I have a Button in QML that onClicked(), I would like it to:
- open a Dialog or PopUp and then
- run a fairly CPU-intensive function in Python.
However, I can never get the Dialog or PopUp to display until after the function finishes. I have tried moving the function to a new thread, but to no avail. Has anyone else run into this problem before, and have an idea how to solve? My code is as below:
So far, this sounds like the expected behavior. The Qt event loop is blocked as long as application code is running in the UI thread. There are caveats with nested event loops or explicit processing, but those involve more caveats that are easiest to handle by avoiding entirely.
Further, presuming cpython in early 2024, the global interpreter lock (GIL) implies that if one thread is executing python code, no other thread is.
**main.py #this file houses all my python functions under the Bridge class
@QmlElement class Bridge(QObject): @Slot(str, result=str) //creates a new thread to run the function def start_run_function(self,checkedTuple): self.running = False if not self.running: self.running = True
I was going to ignore the code entirely, but this sticks out as at least partially misplaced. Perhaps
self.running = False
is supposed to be in the constructor.Please note that I was able to get the pop-up window to display correctly first using QT Widgets, but that is a very different system from QML.
This is the interesting part. Is the Widgets implementation more or less free of python code? If so, this is the expected behavior. If not, or perhaps regardless, a comparison could be fruitful.
-
@robliou said in QML Button onClicked() - cannot open Dialog/PopUp until function finishes?:
Hi!
I have a fairly simple question that I surprisingly have not been able to find a lot of online help for.
Basically, I have a Button in QML that onClicked(), I would like it to:
- open a Dialog or PopUp and then
- run a fairly CPU-intensive function in Python.
However, I can never get the Dialog or PopUp to display until after the function finishes. I have tried moving the function to a new thread, but to no avail. Has anyone else run into this problem before, and have an idea how to solve? My code is as below:
So far, this sounds like the expected behavior. The Qt event loop is blocked as long as application code is running in the UI thread. There are caveats with nested event loops or explicit processing, but those involve more caveats that are easiest to handle by avoiding entirely.
Further, presuming cpython in early 2024, the global interpreter lock (GIL) implies that if one thread is executing python code, no other thread is.
**main.py #this file houses all my python functions under the Bridge class
@QmlElement class Bridge(QObject): @Slot(str, result=str) //creates a new thread to run the function def start_run_function(self,checkedTuple): self.running = False if not self.running: self.running = True
I was going to ignore the code entirely, but this sticks out as at least partially misplaced. Perhaps
self.running = False
is supposed to be in the constructor.Please note that I was able to get the pop-up window to display correctly first using QT Widgets, but that is a very different system from QML.
This is the interesting part. Is the Widgets implementation more or less free of python code? If so, this is the expected behavior. If not, or perhaps regardless, a comparison could be fruitful.
Hello Jeremy, thank you for taking the time to reply.
So far, this sounds like the expected behavior. The Qt event loop is blocked as long as application code is running in the UI thread.
OK, makes sense, but surely when i runprogressBarDialog.open()
this isn't blocking the GUI event loop, or is it? Isn't opening a dialog/ popup just a local event?Even if it is blocking, I am opening this dialog window before running the function, so why doesn't it just show up first before the function runs?
Secondly, the fact that I used QThread to run
run_function
should mean that a new thread is now running this function, so there should be no interference with the GUI event loop?I was going to ignore the code entirely, but this sticks out as at least partially misplaced
You are correct, this belongs in the constructor. I just temporarily put it here for testing/expediency purposes, but I don't think it affects the outcome of our output here.This is the interesting part. Is the Widgets implementation more or less free of python code? If so, this is the expected behavior. If not, or perhaps regardless, a comparison could be fruitful.
I took another look at my QT Widgets code on how I achieved proper functioning.
In my Widgets implementation, I used a combination of Python and QT Widgets. Here is the process I used to get the progress bar to display correctly upon clicking a button:
- in my main.py file, I created a
text_browser
that contains the progress bar - created a function called
show_progress_bar
that renders thetext_browser
visible when called (i.e.self.show_text_browser()
) - created a function called
submit_clicked()
that, when the button is clicked, first:
a) runsshow_progress_bar
to make the progress bar visible, then
b) callsrun_function
which, in a new thread, runs the complex Python function
This code seems to work perfectly, with the progress bar accurately picking up signals that are emitted as
run_function
proceeds.Unfortunately, I am trying to achieve the same effect with QML but frustratingly cannot seem to do it. Any ideas or assistance you could provide would be much appreciated.
Thank you,
-
Hello Jeremy, thank you for taking the time to reply.
So far, this sounds like the expected behavior. The Qt event loop is blocked as long as application code is running in the UI thread.
OK, makes sense, but surely when i runprogressBarDialog.open()
this isn't blocking the GUI event loop, or is it? Isn't opening a dialog/ popup just a local event?Even if it is blocking, I am opening this dialog window before running the function, so why doesn't it just show up first before the function runs?
Secondly, the fact that I used QThread to run
run_function
should mean that a new thread is now running this function, so there should be no interference with the GUI event loop?I was going to ignore the code entirely, but this sticks out as at least partially misplaced
You are correct, this belongs in the constructor. I just temporarily put it here for testing/expediency purposes, but I don't think it affects the outcome of our output here.This is the interesting part. Is the Widgets implementation more or less free of python code? If so, this is the expected behavior. If not, or perhaps regardless, a comparison could be fruitful.
I took another look at my QT Widgets code on how I achieved proper functioning.
In my Widgets implementation, I used a combination of Python and QT Widgets. Here is the process I used to get the progress bar to display correctly upon clicking a button:
- in my main.py file, I created a
text_browser
that contains the progress bar - created a function called
show_progress_bar
that renders thetext_browser
visible when called (i.e.self.show_text_browser()
) - created a function called
submit_clicked()
that, when the button is clicked, first:
a) runsshow_progress_bar
to make the progress bar visible, then
b) callsrun_function
which, in a new thread, runs the complex Python function
This code seems to work perfectly, with the progress bar accurately picking up signals that are emitted as
run_function
proceeds.Unfortunately, I am trying to achieve the same effect with QML but frustratingly cannot seem to do it. Any ideas or assistance you could provide would be much appreciated.
Thank you,
@jeremy_k Wondering if anyone else has any ideas on this? I tried implementing my multithreading using this method:
https://www.pythonguis.com/tutorials/multithreading-pyside6-applications-qthreadpool/
and the worker threads seem to run OK (as in, they run successfully from the Worker thread), but they still seem to be blocking on my GUI event loop for some reason?
- in my main.py file, I created a
-
I suspect that QML, not Python, is causing the problem. The GUI locks when you try to both do something in the main GUI event loop (i.e. open a QML popup window) and also run a Python function, as triggered from a QML event (i.e. clicking a button). QML seems to automatically lock the main event loop, even if the Python function is called using a separate thread.
Meanwhile, if one were to do all of this completely within Python (ie using Python + QT Widgets + Qthread), the problem goes away and everything works fine.
Isn't that strange?
-
Hello Jeremy, thank you for taking the time to reply.
So far, this sounds like the expected behavior. The Qt event loop is blocked as long as application code is running in the UI thread.
OK, makes sense, but surely when i runprogressBarDialog.open()
this isn't blocking the GUI event loop, or is it? Isn't opening a dialog/ popup just a local event?Even if it is blocking, I am opening this dialog window before running the function, so why doesn't it just show up first before the function runs?
Secondly, the fact that I used QThread to run
run_function
should mean that a new thread is now running this function, so there should be no interference with the GUI event loop?I was going to ignore the code entirely, but this sticks out as at least partially misplaced
You are correct, this belongs in the constructor. I just temporarily put it here for testing/expediency purposes, but I don't think it affects the outcome of our output here.This is the interesting part. Is the Widgets implementation more or less free of python code? If so, this is the expected behavior. If not, or perhaps regardless, a comparison could be fruitful.
I took another look at my QT Widgets code on how I achieved proper functioning.
In my Widgets implementation, I used a combination of Python and QT Widgets. Here is the process I used to get the progress bar to display correctly upon clicking a button:
- in my main.py file, I created a
text_browser
that contains the progress bar - created a function called
show_progress_bar
that renders thetext_browser
visible when called (i.e.self.show_text_browser()
) - created a function called
submit_clicked()
that, when the button is clicked, first:
a) runsshow_progress_bar
to make the progress bar visible, then
b) callsrun_function
which, in a new thread, runs the complex Python function
This code seems to work perfectly, with the progress bar accurately picking up signals that are emitted as
run_function
proceeds.Unfortunately, I am trying to achieve the same effect with QML but frustratingly cannot seem to do it. Any ideas or assistance you could provide would be much appreciated.
Thank you,
@robliou said in QML Button onClicked() - cannot open Dialog/PopUp until function finishes?:
Hello Jeremy, thank you for taking the time to reply.
So far, this sounds like the expected behavior. The Qt event loop is blocked as long as application code is running in the UI thread.
OK, makes sense, but surely when i runprogressBarDialog.open()
this isn't blocking the GUI event loop, or is it? Isn't opening a dialog/ popup just a local event?I don't know what's meant by a local event. In my toy test on macOs, Dialog.open() followed by a long-running operation still shows the dialog before becoming unresponsive. I don't know that this is required by the interface. It's not inconceivable to me that some windowing systems might require a working event loop to show a window.
toy.qml:
import QtQuick.Window import QtQuick.Dialogs Window { visible: true MessageDialog { id: dialog } MouseArea { anchors.fill: parent onClicked: { console.log("pre"); dialog.open(); for (var i = 0; i < 500000000; i++) ; console.log("post"); } } }
Secondly, the fact that I used QThread to run
run_function
should mean that a new thread is now running this function, so there should be no interference with the GUI event loop?It shouldn't interfere with the C++ event loop implementation. There will be contention for any python code run in an event handler, slot, etc.
This is the interesting part. Is the Widgets implementation more or less free of python code? If so, this is the expected behavior. If not, or perhaps regardless, a comparison could be fruitful.
I took another look at my QT Widgets code on how I achieved proper functioning.
I'm lost in the description. If you can share a terse, brief example, that might help. If not, it sounds like you understand the Python and Qt widgets fundamentals.
@robliou said in QML Button onClicked() - cannot open Dialog/PopUp until function finishes?:
I suspect that QML, not Python, is causing the problem. The GUI locks when you try to both do something in the main GUI event loop (i.e. open a QML popup window) and also run a Python function, as triggered from a QML event (i.e. clicking a button). QML seems to automatically lock the main event loop, even if the Python function is called using a separate thread.
Native code that is run from python (PySide and PyQt included) need to explicitly drop the global interpreter lock to allow other python threads to execute. It's possible that you've found a defect where this release is missing. The QML engine itself isn't likely to be involved. While a signal handler/QML event is running, that QML engine is blocking its thread's event loop.
If you can put together a minimal example, that would help for a bug report. And as usual, try the latest release to verify that the issue hasn't already been addressed.
- in my main.py file, I created a