Python tkinter: Update GUI between subprocess calls - python

I programmed a GUI that calls a .cmd file several times (with different parameters)
class App:
def process(self):
for filename in os.listdir(path):
subprocess.call(['script.cmd', filename])
self.output('processed ' + filename)
def output(self, line):
self.textarea.config(state = NORMAL)
self.textarea.tag_config("green", background="green", foreground="black")
self.textarea.insert(END, line, ("green"))
self.textarea.yview(END)
self.textarea.config(state = DISABLED)
self.textarea.update_idletasks()
root = Tk()
app = App()
app.build_gui(root)
app.pack_gui(root)
root.mainloop()
process() is called when pressing a button
I also tried subprocess.Popen() and the old os.spawnv()
It's always the same. The GUI is not reacting when processing the files. Only after all files have been processed, the GUI is updated with all the 'processed XYZ' messages.
Shouldn't update_idletasks() update the GUI after every subprocess call?
Thank you
edit:
I narrowed the problem to this simple code:
from Tkinter import *
import subprocess
file_list = ['file1', 'file2', 'file3', 'file4', 'file5']
def go():
labeltext.set('los')
for filename in file_list:
labeltext.set('processing ' + filename + '...')
label.update_idletasks()
proc = subprocess.call(["C:\\test\\process.exe", filename])
labeltext.set('all done!')
root = Tk()
Button(root, text="Go!", command=go).pack(side=TOP)
labeltext = StringVar()
labeltext.set('Press button to start')
label = Label(root, textvariable=labeltext)
label.pack(side=TOP)
root.mainloop()
Now it depends on the process.exe if the script works properly. If I write a simple C program with busy-looping (e.g. source code of process.exe: int i=0; while(i<1e9){ i++; }), the GUI is updated with every file1-5. When I call the original .exe-file I wanted to use, it displays "processing file1" and switches to "processing file2" but then freezes until program termination ("all done!").
I dont really understand whats up here. Obviously it has something to do with the process called. Does anyone have an idea?

I found a dirty solution:
I call root.update() before every subprocess.call().
To make sure that no buttons are pressed during processing (that seems to be a problem with root.update() according to a quick google search), I disable them all before the subprocesses are started
like this:
from Tkinter import *
import subprocess
file_list = ['file1', 'file2', 'file3', 'file4', 'file5']
def button():
b_process.configure(state=DISABLED)
go()
b_process.configure(state=NORMAL)
def go():
for filename in file_list:
label.configure(text="processing " + filename)
root.update()
proc = subprocess.call(["C:\\DTNA\\stat\\run.exe", filename])
print 'process terminated with return code ' + str(proc)
label.configure(text="all done!")
root = Tk()
b_process = Button(root, text="Go!", command=button)
b_process.pack(side=TOP)
label = Label(root, text='Press button to start')
label.pack(side=TOP)
root.mainloop()

Related

Writing a Python UI for a seperate program with tkinter. The stop button for this program basically freezes the UI and continues with the script

