QMessage in a function thread? [duplicate] - python

This question already has answers here:
In PyQt, what is the best way to share data between the main window and a thread
(1 answer)
Background thread with QThread in PyQt
(7 answers)
Example of the right way to use QThread in PyQt?
(3 answers)
Closed 3 months ago.
I want to do a popup when the function in a thread finish but when it run the popup the program crash. I tried doing a thread in the main function thread but crash the app.
I put a large and slow funtion in a thread to not crash the GUI but I want when this slow function finish, run a popup with QMessageBox, my sollution work but when I press the 'Ok' button in the popup crash the program, but I dont want that, so I tried to make a thread from the main thread but do the same, only crash the program.
I want something like this:
def popup():
msg = QMessageBox()
msg.setWindowTitle("Alert")
...
a = msg.exec_()
def slow_func():
time.sleep(10) # exemple of slow
popup() # when I press 'Ok' crash so I tried...
# I tried...
def slow_func():
time.sleep(10) # exemple of slow
threading.Thread(target=popup).start() # still crashing
threading.Thread(target=slow_func).start()
I don't know how to do it, I tried a thread in a thread but still crashing when I press 'Ok' button. The popup works but crash the app when I press 'Ok'
I'm in Windows 11 using python 3.10 and PyQt 5.15

You must not use any GUI functions outside of the main thread. So you can not call popup() from your calculation thread and you can't create a new thread in your calculation thread and have that call it. You must make the main thread call it.
For possible solutions see How to properly execute GUI operations in Qt main thread?
In short: Use the signal-slot-mechanism or QMetaObject::invokeMethod()

Related

How to easily avoid Tkinter freezing?

I developed a simple Python application doing some stuff, then I decided to add a simple GUI using Tkinter.
The problem is that, while the main function is doing its stuff, the window freezes.
I know it's a common problem and I've already read that I should use multithreads (very complicated, because the function updates the GUI too) or divide my code in different function, each one working for a little time.
Anyway I don't want to change my code for such a stupid application.
My question is: is it possible there's no easy way to update my Tkinter window every second? I just want to apply the KISS rule!
I'll give you a pseudo code example below that I tried but didn't work:
class Gui:
[...]#costructor and other stuff
def refresh(self):
self.root.update()
self.root.after(1000,self.refresh)
def start(self):
self.refresh()
doingALotOfStuff()
#outside
GUI = Gui(Tk())
GUI.mainloop()
It simply will execute refresh only once, and I cannot understand why.
Thanks a lot for your help.
Tkinter is in a mainloop. Which basically means it's constantly refreshing the window, waiting for buttons to be clicked, words to be typed, running callbacks, etc. When you run some code on the same thread that mainloop is on, then nothing else is going to perform on the mainloop until that section of code is done. A very simple workaround is spawning a long running process onto a separate thread. This will still be able to communicate with Tkinter and update it's GUI (for the most part).
Here's a simple example that doesn't drastically modify your psuedo code:
import threading
class Gui:
[...]#costructor and other stuff
def refresh(self):
self.root.update()
self.root.after(1000,self.refresh)
def start(self):
self.refresh()
threading.Thread(target=doingALotOfStuff).start()
#outside
GUI = Gui(Tk())
GUI.mainloop()
This answer goes into some detail on mainloop and how it blocks your code.
Here's another approach that goes over starting the GUI on it's own thread and then running different code after.
'Not responding' problem can be avoided using Multithreading in python using the thread module.
If you've defined any function, say combine() due to which the Tkinter window is freezing, then make another function to start combine() in background as shown below:
import threading
def combine():
...
def start_combine_in_bg():
threading.Thread(target=combine).start()
Now instead of calling combine(), you have to call start_combine_in_bg()
I did this and now my window is not freezing so I shared it here. Remember you can run only one thread at a time.
Have a look for reference: stackoverflow - avoid freezing of tkinter window using one line multithreading code
Here's how I managed to work around this issue (maybe it's quite dirty I don't know):
Whenever I update my tkinter window through the doingALotOfStuff() function, I call myTk.update().
It avoids freezing for me.

Joining process during quitting - Tkinter + multiporcessing

