I don't understand much about Gui action execution order etc., but my Problem is very simple:
I have a Code that looks like this:
button.setText('text')
do sth
time.sleep(1)
do sth else
Somehow the script always fails to set the text before the timeout, and therefore the timeout also breaks this action and the text appears only after the timeout. How can I tell this line of code to finish execution dispite the time.sleep()? Or do i have to introduce an if-statement, only to wait until the button has set its text (is there a better way)
Thanks
GUI applications typically use some event loop, which is responsible for actually painting your widgets, etc.
You can not block this event loop with a sleep, or your application will stop responding.
Instead, in the case of Qt, use a QTimer to invoke whatever you want to do asynchronously.
Related
MyButton1 =Button(master, text='Quit',bg="grey",width=20,
command=master.quit)
MyButton1.place(x=200, y=100)
MyButton2 =Button(master, text='Propagate', bg="grey",width=20,
command=mainmethod)
MyButton2.place(x=1000, y=100)
master.geometry("1500x1500")
master.mainloop( )
In the above code after pressing propagate button mainmethod is invoking..
I wrote my logic in main method where this method alone taking 2minutes to execute in the mean time GUI going unresponsive state for few min and later displaying all my required output on text box i inserted
whether any away to avoid the unresponsive issue apart from using multi threading
and i am looking such that after pressing propagate button button should disabled and window should not go unresponsive and display text.insert statements continuously which i added in main method ?????
To prevent hanging, you need to separate the calculations in the mainmethod from Tkinter's main loop by executing them in different threads. However, threading system in Python is not that well-developed as in other languages (AFAIK) because of GIL (Global Interpreter Lock), but there is an alternative - using processes just like threads. This is possible with multiprocessing library.
In order to just prevent hanging, you could create another function
from multiprocessing import Process
def mainmethodLaunch():
global mainmethodProcess
mainmethodProcess = Process(target=mainmethod)
mainmethodProcess.start()
And bind this function to MyButton2 instead of mainmethod itself.
Docs: https://docs.python.org/2/library/multiprocessing.html#the-process-class
You can see p.join in the example. join method will cause your main process to wait for the other one to complete, which you don't want.
So when you press the button, mainmethodLaunch function will be invoked, and it will create another process executing mainmethod. mainmethodLaunch function's own run duration should be insignificant. Due to usage of another process, Tkinter window will not hang. However, if you do just this, you will not be able to interact with mainmethod process in any kind while it will be working.
In order to let these processes communicate with each other, you could use pipes (https://docs.python.org/2/library/multiprocessing.html#exchanging-objects-between-processes)
I guess the example is quite clear.
In order to receive some data from the mainmethod process over time, you will have to poll the parent_conn once a little time, let's say, second. This can be achieved with Tkinter's after method
(tkinter: how to use after method)
IMPORTANT NOTE: when using multiprocessing, you MUST initialize the program in if __name__ == '__main__': block. I mean, there should be no doing-something code outside functions and this block, no doing-something code with zero indent.
This is because multiprocessing is going to fork the same Python executable file, and it will have to distinguish the main process from the forked one, and not do initializing stuff in the forked one.
Check twice if you have done that because if you make such a mistake, it can cost you hanging of not just Tkinter window, but the whole system :)
Because the process will be going to fork itself endlessly, consuming all RAM you have, regardless of how much you have.
I recently switched from wxPython to PyQT and can't find an equivalent of CallAfter. I need to use pubsub due to some imports and with wx I just sent messages with CallAfter -- is there a way to do something similar in PyQT? Basically, I want to inject something into the mainloop with pyQT.
EDIT FOR MORE INFO:
In my old GUI, using wxPython, I was using python-openzwave which uses an old dispatcher module. I would capture the old dispatcher signals and convert them to pubsub messages (for ease of use) and send the new messages with a CallAfter like this:
wx.CallAfter(pub.sendMessage, messagePack.signal, message = messagePack.message)
And then I was able to update the GUI by capturing the message and working directly on the gui elements because it essentially injected something into the mainloop.
Now, using pyqt, there is no callafter so, I have the same system setup without the callafter but the actions that have to occur after the message is received can't happen because it is in the middle of the mainloop.
The closest thing I can think of is using QTimer.singleShot with a short timeout, which will force it into the next event loop.
def other_function(self):
print 'other'
def my_function(self):
print 'one'
QTimer.singleShot(1, self.other_function)
print 'two'
Qt has the idea of an event loop, where it will check if there are events that need processing, like a button click, or part of a widget needs to be redrawn, etc. Typically, a function gets called as the result of an event. The QTimer.singleShot will stick your function call at the end of the list of things to be processed on the next cycle of the event loop.
But I agree with some of the comments that you probably could just use a separate QObject running in another thread to handle the openzwave events and re-dispatch the messages as Qt Signals, which the main thread can listen for and update the GUI.
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.
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.
I have a Python GUI that uses Tkinter. I have to SSH into another place to get data. I start a new thread to do this so that the GUI doesn't hang. During this time, I want to pop up a screen that lets the user know it is loading. Once the program is finished getting the data, I want to close the loading screen. What must I do to have my main loop recognize that the thread is done? I've tried to use that thread to close the loading screen that exists in the main loop, but then I discovered that doesn't work.
I have seen some producer consumer models that don't use GUIs, and they have while loops. This doesn't help me though. I also don't want to download and install other packages, but imports are ok. Thank you for your help!
Have your thread set a flag when it is done. Have the GUI periodically check for that flag and dismiss the window when it is set.
You can check for the flag by creating a function that checks for the flag, and if it's not set it uses after to have itself run again a few hundred ms later. The window won't go away immediately after the thread exits, but as long as the lag isn't more than a couple hundred ms the user will never notice.