Here is what I coded...
import tkinter as tk
import subprocess
import sys
import time
import os
import tkinter.font as font
from tkinter.ttk import *
app = tk.Tk()
app.geometry("400x400")
app.configure(bg='gray')
photo = tk.PhotoImage(file=r"C:\Users\ex\ex_button_active.png")
myFont = font.Font(family='Helvetica', size=20, weight='normal')
tk.Label(app, text='EX', bg='gray', font=(
'Verdana', 15)).pack(side=tk.TOP, pady=10)
app.iconbitmap(r'C:\Users\ex\ex_icon.ico')
start = time.time()
cmd = sys.executable + " -c 'import time; time.sleep(2)' &"
subprocess.check_call(cmd, shell=True)
assert (time.time() - start) < 1
p = subprocess.Popen(cmd, shell=True)
def ex_activation():
#Python Code
#Python Code...
def ex_stop():
sys.exit(ex_activation) #This area is basically where I have a button to terminate the other script running.
#I have tried sys.exit() and had the same result
ex_activation_button = tk.Button(app,
bg='black',
image=photo,
width=120,
height=120,
command=ex_activation)
ex_stop_button = tk.Button(app,
bg='Gray',
text='ex',
width=12,
command=ex_stop
height=3)
ex_stop_button['font'] = myFont
app.title("Example")
ex_activation_button.pack(side=tk.TOP)
ex_stop_button.pack(side=tk.LEFT)
app.mainloop()
I am looking for a way to get my program to stop the program the other button runs. I realized that this maybe be a "self destruct button" but I don't know how to do this with the script the other button runs. Any help greatly appreciated! I tried killing the code by putting the def ex_activation in the p.kill
This did not work...
If the other python script is made to run forever (has some kind of while True:), you can't run it on the command line as you did, because it will freeze your window while that script is running.
In order to run a python script on background you will need to do it with the subprocess library. (Find out here)
I also found an answer of another question that uses check_ouput() in order to know when the python program has finished. This can also be useful if you want to send a status to the tkinter app: you can print("33% Complete"), for example. You could add this in tkinter's main loop, so you always know if your program is running or not.
And last but not least, to kill that process (using the stop button), you should do it using os, and looking for the subprocess' ID. Here you can also find a good example.
I would try something like this:
cmd = "exec python file.py"
p = subprocess.Popen(cmd, shell=True)
# Continue running tkinter tasks.
tk.update()
tk.update_idletasks() # These both lines should be inside a while True
# Stop secondary program
p.kill()
EDIT
Example code using your question's code. WARNING: I have changed the png file location for testing, commented the app icon, and tested ONLY on Windows.
It's important to remove the mainloop() on the main file and put update...() in order to catch the keyboardInterrupt that (I don't know why) is killing both parent and child process.
I invite you to try it and be as happy as I have been when it was working after half an hour of testing!!
File 1: daemon.py - this file will run forever.
from time import sleep
from sys import exit
while True:
try:
print("hello")
sleep(1)
except KeyboardInterrupt:
print("bye")
exit()
File 2: tkinterapp.py - The name is self-explainatory
import tkinter as tk
import subprocess
import sys
import time
import os
import tkinter.font as font
from tkinter.ttk import *
app = tk.Tk()
app.geometry("400x400")
app.configure(bg='gray')
photo = tk.PhotoImage(file=r"C:\Users\royal\github\RandomSketches\baixa.png")
myFont = font.Font(family='Helvetica', size=20, weight='normal')
tk.Label(app, text='EX', bg='gray', font=(
'Verdana', 15)).pack(side=tk.TOP, pady=10)
# app.iconbitmap(r'C:\Users\ex\ex_icon.ico')
def ex_activation():
global pro
print("running!")
pro = subprocess.Popen("python daemon.py", shell=True)
def ex_stop():
global pro
print("stopping!")
os.kill(pro.pid, 0)
ex_activation_button = tk.Button(app,
bg='black',
image=photo,
width=120,
height=120,
command=ex_activation)
ex_stop_button = tk.Button(app,
bg='Gray',
text='ex',
width=12,
command=ex_stop, # BE CAREFUL You were missing a "," here !!!
height=3)
ex_stop_button['font'] = myFont
app.title("Example")
ex_activation_button.pack(side=tk.TOP)
ex_stop_button.pack(side=tk.LEFT)
# app.mainloop()
while True:
try:
app.update()
app.update_idletasks()
except KeyboardInterrupt:
pass

Tkinter GUI - Can't pass argument to script

