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()
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.
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)
Related
I m sorry if it's a little confusing I will try to explain as simple as possible way, I have been trying to solve this threading issue for the past 2 days.
In short, what I m doing is running a big script on a separate thread once I hit the run button from the first screen, it reads data from the JSON file I have stored in the directory and Run a job.
Which gradually after completing steps increases the progress bar value and updates the label status from the run_script function.
And the error is after completing the first cycle when it comes back to first screen and if I run another job it doesnt work because of progressbar and label.
What I've tried so far is destroying the window from script_function and recreating it on the first screen. (2nd job worked but it didn't destroy the first job window, and after completing 2nd job it completely shutdown both windows)
without mttkinter, I got tcl run time error because I m trying to destroy the window from another thread.
from mttkinter import mtTkinter as tk
from tkinter import ttk
import threading
#other imports
window = Tk()
def screen1():
##show the screen with json data labels and buttons#
window.geometry("1215x770")
window.configure(bg="#FFFFFF")
b0 = Button(window, text='Run job', command=do_something) #samplebutton
window.resizable(False, False)
window.mainloop()
def do_something():
##running big script with progressbar and status label.
window.geometry("1215x770")
window.configure(bg="#FFFFFF")
progress = ttk.Progressbar(window, style='red.Horizontal.TProgressbar', orient=HORIZONTAL,
length=443, mode='determinate')
progress.pack()
label = Label("Completed tables 0/{totaltables}") #samplelabel
label.pack()
if data['foo'] == 'bar':
threading.Thread(target=run_script, args=(arg1, arg2,)).start() #run script in seperate thread this script also has a function that runs on multiple threads.
window.resizable(False, False)
window.mainloop()
def run_script(arg1, arg2):
#running the script#
for i in range(5): #running each i on new thread followed by starting and joining.
print(i)
#main problem#
#once script finishes#
response = messagebox.showinfo("Task", "Did something!")
if response:
screen1()
What is the possible way to accomplish this?
I want to keep running jobs without having to open the app again and again.
the script should be on a separate thread because if it's not the app goes not responding.
I want to keep showing the progress bar and status and once it's complete go back to the first screen.
How do I kill the main Tkinter thread after completing the first job?
or How do I destroy the Tkinter window from another thread?
If you have a tcl/tk that is built with multithreading support, and you are using Python 3, you should be able to call tk functions from a second thread.
This is an example program, where a second thread modifies widgets:
import os
import sys
import threading
import time
import tkinter as tk
import tkinter.font as tkfont
import types
__version__ = "2022.02.02"
# Namespace for widgets that need to be accessed by callbacks.
widgets = types.SimpleNamespace()
# State that needs to be accessed by callbacks.
state = types.SimpleNamespace()
def create_widgets(root, w):
"""
Create the window and its widgets.
Arguments:
root: the root window.
w: SimpleNamespace to store widgets.
"""
# General commands and bindings
root.wm_title("tkinter threading v" + __version__)
root.columnconfigure(2, weight=1)
root.rowconfigure(3, weight=1)
root.resizable(False, False)
# First row
tk.Label(root, text="Thread status: ").grid(row=0, column=0, sticky="ew")
w.runstatus = tk.Label(root, text="not running", width=12)
w.runstatus.grid(row=0, column=1, sticky="ew")
# Second row
tk.Label(root, text="Timer: ").grid(row=1, column=0, sticky="ew")
w.counter = tk.Label(root, text="0 s")
w.counter.grid(row=1, column=1, sticky="ew")
# Third row
w.gobtn = tk.Button(root, text="Go", command=do_start)
w.gobtn.grid(row=2, column=0, sticky="ew")
w.stopbtn = tk.Button(root, text="Stop", command=do_stop, state=tk.DISABLED)
w.stopbtn.grid(row=2, column=1, sticky="ew")
def initialize_state(s):
"""
Initialize the global state.
Arguments:
s: SimpleNamespace to store application state.
"""
s.worker = None
s.run = False
s.counter = 0
def worker():
"""
Function that is run in a separate thread.
This function *does* update tkinter widgets. In Python 3, this should be
safe if a tkinter is used that is built with threads.
"""
# Initialization
widgets.runstatus["text"] = "running"
# Work
while state.run:
time.sleep(0.25)
state.counter += 0.25
widgets.counter["text"] = f"{state.counter:.2f} s"
# Finalization
state.counter = 0.0
widgets.counter["text"] = f"{state.counter:g} s"
widgets.runstatus["text"] = "not running"
def do_start():
"""Callback for the “Go” button"""
widgets.gobtn["state"] = tk.DISABLED
widgets.stopbtn["state"] = tk.NORMAL
state.run = True
state.worker = threading.Thread(target=worker)
state.worker.start()
def do_stop():
"""Callback for the “Stop” button"""
widgets.gobtn["state"] = tk.NORMAL
widgets.stopbtn["state"] = tk.DISABLED
state.run = False
state.worker = None
# Main program starts here.
if __name__ == "__main__":
# Detach from the command line on UNIX systems.
if os.name == "posix":
if os.fork():
sys.exit()
# Initialize global state
initialize_state(state)
# Create the GUI window.
root = tk.Tk(None)
# Set the font
default_font = tkfont.nametofont("TkDefaultFont")
default_font.configure(size=12)
root.option_add("*Font", default_font)
create_widgets(root, widgets)
root.mainloop()
Note how the worker thread monitors state.run and exits when it becomes False.
In general I find using multiple top-level windows confusing, so I don't use that pattern.
I would rather create a grid with rows of a progressbar and stop button for every thread.
I have a program (say p1.py) which calls another python script (say p2.py) on click of a button. I would like to have a button which stops the execution of the p2.py but all the buttons freeze when it is running.
The only way to stop it is to use a keyboard interrupt in the console. I have read about the after() function but do I have to implement it in p1 or p2? Or is there any other way to do it without the after() function?
import tkinter
import os
window = tkinter.Tk()
window.title("Detecting")
def clicked():
os.system('python extract_frames.py')
bt = tkinter.Button(window,text="Start",command=clicked)
bt.pack()
stop = tkinter.Button(window,text="Stop",command="break") #also what command should I use for the interrupt?
stop.pack()
window.geometry('400x400')
window.mainloop()
You should use subprocess.Popen() instead of os.system():
import tkinter
import subprocess
proc = None
def clicked():
global proc
proc = subprocess.Popen(['python', 'extract_frames.py'])
def kill_task():
if proc and proc.poll() is None:
print('killing process ...')
proc.kill()
window = tkinter.Tk()
window.geometry('400x400')
window.title("Detecting")
bt = tkinter.Button(window, text="Start", command=clicked)
bt.pack()
stop = tkinter.Button(window, text="Stop", command=kill_task)
stop.pack()
window.mainloop()
I am trying to route the output of a separate script to a Tkinter window.
Below is my attempt at the problem. The Tkinter box appears but does not update when the controller (abstracted here) writes to pipeout using os.write(pipeout, msg) .
from Tkinter import *
from controller import controller
import os
def run():
"""Top level run method which initiates program."""
def updateInput():
"""Update the textbox with controller output."""
readFrom = os.fdopen(pipein)
line = readFrom.readline()
text.insert(END, line)
text.after(1000, updateInput)
pipein, pipeout = os.pipe() #pipe for comms between tkinter and controller
pid = os.fork()
if not pid:
#within child process, launch controller with passed pipe
os.close(pipein)
mainController = controller(pipeout)
os.close(pipeout)
root = Tk()
text = Text(root)
text.pack()
text.after(1000, updateInput) #update text box each second
root.mainloop()
if __name__ == "__main__":
run()
The abstracted controller is writing to the pipe via
os.write(self.pipeout, msg)
where self.pipeout has been assigned from self.pipeout = pipeout in the controller class init .
Sounds like you forgot to flush.
self.pipeout.write(msg)
self.pipeout.flush()
Also, make sure the msg ends in a newline.
Edit: are you sure you need a pipe? There's probably neater ways to do whatever you are doing, like threading.
Solution came from using os.read(pipein, 100) instead of os.fdopen(pipein) in updateInput.
line = os.read(pipein, 100)
text.insert(END, line)
text.after(1000, updateInput)
My question is almost the same as this one:
Widget to Display subprocess stdout?
but a step further.
I have the following code (python2.7):
def btnGoClick(p1):
params = w.line.get()
if len(params) == 0:
return
# create child window
win = tk.Toplevel()
win.title('Bash')
win.resizable(0, 0)
# create a frame to be able to attach the scrollbar
frame = ttk.Frame(win)
# the Text widget - the size of the widget define the size of the window
t = tk.Text(frame, width=80, bg="black", fg="green")
t.pack(side="left", fill="both")
s = ttk.Scrollbar(frame)
s.pack(side="right", fill="y")
# link the text and scrollbar widgets
s.config(command=t.yview)
t.config(yscrollcommand=s.set)
frame.pack()
process = subprocess.Popen(["<bashscript>", params], shell=False,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
while True:
out = process.stdout.readline()
if out == '' and process.poll() is not None:
break
print out
t.insert(tk.END, out)
The output from the "longrunning" bash script is captured in realtime (appear in the console) but the Tkinter window appear only after the end of the subprocess !!
How can I have the window appearing before the subprocess start and update its content in realtime ?
Finally I found the solution.
After the window construction, you must add :
frame.pack()
# force drawing of the window
win.update_idletasks()
And then after every line insertion in the widget, you must also force a refresh with the same method only on the widget.
# insert the line in the Text widget
t.insert(tk.END, out)
# force widget to display the end of the text (follow the input)
t.see(tk.END)
# force refresh of the widget to be sure that thing are displayed
t.update_idletasks()
This is an interesting solution. Would that be possible to have the whole working code?
I am asking because I was wonder how the while True does not block the usability of the whole GUI... does it not?
As suspected, I tried and this example is not really work. If you use something like "ls -Rf /" as command, you will see that the while loop will make the txt output flowing pretty well. However both windows (main and secondary) will block big time.
I suspect you need to send the print txt part in a separated thread or process. Or, if you use pygtk you can use stuff like
gobject.io_add_watch(self.ep1.stdout, # file descriptor
gobject.IO_IN, # condition
self.write_to_buffer ) # callback
which was actually invented for this.
Just in case someone else is looking for it...
log_box_1 = tk.Text(root, borderwidth=3, relief="sunken")
with subprocess.Popen("ls -la", shell=True, stdout=subprocess.PIPE, bufsize=1, universal_newlines=True) as p:
for line in p.stdout:
log_box_1.insert(tk.END, line)
From here
I have created a program that prints results on command line.
(It is server and it prints log on command line.)
Now, I want to see the same result to GUI .
How can I redirect command line results to GUI?
Please, suggest a trick to easily transform console application to simple GUI.
Note that it should work on Linux and Windows.
You could create a script wrapper that runs your command line program as a sub process, then add the output to something like a text widget.
from tkinter import *
import subprocess as sub
p = sub.Popen('./script',stdout=sub.PIPE,stderr=sub.PIPE)
output, errors = p.communicate()
root = Tk()
text = Text(root)
text.pack()
text.insert(END, output)
root.mainloop()
where script is your program. You can obviously print the errors in a different colour, or something like that.
To display subprocess' output in a GUI while it is still running, a portable stdlib-only solution that works on both Python 2 and 3 has to use a background thread:
#!/usr/bin/python
"""
- read output from a subprocess in a background thread
- show the output in the GUI
"""
import sys
from itertools import islice
from subprocess import Popen, PIPE
from textwrap import dedent
from threading import Thread
try:
import Tkinter as tk
from Queue import Queue, Empty
except ImportError:
import tkinter as tk # Python 3
from queue import Queue, Empty # Python 3
def iter_except(function, exception):
"""Works like builtin 2-argument `iter()`, but stops on `exception`."""
try:
while True:
yield function()
except exception:
return
class DisplaySubprocessOutputDemo:
def __init__(self, root):
self.root = root
# start dummy subprocess to generate some output
self.process = Popen([sys.executable, "-u", "-c", dedent("""
import itertools, time
for i in itertools.count():
print("%d.%d" % divmod(i, 10))
time.sleep(0.1)
""")], stdout=PIPE)
# launch thread to read the subprocess output
# (put the subprocess output into the queue in a background thread,
# get output from the queue in the GUI thread.
# Output chain: process.readline -> queue -> label)
q = Queue(maxsize=1024) # limit output buffering (may stall subprocess)
t = Thread(target=self.reader_thread, args=[q])
t.daemon = True # close pipe if GUI process exits
t.start()
# show subprocess' stdout in GUI
self.label = tk.Label(root, text=" ", font=(None, 200))
self.label.pack(ipadx=4, padx=4, ipady=4, pady=4, fill='both')
self.update(q) # start update loop
def reader_thread(self, q):
"""Read subprocess output and put it into the queue."""
try:
with self.process.stdout as pipe:
for line in iter(pipe.readline, b''):
q.put(line)
finally:
q.put(None)
def update(self, q):
"""Update GUI with items from the queue."""
for line in iter_except(q.get_nowait, Empty): # display all content
if line is None:
self.quit()
return
else:
self.label['text'] = line # update GUI
break # display no more than one line per 40 milliseconds
self.root.after(40, self.update, q) # schedule next update
def quit(self):
self.process.kill() # exit subprocess if GUI is closed (zombie!)
self.root.destroy()
root = tk.Tk()
app = DisplaySubprocessOutputDemo(root)
root.protocol("WM_DELETE_WINDOW", app.quit)
# center window
root.eval('tk::PlaceWindow %s center' % root.winfo_pathname(root.winfo_id()))
root.mainloop()
The essence of the solution is:
put the subprocess output into the queue in a background thread
get the output from the queue in the GUI thread.
i.e., call process.readline() in the background thread -> queue -> update GUI label in the main thread. Related kill-process.py (no polling -- a less portable solution that uses event_generate in a background thread).
Redirecting stdout to a write() method that updates your gui is one way to go, and probably the quickest - although running a subprocess is probably a more elegant solution.
Only redirect stderr once you're really confident it's up and working, though!
Example implimentation (gui file and test script):
test_gui.py:
from Tkinter import *
import sys
sys.path.append("/path/to/script/file/directory/")
class App(Frame):
def run_script(self):
sys.stdout = self
## sys.stderr = self
try:
del(sys.modules["test_script"])
except:
## Yeah, it's a real ugly solution...
pass
import test_script
test_script.HelloWorld()
sys.stdout = sys.__stdout__
## sys.stderr = __stderr__
def build_widgets(self):
self.text1 = Text(self)
self.text1.pack(side=TOP)
self.button = Button(self)
self.button["text"] = "Trigger script"
self.button["command"] = self.run_script
self.button.pack(side=TOP)
def write(self, txt):
self.text1.insert(INSERT, txt)
def __init__(self, master=None):
Frame.__init__(self, master)
self.pack()
self.build_widgets()
root = Tk()
app = App(master = root)
app.mainloop()
test_script.py:
print "Hello world!"
def HelloWorld():
print "HelloWorldFromDef!"
Sorry for my bad English. I actually, used a different way to print Command Prompt output into my new Automation tool.
Please find those steps below.
1> Create a Bat File & redirect its output to a LOG file.
Command Prompt command: tasklist /svc
2> Make read that file with Python 3.x.
`processedFile = open('D:\LOG\taskLog.txt', 'r')
3> The Finale step.
ttk.Label(Tab4, text=[ProcessFile.read()]).place(x=0, y=27)
**Hence please be informed that, I have not include scrollbar into this code yet.
Posting Screenshot: