Reputation: 21
How can I change it to non-modal ? If button is clicked then cursor doesn't change. If I change it to non-modal then it should be ok.
def start_new_proc():
# text.config(cursor="clock")
root.config(cursor="clock")
text.config(state="normal")
command = "ping 8.8.8.8 -c 1"
proc = Popen(command, shell=True, stdout=PIPE)
for line in iter(proc.stdout.readline, ''):
line = line.decode('utf-8')
if line == '':
break
text.insert("end", line)
proc.wait()
# text.config(cursor="")
root.config(cursor="")
text.config(state="disable")
root = tk.Tk()
text = tk.Text(root, state="disabled")
text.pack()
button = tk.Button(root, text="Run", command=start_new_proc)
button.pack()
root.mainloop()
Upvotes: 0
Views: 515
Reputation: 6156
Here is one approach, it uses threading and queue to not block the .mainloop()
and to not call tkinter
methods from another thread (which may potentially break it):
import tkinter as tk
from subprocess import Popen, PIPE
from threading import Thread
from queue import Queue
def start_new_proc():
# text.config(cursor="clock")
root.config(cursor="clock")
text.config(state="normal")
queue = Queue()
Thread(target=lambda: run_proc(queue)).start()
update_text(queue)
def after_proc():
# text.config(cursor="")
root.config(cursor="")
text.config(state="disable")
def update_text(queue):
data = queue.get()
if data == 'break':
after_proc()
return
text.insert('end', data)
root.after(100, update_text, queue)
def run_proc(queue):
command = "ping 8.8.8.8"
proc = Popen(command, shell=True, stdout=PIPE)
for line in iter(proc.stdout.readline, ''):
line = line.decode('utf-8')
if line == '':
queue.put('break')
break
queue.put(line)
# proc.wait()
root = tk.Tk()
text = tk.Text(root, state="disabled")
text.pack()
button = tk.Button(root, text="Run", command=start_new_proc)
button.pack()
root.mainloop()
Quick explanation:
When you click the button, it calls start_new_proc()
:
Now first all the configurations are run so now the cursor and text are configured (note that the clock cursor is visible only when the cursor is not on Text widget, since the config for text widget is commented out)
Then a queue object is created, this will allow to safely communicate between threads (and it will get garbage collected once the function "stops")
Then a thread is started where the cmd
command is run (oh and I changed it a bit (removed "-c 1") because I couldn't test otherwise). So every iteration the data is put into the queue for the main thread to safely receive it, other stuff is the same, also notice that there is '"break"' put into queue once the loop has to stop, so that the other end knows to stop too (probably better to place some object since "break"
can be placed there by line
variable)
Then the update_text()
function is called which receives data from the queue and then inserts it to the text widget (there is also the "break"
check) and then the .after()
method for making the function loop without blocking the .mainloop()
(and also when "break"
apppears then configuration function gets called to config the state and cursor)
Upvotes: 0
Reputation: 3942
You can achieve this using threading.
import threading
def ping_func(output):
command = "ping 8.8.8.8 -c 1"
proc = Popen(command, shell=True, stdout=PIPE)
for line in iter(proc.stdout.readline, ''):
line = line.decode('utf-8')
if line == '':
break
output.append(line)
def check_complete():
if not ping_thread.is_alive():
root.config(cursor = "")
button.config(state = "normal")
else:
root.after(100, check_complete)
def check_output():
if output != []:
text.config(state = "normal")
text.insert("end", output.pop(0))
text.config(state = "disabled")
root.after(100, check_output)
def start_new_proc():
#text.config(cursor="clock")
root.config(cursor="clock")
button.config(state = "disabled") #Disable button until process completed
global output, ping_thread
output = []
ping_thread = threading.Thread(target = ping_func, args = (output,))
ping_thread.start()
check_output()
check_complete()
root = tk.Tk()
text = tk.Text(root, state="disabled")
text.pack()
button = tk.Button(root, text="Run", command=start_new_proc)
button.pack()
root.mainloop()
By putting the command execution in a thread, it doesn't hold up the tkinter thread. The function ping_func
is what the thread runs. It does what the program did before, but the output is different. Because tkinter isn't thread safe, you can't use tkinter objects in other threads. Therefore we need to pass a variable instead which can then be polled by the tkinter thread. This is what the output
list is for. The output from ping_func
is appended to output
. To check the contents of output
and insert it into the Text widget, we need a function check_output
. This calls itself every 100ms to check if the contents of output have changed. If they have it inserts the new line into the Text widget. There is also check_complete
which checks every 100ms if the thread has ended and changes the cursor and enables the button if it has.
Upvotes: 0