I've made a GUI with Tkinter and i linked a script to a button. I've also created a browse file option in my GUI and when i select a file i store it's path into a variable named "file". What i'm trying to do is click the button and run the script using the path i stored into the variable "file", but i get a 'no such file or directory error'. The solution must be pretty obvious but i just can't figure it out. Here's my GUI code:
from tkinter import *
from tkinter import filedialog
from tkinter import ttk
from tkinter import messagebox
import subprocess
window = Tk()
#modify window
window.title("Random Title")
window.geometry("600x400")
tab_control = ttk.Notebook(window)
#Creating tabs
tab1 = ttk.Frame(tab_control)
tab2 = ttk.Frame(tab_control)
#Modifying tabs
tab_control.add(tab1, text='Issue')
tab_control.add(tab2, text='Verify')
file = ""
var = StringVar()
var.set("")
w = Entry(tab2,textvariable=var)
w.grid(column=1,row=0)
#Creating button & actions
def issue():
subprocess.call('./issue_script.sh', shell=True)
messagebox.showinfo('Issue Certificate', 'Certificate issued successfully!')
btn = Button(tab1, text="Issue Certificate", command=issue)
btn.grid(column=1, row=5)
def browse():
file = filedialog.askopenfilename(filetypes = (("all files","*.*"),("Text files","*.txt")))
var.set(file)
print(file)
btn2 = Button(tab2, text="Browse", command=browse)
btn2.grid(column=3, row=0)
def verify():
subprocess.call(['./verify_script.sh', file], shell=True)
btn = Button(tab2, text="Verify Certificate", command=verify)
btn.grid(column=1, row=5)
tab_control.pack(expand=1, fill='both')
#event loop
window.mainloop()
I've also added a print(file) command so that i see what is stored in the variable and i get the correct result(the path i selected). Maybe the error is in the line i call the script subprocess.call(['./verify_script.sh', file], shell=True) or in the script itself. Here's the script code:
#!/bin/bash
echo "Verifying certificate..."
cd
python3 cert-issuer/cert-verifier/cert_verifier/verifier.py $1
I actually made it work, but i don't know why it does.
All i changed was instead of calling my script like this
subprocess.call(['./verify_script.sh', var.get()], shell=True)
i omitted the shell=True command and the argument passes correctly into the script.
So i called subprocess.call(['./verify_script.sh', var.get()]) and it works just fine but i can't think why. Any explanation is much appreciated.

Show command line results in Tkinter text widget

I want the output of a Python script in a Tkinter text widget instead of in the command line. I have this script from https://stackoverflow.com/a/665598/3524043:
from Tkinter import *
import subprocess as sub
p = sub.Popen('./Scripts/Speedtest.py',stdout=sub.PIPE,stderr=sub.PIPE, shell=True)
output, errors = p.communicate()
root = Tk()
text = Text(root)
text.pack()
text.insert(END, output)
root.mainloop()
I've added shell=true at the subprocess, cause I had a OSError: [Errno 13] Permission denied.
When I run the program there's only an empty text widget.
Edited with a better solution:
Import the script and call the objects
from Tkinter import *
from Speedtest import ping_speed, download_speed, upload_speed
root = Tk()
text = Text(root)
text.insert(INSERT, ping_speed)
text.insert(END, download_speed)
text.pack()
mainloop()
Based on this answer you can do it fairly simply with the below code:
import subprocess # required for redirecting stdout to GUI
try:
import Tkinter as tk # required for the GUI python 2
except:
import tkinter as tk # required for the GUI python 3
def redirect(module, method):
'''Redirects stdout from the method or function in module as a string.'''
proc = subprocess.Popen(["python", "-c",
"import " + module + ";" + module + "." + method + "()"], stdout=subprocess.PIPE)
out = proc.communicate()[0]
return out.decode('unicode_escape')
def put_in_txt():
'''Puts the redirected string in a text.'''
txt.insert('1.0', redirect(module.get(), method.get()))
if __name__ == '__main__':
root = tk.Tk()
txt = tk.Text(root)
module = tk.Entry(root)
method = tk.Entry(root)
btn = tk.Button(root, text="Redirect", command=put_in_txt)
#layout
txt.pack(fill='both', expand=True)
module.pack(fill='both', expand=True, side='left')
btn.pack(fill='both', expand=True, side='left')
method.pack(fill='both', expand=True, side='left')
root.mainloop()
given that the module is in the same directory. The code returns console output of a method or a function(rightmost entry) in a module(leftmost entry) as a string. It then puts that string in a Text field.
See this answer for Returning all methods/functions from a script without explicitly passing method names.

