Tkinter GUI freeze while multiprocessing/subprocessing - python

So i am running into a major issue. I am currently trying to use multi-processing/sub-processing to run files next to my Tkinter application, however, as soon as I run the process, the GUI freezes until I finish the process. Is there any way to get around this? I have also looked at other questions on SO but to no avail (I found one suggesting root.update() however that is not working as expected.
Note: I have not included GUI elements, as I have made basic programs to try this (only a couple of lines) and get the same problem. It also may be worth noting that I am running windows.
code (taken out of context):
def run_file(self):
self.root.update()
sub_process=subprocess.call(self.sub_proc_args)
process=multiprocessing.Process(target=self.run_file())
process.start()

By doing self.run_file() you are calling run_file before multiprocessing can use it. You need to use target=self.run_file (note, without parentheses).

I have found the problem (other then the target issue that BrenBarn pointed out). Here is my fix:
(i took out the multiprocessing.)
def run_file(self):
sub_process=subprocess.Popen(self.sub_proc_args) #Open subprocess
self.root.update() #Update GUI
self.run_file() #Initiate sub_process
I have found that this is the fix because using call, when you execute it, it must return a return value, which makes Tkinter not continue its mainloop. This is fixed by using Popen.

Related

Applying LiveServer Logic for Tkinter File

The people who are familiar with the Live Server of VS Code, would have easily understood what is the main motive of this question.
But for others, here's the explanation:
Main motive of Live Server is to Automatically Reload Your Site on Save in web development! (Which get changed for python tkinter).
When ever I change something in my python file which contains tkinter code, the change should be reflected in the main window (the main window should not re-open to reflect the changes).
I have tried to search on web as well as on stack over flow, but all the results are for updating value in entry, label, buttons etc. But what I want is, the whole window should be updated when ever I change something in my main file, and the main window should not be reopened to do so. So in short, updating whole window without closing it, on every changes in the main file or automatically reload your program on save without reopening!
What have I tried?:
I tried to detect change in file using os.getsize which satisfied the first part of my question, but however I am not able to solve the second part i.e window should not be closed.
import os
main__tkinter_filename="myfile.py"
initial_filesize=os.path.getsize(main_tkinter_filename) # Getting size of the file for
# comparison.
while 1:
final_filesize=os.path.getsize(main_tkinter_filename)
if final_filsize<intial_filesize or final_filesize>initial_filesize:
webbrowser.open(main_tkinter_filename)
Example:
from tkinter import *
root=Tk()
root.mainloop
results in the below GUI:
If i have added a=Label(text='text')anda.pack() after root=Tk(), it should show me the label, and if i have removed the same code, it should remove them.
I will answer your question by the best of my understanding,
I have some (a few projects of my own, still way too limited) experience with flutter which has hot-reload feature (same as you described above, which you want with python, mainly tkinter), I recently switched to python for gui (Loved it!), so I would like to share my research here:
I was successfully able to set up hot-reload both with kivy (kivymd hot reload, which comes with watchdog and kaki, which works real-time), and with tkinter, while there is a hitch with the later, you will have to press Ctrl + R as to reload the tkinter window, but it works without having to re-run the python program, I will leave the link to the found resources here, hope it helps with your query!
To setup hot-reload with tkinter (requires Ctrl + R), please refer here.
To setup hot-reload with kivy/kivymd (real-time), which I personally prefer, you can find the official docs here.
To mention, I use the above on Manjaro (Arch linux) with pycharm, atom, but I have also tried and have made it run successfully on Windows 10 with vs code (worked like charm)
Hope I could be of help! If you face any problem regarding the same, please feel free to ask! Thanks!
After digging around I have finally found out a way to implement hot reload feature (which #Stange answers provides) but just updating the selected frame or code.
The basic idea is constanly reading the file and executing the selected code, and removing the object in a list which are meant to be removed.
# Live Checker.py
import keyboard
while 1:
if keyboard.is_pressed("Ctrl+r"):
with open('test.py','r') as file:
file_data=file.read()
file_data_start_index=file_data.find("'#Start#'")
file_data_end_index=file_data.find("'#End#'")
exec_command=file_data[file_data_start_index:file_data_end_index]
with open('exec_log.txt','w') as txt_file:
txt_file.write(exec_command)
Here I am constantly checking if if ctrl+r key is pressed, and if pressed
it reads the file,
writes the selected code from the file into a txt file.
I have specified the start and end of the code to be updated by #Start# and #End# respectively.
# Main.py
def check():
with open('exec_log.txt','r') as exec_c:
exec_command=exec_c.read()
if len(exec_command)==0:
pass
else:
print(exec_command)
exec('for i in root.winfo_children():i.destroy()\n'+exec_command)
print('exec')
with open('exec_log.txt','w') as exec_c:
pass
root.update()
root.after(100,check)
root.after(100,check)
And in the main file, i have added the above code which continusly check if exec_log.txt file has any changes, and if changes are there, then it executes them and all so destroys the widget specified in the remove_list.
This is just a temporary solution which in my case helps me to implement the hot reload feature in tkinter.

How do I stop Pyzo from calling mainloop?

I used to use the Interactive Editor for Python, and then I "upgraded" to Pyzo (since IEP was merged into Pyzo). One of my programs uses tkinter to create a GUI. The code used to work just fine: I would run the file as a script, and then, in the interpreter, I would call main, which would launch the application.
The code skeleton looks like this.
def main():
class Application(tk.Frame):
def __init__(self, master=None):
# a bunch of stuff
# several more methods here
front=Application()
front.mainloop()
# then I can either call main in the interpreter, or I can add this:
# the name==main part is to handle some multiprocessing that occurs within the application class
if __name__=="__main__":
main()
This worked like a charm in IEP. However, in Pyzo, main() never launches, or, rather, it launches, but the gui never appears and it doesn't let me do anything. Instead, I get this message: Note: The GUI event loop is already running in the pyzo kernel. Be aware that the function to enter the main loop does not block.
This message occurs in Pyzo when I am using the CPython 3 or PyPy interpreter, but not when I'm using Anaconda 3 (I actually need to use PyPy because the work I'm doing is computationally expensive).
The other alternative is to not use Pyzo, but that's no fun.
I discovered the answer a while ago but I didn't get back to posting the answer until now. Essentially, there is a setting in Pyzo itself which attempts to detect GUIs. Switching that setting from auto to none fixed the problem.

Python Tkinter to run subprocess on a different thread to avoid non-responding GUI

I've been fighting with Tkinter for a while now and have exhausted most the resources I have for referencing this. I've found a couple similar topics here but none quite bring me to where I need to be.
I've got a long running (not long actually, it only takes 10-12 secs) python script that silently install an application using subprocess from the CLI. Subprocess worked and it successfully installed the application, however, the GUI locks (i.e hangs/freeze) after the execution (and the program no longer run the succeeding code). I know I have to use threading here but I've already tried using it to no avail.
As part of my learning process, I cloned a repo from Github and modify it to run on Windows (since it only run in MAC platform) and planning to extend it, and this is the part where I got stucked. This is my first time to use TKinter and I apologize if I have missed something stupid or not asked the question in the right way. Hope you can help me and thank you in advance for the assistance.
Code can be found on this link.
You have defined a button
installButton = Button(bottomFrame, text=installButtonTxt,
command=on_install_thread, width=9)
with command handler
def on_install_thread():
...
loop_thread = threading.Thread(target=on_install_button_active,
args=['button', 'model', itemSelectCount])
and the target for thread on_install_button_active.
Then
def on_install_button_active(button, model, selectcount):
...
# Reset install status
installStatus = 'complete'
# Remove Cancel Button
cancelButton.destroy()
# Activate Install/Done button and menus
installButton.configure(state=NORMAL)
# menuControl('normal')
refreshGui(mainWindow)
It looks like there is code at the end of on_install_button_active that involves Tkinter
widgets. Calling tkinter methods from other threads seems to be unreliable, for example 1.
It is possible to define virtual events
def renderMainWindow():
...
mainWindow.bind('<<InstallComplete>>', on_install_complete)
def on_install_complete():
cancelButton.destroy()
installButton.configure(state=NORMAL)
def on_install_button_active(button, model, selectcount):
...
# Reset install status
installStatus = 'complete'
mainWindow.event_generate('<<InstallComplete>>', when='tail')
If there are other calls in that thread that involve Tkinter widgets, it might be better to remove them.
I was able to resolved problems with my GUI by using mtTkinter and by referring to this post.

GTK Progressbar pulsing python

How can I get a Progressbar to "pulse" while another function is run?
There is an example of how to do this here.
Push that another function into a separate thread. As long as your main thread runs any code, GUI is frozen. This is not a problem for short code pieces, but obviously a problem in your case.
Also read what PyGTK FAQ has to say about using threads in PyGTK program.
If your function runs in many iterations that don't take too long by themselves, then you don't necessarily need to mess around with separate threads. You can also cause the GUI to update itself during your long calculation:
def long_function(some_args):
while task_is_not_finished():
do_some_stuff_that_doesnt_take_too_long()
progress_bar.pulse()
while gtk.events_pending():
gtk.main_iteration()

Progress bar not updating during operation

in my python program to upload a file to the internet, im using a GTK progress bar to show the upload progress. But the problems that im facing is that the progress bar does not show any activity until the upload is complete, and then it abruptly indicates upload complete. im using pycurl to make the http requests...my question is -
do i need to have a multi-threaded application to upload the file and simultaneously update the gui? or is there some other mistake that im making?
Thanks in advance!
I'm going to quote the PyGTK FAQ:
You have created a progress bar inside a window, then you start running a loop that does some work:
while work_left:
...do something...
progressbar.set_fraction(...)
You will notice that the window doesn't even show up, or if it does the progress bar stays frozen until the end of the task. The explanation is simple: gtk is event driven, and you are stealing control away from the gtk main loop, thus preventing it from processing normal GUI update events.
The simplest solution consists on temporarily giving control back to gtk every time the progress is changed:
while work_left:
...do something...
progressbar.set_fraction(...)
while gtk.events_pending():
gtk.main_iteration()
Notice that with this solution, the user cannot quit the application (gtk.main_quit would not work because of new loop [gtk.main_iteration()]) until your heavy_work is done.
Another solution consists on using gtk idle functions, which are called by the gtk main loop whenever it has nothing to do. Therefore, gtk is in control, and the idle function has to do a bit of work. It should return True if there's more work to be done, otherwise False.
The best solution (it has no drawbacks) was pointed out by James Henstridge. It is taking advantage of python's generators as idle functions, to make python automatically preserve the state for us. It goes like this:
def my_task(data):
...some work...
while heavy_work_needed:
...do heavy work here...
progress_label.set_text(data) # here we update parts of UI
# there's more work, return True
yield True
# no more work, return False
yield False
def on_start_my_task_button_click(data):
task = my_task(data)
gobject.idle_add(task.next)
The 'while' above is just an example. The only rules are that it should yield True after doing a bit of work and there's more work to do, and it must yield False when the task is done.
More than likely the issue is that in your progress callback, which is where I presume you're updating the progress bar, you're not making a call to manually update the display i.e. run through the GUI's event loop. This is just speculation though, if you can provide more code, it might be easier to narrow it down further.
The reason you need to manually update the display is because your main thread is also performing the upload, which is where it's blocking.
In python 2.x integer operands result in integer division. Try this:
#Callback function invoked when download/upload has progress
def progress(download_t, download_d, upload_t, upload_d):
print 'in fileupload progress'
mainwin.mainw.prog_bar.set_fraction(float(upload_d) / upload_t)
Yes, you probably need concurrency, and yes threads are one approach, but if you do use threads, please use an method like this one: http://unpythonic.blogspot.com/2007/08/using-threads-in-pygtk.html which will abstract away the pain, and allow you to focus on the important aspects.
(I have not repeated everything in that blog post through laziness, hence community wiki).
One option, if you are not married to pycurl, is to use GObject's IO watchers.
http://pygtk.org/pygtk2reference/gobject-functions.html#function-gobject--io-add-watch
Using this you can interleave the file upload with the normal PyGTK event loop, and even do the set_progress call in your IO watch callback. If you are offloading all the work for uploading onto pycurl this is not really feasible, but if you're just uploading a file over HTTP, io_add_watch will make using a socket for this much less painful as well.

Categories

Resources