I'm writing a GUI application that has a button which invokes a long task. In order for it not to freeze the GUI I delegate the task to a different process using python 3.3's multiprocessing module. Then I return the result for display using a Pipe.
I want the application not to leave any zombie process even if quit during the computation. As I'm on a mac this can happen one of two ways: through quitting the application (Command+Q) or but closing it's window.
Here's the code in the function linked to a button in the GUI:
main_pipe,child_pipe=Pipe()
p=Process(target=worker,args=(child_pipe,data))
p.start()
try:
while not main_pipe.poll():
root.update()
value_array=main_pipe.recv()
finally:
p.join()
This doesn't work the application doesn't respond to Command+q, and closing the window leaves two zombie process running (one for the GUI and one for the worker).
How to make it work in the other case as well?
Is this good practice? Is the a nicer, more pythonic way of doing it?
Additionally at the very end of the script I have those two lines (the exit() closes the application if the window is closed while not processing anything):
root.mainloop()
exit()
And finally, what's the difference between update() and mainloop()? Is it only that the latter hogs up the program while update() doesn't?
Ok, I finally solved it. Although I not complete sure regarding this method's pythoness or side effects, if somebody needs it here it is.
I figured that quitting correctly can only happen in a mainloop() not in a update() so I wrote two functions, one for creating the process, and one for checking it's output and they call each other using root.after(). I set the processes daemon flag to true to ensure proper quitting behaviour. Here's the code:
def process_start():
global value_array
global main_pipe
main_pipe,child_pipe = Pipe()
p=Process(target=worker,args=(child_pipe,data))
p.daemon=True
p.start()
root.after(500,check_proc)
def check_proc():
if not main_pipe.poll():
root.after(500,check_proc)
else:
global value_array
value_array=main_pipe.recv()
I'm still not sure if p.join() is needed but the deamon thing seems to get around the zombie-proceses problem
I was facing the similar problem earlier (except I wasn't using multiprocess). After nearly a whole days research, I come up with the following conclusion:
mainloop and root.waitwindow will (sometimes) block the sys.exit signal, which your program should receive after you hit ⌘Q.
You can bind ⌘Q to a new function, although you may still receive the sys.exit signal
Another way (more reliable in my case) is to remap the tcl quitting signal to another function, instead of the default one. You can remap the quit event (dock quit and ⌘Q) using this: root.createcommand('::tk::mac::Quit',function)
When trying to quit the software, use sys.exit instead of exit() or quit()
You can also use root.wm_protocol("WM_DELETE_WINDOW", function) to define the behavior when users clicking on the red button.
You can actually make the border of the window and the three default buttons disappear by flagging root.overridedirect(1) and thus force the user to click on a button on your GUI instead of closing the window.

Python / Pyside Code not executing sequentially?

I am building a Qt-based GUI using Pyside. Inside a specific class who has access to a QMainWindow (_theMainWindow) class which in turn has access to 2 other Qt Widgets (theScan & theScanProgress) I am trying to show() the last one by executing
def initiateScan(self):
self._theMainWindow.theScan.theScanProgress.show()
This works just fine, the theScanProgress widget appears.
However, when I add the line that makes the application sleep (and a print statement), as below
def initiateScan(self):
self._theMainWindow.theScan.theScanProgress.show()
print("test")
time.sleep(3)
the program seems to go to sleep BEFORE the widget appears, i.e. as if the time.sleep(3) gets executed before self._theMainWindow.theScan.theScanProgress.show()
Any ideas why this happens?
This is because of the main loop which processes gui events. If you are not using threads you can only execute one function at a time. I strongly suspect that show emits a signal which goes into the event queue, which is in turn blocked until the current function returns.
Put another way, Qt is event driven, and it can only do one event at a time. What ever you did call initiateScan added an event to the stack that was to execute the function (something like you pushed a button, which emitted a signal, which then triggered the function), and that function can do some computation, alter internal state of your objects, and add events to the stack. What show does underneath is emit a signal to all of it's children for them to show them selves. For that code to run, it has to wait for the current event (the function with your sleep) to return. During the sleep the entire gui will be unresponsive for the exact same reason.
[I probably butchered some of the terms of art]
show only schedules the appearance of the progress widget. But since you are blocking the main thread with your sleep, the main thread cannot perform the scheduled action until you release it.
You have to use threads or find another way to wait 3 seconds.

python script takes long time and window seems to freeze [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Simple pygtk and threads example please
I have this "problem" with my python script.
I make window with pygtk (with one text field) and then my script performs a database query and prepare the data for storage into a txt file. Everything works great but there is only one thing I am facing - this function runs approx. 2-4 minutes and during this time my program window is not "responding", so it looks like program window freezed (but the script is running and window is "live" again after finish).
How could I treat this behaviour? I would like to have my window responsive all time. For example: have a textfield "working...".
You need to use threading in your application. Any time you have a long running process you need to put that work into a separate thread and send progress updates to the main thread. I've answered a similar question before you can find a working example here. A shorter just dummy kind of example is provided below.
example
import gtk, gobject, urllib, time, threading
def run():
for i in range(50):
gobject.idle_add(button.set_label, '%s/50 complete' % i)
time.sleep(0.1)
def clicked(button):
threading.Thread(target=run).start()
gtk.gdk.threads_init()
win = gtk.Window()
button = gtk.Button(label="Start")
button.connect('clicked', clicked)
win.add(button)
win.show_all()
gtk.main()

Closing threaded Tkinter window

I use a Tkinter window to visualize some output of my programm. The window is threaded (see basic structure below) and basically it works quite fine. So far, I only have trouble closing the window. When I clicke the "X" button for closing the window it works.
However, when I call the Monitor.close() method from the main programm that starts the monitor thread, the window just freezes (e.g., it doesn't react on clicking the "X" button) and the thread monitor keeps running. Thus, the main program does not exit.
So, at the moment, I also have to close first the window "manually" by clicking the closing button and then the main program. Not a big issue, but it would be great, if the main program could close the window by itself. Any hints?
Thanks and best regards,
Christian
class Monitor(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.start()
def close(self):
self.root.quit()
self.root.destroy()
def run(self):
self.root=Tkinter.Tk()
self.root.protocol("WM_DELETE_WINDOW", self.close)
self.root.mainloop()
Python Threading and Tk(inter) used in this way do not mix well, as they violate the Tcl/Tk threading model of using Tk just from one thread.
It works great with message passing though, just not with direct calls from a thread. So you need to add some message passing via Queue to this.
Have a look at http://effbot.org/zone/tkinter-threads.htm for an example.

Categories