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.
Related
this is my first time here so if I break some unwritten rules please don't shoot me
I've been trying build an app for editing, reading and opening .ahk files. The last one gives me problems.
this code kind of does what I want except it fires the command immediately after opening the app.
from tkinter import *
from tkinter import filedialog
import os
import threading
root = Tk()
def openfile():
os.system( r'\Users\merijn\PycharmProjects\infoprojectP3\venv\Scripts\ahkScript1.ahk')
def func():
print("hello")
btn1 = Button(text='programma', command=threading.Thread(target=openfile).start())
btn2 = Button(text='ander ding', command=func)
btn1.pack()
btn2.pack()
root.mainloop()
Any help would be much appreciated!
Change the following line (it just assigns the result of threading.Thread(...) to command option):
Button(text="run program1", command=threading.Thread(target=lambda: os.system(r'\Users\merijn\PycharmProjects\infoprojectP3\venv\Scripts\ahkScript1.ahk').start()))
to
Button(text="run program1", command=lambda: threading.Thread(target=lambda: os.system(r'\Users\merijn\PycharmProjects\infoprojectP3\venv\Scripts\ahkScript1.ahk')).start())
or
Button(text="run program1", command=lambda: threading.Thread(target=os.system, args=[r'\Users\merijn\PycharmProjects\infoprojectP3\venv\Scripts\ahkScript1.ahk']).start())
Same for buttons of run program2 and run program3.
Update: based on your updated code, you need to change the following line:
btn1 = Button(text='programma', command=threading.Thread(target=openfile).start())
to
btn1 = Button(text='programma', command=lambda: threading.Thread(target=openfile).start())
So I'm trying to use a Tkinter window as an output log. My main program is a command line interface, so if the output from the "side programs" that run fills up the python console, it can ruin the user experience.
I have the Tkinter output log program in a seperate file, which I import into my main file. This is the gyst of the Tkinter file I created:
import time
from datetime import datetime
root = Tk()
root.configure(background='black')
root.geometry('700x460')
console = Listbox(root, width=40000, height=30000, font=('Lucida Console', 14), relief=FLAT, bg='black',
fg='white',
borderwidth=0, highlightbackground='black', selectbackground='black',
selectforeground='white')
console.pack()
def log(what_to_log):
now = datetime.now()
console.insert('[' + now.strftime("%H:%M:%S") + ']: ' + what_to_log)
root.mainloop()
So I import it into my main program like so:
# this is the file with the Tkinter code
import Logs
Logs.log('Test')
The issue is that the main program doesn't continue running after importing Logs, it just stops, and continues to run the mainloop in the Logs file.
So my question is, how could I get both files to continue running, while also being able to update the Tkinter listbox via the log function??
You should not execute root.mainloop() as it will block your console application. Use root.update() at the end of log() to force tkinter to update the list box. Also there is syntax error in console.log(...).
Below is a modified Logs.py:
from tkinter import *
from datetime import datetime
root = Tk()
root.configure(background='black')
root.geometry('700x460')
console = Listbox(root, font=('Lucida Console', 14), relief=FLAT, bg='black', fg='white',
borderwidth=0, highlightbackground='black', selectbackground='black',
selectforeground='white')
console.pack(fill='both', expand=1)
def log(what_to_log):
now = datetime.now()
console.insert('end', '['+now.strftime("%H:%M:%S")+']: '+what_to_log)
console.see('end') # make sure last log is visible
root.update() # force tkinter to update
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
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.
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()