I have gone through many solutions on stackoverflow, but none was helpful to me. I'm stuck on implementing cmd into tkinter to see output inside of gui and be able to enter values there. I appreciate any help, thanks for advance!
from subprocess import Popen
from tkinter import Tk, Button, messagebox, Label
from PIL import ImageTk, Image
gui = Tk(className='IDPass')
gui.geometry('500x500')
gui.iconbitmap('Turnstile/icons/mini_logo.ico')
img = ImageTk.PhotoImage(Image.open('Turnstile/icons/logo.png'))
panel = Label(gui, image=img)
def run_server():
global process
process = Popen(['python', 'C:/Test/Turnstile/manage.py', 'runserver'])
def run_rfid_scanner():
global process
process = Popen('python C:/Test/Turnstile/rfid_scanner.py')
def run_face_scanner():
global process
process = Popen('python C:/Test/Turnstile/face_scanner.py')
def run_photo_deleter():
global process
process = Popen('python C:/Test/Turnstile/photo_deleter.py')
def run_face_recognizer():
global process
process = Popen('python C:/Test/Turnstile/face_recognizer.py')
def stop_program():
process.kill()
messagebox.showinfo('Информационное окно', 'Программа остановлена')
server = Button(gui, text='Запустить сервер', command=run_server, bg='green')
rfid_scanner = Button(gui, text='Запустить RFID сканер', command=run_rfid_scanner, bg='green')
face_scanner = Button(gui, text='Добавить фото для сканирования', command=run_face_scanner, bg='green')
face_recognizer = Button(gui, text='Начать распознавание лица', command=run_face_recognizer, bg='green')
photo_deleter = Button(gui, text='Удалить фото пользователя', command=run_photo_deleter, bg='grey')
stop_programm = Button(gui, text='Остановить выполнение программы', command=stop_program, bg='grey')
panel.pack()
server.pack()
rfid_scanner.pack()
face_scanner.pack()
face_recognizer.pack()
photo_deleter.pack()
stop_programm.pack()
gui.mainloop()
This is how I want to see it
One of the way is:
create a Text box to show the command output
create a threaded task to get the process output and put the output in a queue
create a periodic task to get output from the queue and insert it into text box
redirect command output using subprocess.PIPE
import sys
import threading
from queue import Queue
from subprocess import Popen, PIPE
from tkinter import Tk, Button, messagebox, Label, Text
...
process = None
queue = Queue()
def run_server():
global process
if process:
process.terminate()
#process = Popen(['python', 'C:/Test/Turnstile/manage.py', 'runserver'])
process = Popen([sys.executable, '-u', 'C:/Test/Turnstile/manage.py', 'runserver'], stdout=PIPE, bufsize=1, text=True)
...
output = Text(gui, width=100, height=20)
output.pack(padx=20, pady=20)
def monitor_output(q):
while True:
if process and process.stdout:
msg = process.stdout.readline()
if msg:
q.put(msg)
def check_output(q):
while not q.empty():
output.insert('end', q.get())
output.see('end')
gui.after(10, check_output, q)
threading.Thread(target=monitor_output, args=[queue], daemon=True).start()
check_output(queue)
gui.mainloop()
Note that I have used sys.executable instead of 'python' to make sure same Python interpreter is used.
Try this:
from subprocess import Popen, PIPE
from threading import Thread, Lock
import tkinter as tk
class TkinterPopen(tk.Text):
def __init__(self, master, state="disabled", **kwargs):
super().__init__(master, state=state, **kwargs)
self.commands = []
self.proc = None
self.running = True
self.stdout_buffer = ""
self.stdout_buffer_lock = Lock()
def stdout_loop(self, last_loop:bool=False) -> None:
with self.stdout_buffer_lock:
# Get the data and clear the buffer:
data, self.stdout_buffer = self.stdout_buffer, ""
state = super().cget("state")
super().config(state="normal")
super().insert("end", data)
super().see("end")
super().config(state=state)
if self.proc is None:
if len(self.commands) == 0:
# If we are done with all of the commands:
if last_loop:
return None
super().after(100, self.stdout_loop, True)
else:
# If we have more commands to do call `start_next_proc`
self.start_next_proc()
else:
super().after(100, self.stdout_loop)
def start_next_proc(self) -> None:
command = self.commands.pop(0) # Take the first one from the list
self.proc = Popen(command, stdout=PIPE)
new_thread = Thread(target=self.read_stdout, daemon=True)
new_thread.start()
self.stdout_loop()
def run_commands(self, commands:list) -> None:
self.commands = commands
self.start_next_proc()
def read_stdout(self):
while self.proc.poll() is None:
self._read_stdout()
self._read_stdout()
self.proc = None
def _read_stdout(self) -> None:
line = self.proc.stdout.readline()
with self.stdout_buffer_lock:
self.stdout_buffer += line.decode()
if __name__ == "__main__":
def start_echo():
command = ["echo", "hi"]
tkinter_popen.run_commands([command])
def start_ping():
# For linux use "-c". For windows use "-n"
command = ["ping", "1.1.1.1", "-n", "3"]
tkinter_popen.run_commands([command])
root = tk.Tk()
tkinter_popen = TkinterPopen(root)
tkinter_popen.pack()
button = tk.Button(root, text="Run echo", command=start_echo)
button.pack()
button = tk.Button(root, text="Run ping", command=start_ping)
button.pack()
root.mainloop()
I think this is the functionality that you wanted. The code is similar to #acw1668 but I read stdout in another thread and out the data in a queue named self.stdout_buffer.
This is just a copy of the answer I gave here.
Related
i'm currently need help of my python system. i made a simple GUI that insist of a simple GUI with buttons that calls out other python scripts and print it to the geometry. however, i have an issue where i could not get the system to print anything out of the GUI or input anything on the tkinter geometry when the python script needed of any input from the User. the GUI fully works when there are no input needed from the users.
from the code below, i shown my full GUI system that calls out the 2 python scripts whenever button is pressed. i would like to make the tkinter geometry to accept inputs directly to the tkinter geometry and send its input to the python script and display its output back again to the tkinter geometry. How do i improve this system or address this problem?
GUI.py
from tkinter import *
from subprocess import *
from threading import *
import time
import subprocess
#text1.insert(END, your stuff here) to put output to GUI
#GUI
root = Tk()
root.title('System 1')
root.geometry('1000x900')
root.configure(background='black')
text1 = Text(root, width= 100, height = 40)
text1.pack()
#command list
def command():
try:
child = Popen(['python', '-u', 'CMDping.py'], stdout=PIPE)
text1.delete("1.0", END)
for line in iter(child.stdout.readline, ''):
text1.insert(INSERT, line)
text1.see(END)
text1.update_idletasks()
child.stdout.close()
child.wait()
except CalledProcessError:
text1.insert(END, "File Not Found!")
def command1():
try:
bttn1.destroy()
pythonfile1 = 'python NetworkCommands.py'
p1 = subprocess.Popen(pythonfile1, shell=True)
out, err = p1.communicate()
except CalledProcessError:
text1.insert(END, "File Not Found!")
#Threading for command Function
def Threading():
t1=Thread(target=command)
t1.start()
root.update()
def Threading1():
t1=Thread(target=command1)
t1.start()
root.update()
#buttons
bttn= Button(root,bg="black",fg="white",highlightcolor="white", text="Diagnose", command=Threading)
bttn.pack(side=LEFT, padx=5,pady=0)
bttn1= Button(root,bg="black",fg="white",highlightcolor="white", text="Flush_DNS", command=Threading1)
bttn1.pack(side=LEFT, padx=5,pady=0)
#Clock
class Clock:
def __init__(self):
self.time1 = ''
self.time2 = time.strftime('%H:%M:%S')
self.mFrame = Frame()
self.mFrame.pack(side=TOP,expand=YES,fill=X)
self.watch = Label(self.mFrame, text=self.time2, font=('times',12,'bold'))
self.watch.pack()
self.changeLabel() #first call it manually
def changeLabel(self):
self.time2 = time.strftime('%H:%M:%S')
self.watch.configure(text=self.time2)
self.mFrame.after(200, self.changeLabel) #it'll call itself continuously
obj1 = Clock()
root.mainloop()
CMDping.py
import subprocess
def ping():
command = input("Enter IP Address to ping: ")
time.sleep(2)
os.system("ping " + command)
ping()
I have a tkinter GUI with a text box and run button. Pressing the run button turns it to yellow and starts a subroutine that prints a few numbers. Text output from the subroutine is redirected to the GUI text box. However, after creating a standalone executable file with pyinstaller, it no longer works. Pressing the run button doesn't seem to start the subprocess. It does turn yellow, but no text appears in the text box and it seems to start another instance of the main program - a second GUI appears after about 10 seconds which is how long it takes for the initial GUI to appear. The run button stays yellow on the initial GUI.
I've seen a bit online about other people having issues with subprocesses not running after pyinstaller, but most of the solutions seem to be to make sure stdout, stdin are set to subprocess.PIPE which I have, so I'm at a bit of a loss what to try next.
I'm creating my standalone with this:
pyinstaller --onefile simpleGUI.py
My subprocess file, testsubprocess.py is:
import time
for i in range(3):
print("%d.%d" % divmod(i, 10))
time.sleep(0.5)
My main GUI file, simpleGUI.py, is:
import sys
import subprocess
from threading import Thread
import tkinter as tk
from queue import Queue, Empty
def iter_except(function, exception):
try:
while True:
yield function()
except exception:
return
class DisplaySubprocessOutputDemo:
def __init__(self, root):
self.root = root
width=600
height=350
xloc=0
yloc=10
self.root.geometry('%dx%d+%d+%d' % (width, height, xloc, yloc))
self.statustext = tk.Text(self.root, height=4, width=30)
self.statustext.grid(row=3, column=1)
self.startbutton = tk.Button(self.root, text = 'Start', command=self.startprocess, bg='green', activebackground = 'orange')
self.startbutton.config(height = 2, width = 15)
self.startbutton.grid(row = 5, column=0,sticky='E')
self.startbuttonpresses = 0
exitbutton = tk.Button(self.root, text = 'Exit', command=self.quit, bg='red')
exitbutton.config(height = 2, width = 15)
exitbutton.grid(row = 5, column=4, sticky='E')
def startprocess(self):
self.startbuttonpresses = self.startbuttonpresses+1
if self.startbuttonpresses == 1:
self.startbutton.configure(bg='yellow')
self.startbutton.configure(text='Stop')
self.process = subprocess.Popen([sys.executable, "-u", "testsubprocess.py"], shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT)
q = Queue(maxsize=1024)
t = Thread(target=self.reader_thread, args=[q])
t.daemon = True
t.start()
self.updatetext(q)
else:
self.startbuttonpresses = 0
self.process.kill()
self.startbutton.configure(bg='green')
self.startbutton.configure(text='Start')
def reader_thread(self, q):
try:
with self.process.stdout as pipe:
for line in iter(pipe.readline, b''):
q.put(line)
finally:
q.put(None)
def updatetext(self, q):
for line in iter_except(q.get_nowait, Empty): # display all content
if line is None:
self.startbuttonpresses = 0
self.startbutton.configure(bg='green')
self.startbutton.configure(text='Start')
return
else:
self.statustext.insert(tk.END, line)
self.root.after(400, self.updatetext, q)
def quit(self):
try:
self.process.kill()
except Exception:
pass
self.root.destroy()
root = tk.Tk()
app = DisplaySubprocessOutputDemo(root)
root.protocol("WM_DELETE_WINDOW", app.quit)
root.eval('tk::PlaceWindow %s center' % root.winfo_pathname(root.winfo_id()))
root.mainloop()
When the process is executing with PyInstaller sys.executable is pointing to the exe file created and not to the python.exe like in the interpreter so the Popen will not work.
You can try to create another executable for the testsubprocess.py and point Popen to it or use multiprocessing (don’t forget to use freeze_support if you do https://docs.python.org/3/library/multiprocessing.html#multiprocessing.freeze_support)
I want to write a program using Tkinter GUI, which performs a certain task in a thread. When an error occurs during the execution of the task, the program should pop up an error-window, for example using tkMessageBox. Of course the program crashes when it should pop up the messagebox from the new thread, since Tkinter isn't thread safe, but I hope that there is a solution to this problem.
Here is an easy example of a not working code (edited):
from tkinter import *
import tkMessageBox
from threading import *
import time
class App:
def __init__(self, master):
frame = Frame(master)
frame.pack()
self.button = Button(frame, text = "Task", command = self.thread_task)
self.button.pack(side=LEFT)
def thread_task(self):
thread = Thread(target = self.task)
thread.start()
def task(self):
#perform task...
time.sleep(1) #Just as a filler in the code
#command to open an error popup, e.g. tkMessageBox.showerror("Error", "Problem occured")
root = Tk()
app = App(root)
root.mainloop()
You can use try-except inside a thread.
Here is an example based on your code (edited to work with Python 2):
from Tkinter import *
import tkMessageBox
from threading import *
class App:
def __init__(self, master):
frame = Frame(master)
frame.pack()
self.button = Button(frame, text="Task", command=self.thread_task)
self.button.pack(side=LEFT)
def thread_task(self):
thread = Thread(target=self.task)
thread.start()
def task(self):
try:
time.sleep(1)
self.button["text"] = "Started"
self.button["state"] = "disabled"
raise ValueError # or anything else
except:
tkMessageBox.showerror("Error", "Problem occured")
self.button["text"] = "Task"
self.button["state"] = "normal"
if __name__ == "__main__":
root = Tk()
app = App(root)
root.mainloop()
Oke i got the problem days ago and someone helped me with treading but my code was really ugly (im totaly new to coding) now i try to make it better and on an smarter way but now my gui get a frezze all time.
i tryed to do it on the way like my last code but it dosent work this time.
What have i to do this time i cant understand it but want understand it.
some Helpful Tips and tricks ?
Or better ways to do it smart, faster, and more powerfull, or mybae the gui more Beautyfule ?
import time
import sys
from tkinter import *
import threading
root = Tk()
root.geometry("600x400")
global start
start = 1
def startUp():
user_input()
thr = threading.Thread(target=user_input)
thr.start()
def user_input():
global nameInput
global start
nameInput = textBox.get("1.0","end-1c")
start = 0
if start < 1:
while True:
apex = ApexLegends("APIKey")
player = apex.player(nameInput)
print("Gesamt Kills: " + player.kills + "\n" + 'Gesamt Damage: ' + player.damage)
time.sleep(3)
else:
print("stop")
anleitung=Label(text="Gib einen Spielernamen ein und drücke Start")
anleitung.pack()
textBox=Text(root, height=1, width=30)
textBox.pack()
startButton=Button(root, height=1, width=10, text="Start", command=lambda:startUp())
startButton.pack()
Tkinter isn't designed to be accessed by more than one thread. Here is an excellent answer by one of the guys who has a very deep understainding of how tcl & tk works (the libraries that tkinter depends on), explaining why this is so.
Callback to python function from Tkinter Tcl crashes in windows
This is the first of the two paragraphs in that answer:
Each Tcl interpreter object (i.e., the context that knows how to run a Tcl procedure) can only be safely used from the OS thread that creates it. This is because Tcl doesn't use a global interpreter lock like Python, and instead makes extensive use of thread-specific data to reduce the number of locks required internally. (Well-written Tcl code can take advantage of this to scale up very large on suitable hardware.)
def startUp():
user_input()
thr = threading.Thread(target=user_input)
thr.start()
This doesn't look right. You're calling user_input() in both the main thread and the child thread. If you only want it to run in the child thread, don't call it that first time.
def startUp():
thr = threading.Thread(target=user_input)
thr.start()
Hi #Trason I've played with your code and I suggest an oo approach.
In the code below I've try to adapt a functional script to your code.
First of all I've use a variable as
self.nameInput = tk.IntVar()
to store the user input on
tk.Entry(w, bg='white', textvariable=self.nameInput).pack()
I've use an Entry widget instead of Text but it should be the same.
Furthermore I use a class to manage thread start and stop operation.
Look, I changed your 'start' variable with 'check' because start is a reserved word
in python thread module.
I tried to recreate the functionality of your code.
Try to import and use your ApexLegends.
import tkinter as tk
import threading
import queue
import datetime
import time
class MyThread(threading.Thread):
def __init__(self, queue,nameInput):
threading.Thread.__init__(self)
self.queue = queue
self.nameInput = nameInput
self.check = True
def stop(self):
self.check = False
def run(self):
while self.check:
# apex = ApexLegends("APIKey")
#player = apex.player(self.nameInput.get())
x = "Gesamt Kills: " + "player.kills" + "\n" + 'Gesamt Damage: ' + "player.damage"+ "\n"
s = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
msg = "{} datetime: {} nameInput {}".format(x,s,self.nameInput.get())
time.sleep(3)
self.queue.put(msg)
class App(tk.Frame):
def __init__(self,):
super().__init__()
self.master.title("Hello World")
self.master.protocol("WM_DELETE_WINDOW",self.on_close)
self.queue = queue.Queue()
self.my_thread = None
self.nameInput = tk.IntVar()
self.init_ui()
def init_ui(self):
self.f = tk.Frame()
w = tk.Frame()
tk.Label(w, text = "Gib einen Spielernamen ein und drücke Start").pack()
tk.Entry(w, bg='white', textvariable=self.nameInput).pack()
w.pack(side=tk.LEFT, fill=tk.BOTH, expand=0)
w = tk.Frame()
tk.Button(w, text="Start", command=self.startUp).pack()
tk.Button(w, text="Stop", command=self.stop_thread).pack()
tk.Button(w, text="Close", command=self.on_close).pack()
w.pack(side=tk.RIGHT, fill=tk.BOTH, expand=0)
self.f.pack(side=tk.LEFT, fill=tk.BOTH, expand=0)
def startUp(self):
if (threading.active_count()!=0):
self.my_thread = MyThread(self.queue,self.nameInput)
self.my_thread.start()
self.periodiccall()
def stop_thread(self):
if(threading.active_count()!=1):
self.my_thread.stop()
def periodiccall(self):
self.checkqueue()
if self.my_thread.is_alive():
self.after(1, self.periodiccall)
else:
pass
def checkqueue(self):
while self.queue.qsize():
try:
ret = self.queue.get(0)
msg = "%s"%(ret)
print(msg)
except queue.Empty:
pass
def on_close(self):
if(threading.active_count()!=0):
if self.my_thread is not None:
self.my_thread.stop()
self.master.destroy()
if __name__ == '__main__':
app = App()
app.mainloop()
I've searched the whole internet to answer my problem, but nobody seems to have the same one: I'm trying to update my tkinter GUI dynamically from a subprocess output, which works fine, if I'm starting my GUI inside eclipse. BUT if I'm running it in the file explorer or in visual studio, the 'stdout.readline' command waits, until the subprocess is finished. Only then the complete output is printed to my textarea... I am working with a thread and I've tried 2 ways: one is shown in 'App.py', the other one is threading the 'read_update' method instead (and not using 'reader_thread' and 'update' methods).
Interesting sidenote: the sys.argv command in Test.py does not return my string "var_test". Can anyone tell me why?
My Classes are:
GUI.py
import Tkinter as tk
from App import App
if __name__ == '__main__':
root = tk.Tk()
app = App(root)
root.protocol("WM_DELETE_WINDOW", app.quit)
root.mainloop()
App.py
#App.py
import Tkinter as tk
import tkFont as tkfont
import subprocess
from subprocess import Popen
from subprocess import PIPE
from itertools import islice
from threading import Thread
from ttk import Scrollbar
from Tkinter import *
from Queue import Queue, Empty
class App():
def __init__(self, root):
self.root = root
self.root.title_font = tkfont.Font(family = "Helvetica", size = 18, weight = "bold", slant = "italic")
Grid.columnconfigure(self.root, 5, weight = 1)
button_ok = tk.Button(self.root, text = "OK", width = 10, command = lambda: self.on_okay())
button_ok.grid(row = 7, column = 0, padx = (20,0), pady = 10, sticky = W)
xscrollbar = Scrollbar(self.root, orient=HORIZONTAL)
xscrollbar.grid(row=8, column=1, columnspan=4, sticky=E + W)
yscrollbar = Scrollbar(self.root, orient=VERTICAL)
yscrollbar.grid(row=8, column=5, sticky=N + S)
self.textarea = Text(self.root, wrap=NONE, bd=0,
xscrollcommand=xscrollbar.set,
yscrollcommand=yscrollbar.set)
self.textarea.grid(row=8, column=1, columnspan=4, rowspan=1,
padx=0, sticky=E + W + S + N)
def on_okay(self):
self.textarea.delete("1.0", END)
exec_path = r"\Test.py" #insert location of Test.py
self.process = subprocess.Popen([exec_path, 'var_test'], shell = True, stdout = subprocess.PIPE, stderr = subprocess.STDOUT)
self.q = Queue(maxsize = 1024)
t = Thread(target=self.reader_thread, args=[self.q])
t.daemon = True
t.start()
self.update(self.q)
def reader_thread(self, q):
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):
for line in self.iter_except(q.get_nowait, Empty):
if line is None:
#self.quit()
return
else:
self.textarea.insert(INSERT, line)
self.textarea.yview(END)
break
self.root.after(40, self.update, q)
def iter_except(self, function, exception):
try:
while True:
yield function()
except exception:
return
def read_update(self):
while True:
line = self.process.stdout.readline()
if line == "" and self.process.poll() != None:
break
elif line == "":
pass
else:
self.textarea.insert(INSERT, line)
self.textarea.yview(END)
self.textarea.update_idletasks()
def quit(self):
try:
self.process.kill()
except AttributeError:
pass
finally:
self.root.destroy()
Test.py
import sys
from time import sleep
var = sys.argv
print var
for i in range(1, 10):
print i
print "finished printing numbers"
sleep(10)
print "finished"
Thank you for your help! I'm pretty desperate 'cause I've been trying to solve this problems for many hours now...
use sys.stdout.flush() after print