Reading from an os.pipe() directly to a Tkinter text box

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)

Splitting a TKInter function

I am new to programming in all languages and am having a crack at some python. I have a collection of the functions I've written so far which work so I can refer back to them for when I get stuck. In order to collect them together I have used tkinter to do this for me 'AddToCollection.py'. I can get it working when I run the .py file I created it in, however I would like to import it to any function I wish. Whenever I run the code with the AddToCollection imported it runs immediately. I have tried to split it into functions so that the new window will only open when I call a function but then it can't access the Entry to get the file name. Help would be appreciated. TL;DR how do I stop this code from running when it's imported?
from tkinter import *
from tkinter import messagebox as box
#pops up a box to confirm you want to save it
def SaveConf():
var = box.askokcancel('Save', 'Are you sure you want to save?')
if var == 1:
Save(FileName.get())
#Does the actual saving
def Save(Oldfile):
file = open(Oldfile+".py", 'r')
ToAdd = '\n#- '+ Oldfile +' -------------\n' + file.read() + '\n#-----------'
file.close()
newfile = open("/New.py", 'a+')
newfile.write(ToAdd)
newfile.close()
newwind.destroy()
#setting up items in window
#Initialising window
newwind = Tk()
newwind.title('Save a file')
#to enter filename
Frame3 = Frame()
Frame3.pack(padx=5, pady=5)
FileName = Entry(Frame3)
FileName.pack(side = LEFT)
#click button
SaveBtn2 = Button(Frame3, text = 'Save to Testicles.py', command = SaveConf)
SaveBtn2.pack(side=RIGHT,padx=2)
A common way to structure tkinter application is to subclass Tk and create your widget in the constructor. Here is an example of how you could architecture for your code. It pack your application in a class (subclass of Tk) and provide an helper function launch_app to initialise your class and run mainloop on it.
The point with __name__ == "__main__" is to segregate code executed when the script is run #> python foo.py from code executed when the module is imported. If you want to provide a default behavior when used as script, as well as the ability to use that functionality from another module, put it in a function and call this function from if __name__ == "__main__" block.
I also took the liberty to transform your code toward python coding standard (described in PEP 8)
import tkinter as tk
from tkinter import messagebox as box
#Does the actual saving
def save(oldfile):
file_ = open(oldfile+".py", 'r')
#[...]
newfile.close()
#do not destroy window here
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.title('Save a file')
frame3 = tk.Frame()
frame3.pack(padx=5, pady=5)
self.filename = tk.Entry(frame3)
self.filename.pack(side=tk.LEFT)
#click button
save_btn2 = tk.Button(frame3, text='Save to Testicles.py', command=self.save_conf)
save_btn2.pack(side=tk.RIGHT, padx=2)
def save_conf(self):
var = box.askokcancel('Save', 'Are you sure you want to save?')
if var == 1:
save(self.FileName.get())
self.destroy() #<-- here come the destroy
def launch_app():
app = App()
app.mainloop()
if __name__ == "__main__":
launch_app()
If I'm understanding this correctly, you just want to import and use the functions you've written? I think part of what you're missing is this:
if __name__ == "__main__":
#setting up items in window
#Initialising window
newwind = Tk()
newwind.title('Save a file')
#to enter filename
Frame3 = Frame()
Frame3.pack(padx=5, pady=5)
FileName = Entry(Frame3)
FileName.pack(side = LEFT)
That will prevent this code from running when the file is imported as a module.

Categories