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)
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 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.
I have a gui in python3 that I want to call another python script using subprocess (or something better?). I have the main gui full screen, which is what I want. The problem is, when I launch the subprocess, it initially becomes the topmost window on LXDE, so far so good. You can click on the main gui in the background which brings the main gui to topmost. This is what is expected from the window manager, but this covers the subprocess that I currently have blocking the main gui. I need to prevent the main gui from accepting focus while the subprocess is running or keeping the subprocess as topmost window.
maingui.py
#!/usr/bin/env python3
import tkinter as tk
import subprocess
def on_escape(event=None):
print("escaped")
root.destroy()
def do_something():
child = subprocess.run(["python3", "childgui.py"], shell=False)
######################################################################
busy=False
root = tk.Tk()
root.title("My GUI")
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
#don't run this as the subprocess blocks #root.attributes("-fullscreen", True) # run fullscreen
root.focus_set()
root.bind("<Escape>", on_escape)
#doesn't work#root.protocol("WM_TAKE_FOCUS", on_focus)
canvas = tk.Canvas(root)
canvas.grid(row=0)
cbutton = tk.Button(root, text = "Run Child", width=50, height=50, bg = "green", compound = "top", command = do_something)
lab = tk.Label(canvas, text = 'Output Here')
lab.grid(row=0, column=1)
cbutton.grid(row=1, column=0)
# --- start ---
root.mainloop()
childgui.py
from tkinter import *
class app(Frame):
def __init__(self, master):
Frame.__init__(self, master=None)
self.master.title("Child Process")
self.master.geometry("500x200")
Button(self.master, text="Grandchild", command=self.dialog).pack()
self.data = StringVar()
self.data.set("Here is the data")
Label(self.master, textvariable=self.data).pack()
def dialog(self):
d = MyDialog(self.master, self.data, "Grandchild", "Enter Data")
self.master.wait_window(d.top)
class MyDialog:
def __init__(self, parent, data, title, labeltext = '' ):
self.data = data
self.rt = parent
self.top = Toplevel(parent)
self.top.transient(parent)
self.top.grab_set()
self.top.geometry("300x100")
if len(title) > 0: self.top.title(title)
if len(labeltext) == 0: labeltext = 'Data'
Label(self.top, text=labeltext).pack()
self.top.bind("<Return>", self.ok)
self.e = Entry(self.top, text=data.get())
self.e.bind("<Return>", self.ok)
self.e.bind("<Escape>", self.cancel)
self.e.pack(padx=15)
self.e.focus_set()
b = Button(self.top, text="OK", command=self.ok)
b.pack(pady=5)
def ok(self, event=None):
print ("The data:", self.e.get())
self.data.set(self.e.get())
self.top.destroy()
def cancel(self, event=None):
self.top.destroy()
def main():
root = Tk()
a = app(root)
root.mainloop()
if __name__ == '__main__':
main()
Edit: I should have mentioned the child gui is an app that is not mine. I only created this one for the example code to show the issue. While self.top.attributes may work, the actual child app is pretty large and I don't want to change it if I can avoid it. I don't have problems getting the focus, which is actually the problem. The main gui gets the focus back causing the subprocess to go behind. When the main gui is set to fullscreen (delete this to see #don't run this as the subprocess blocks #) the main gui is now stuck waiting for the child to close which you can't get to with the mouse
I'm new to python coding and I have been working on a project which could click on an image based on a chosen color. I have been using a program which loops the search 50 times when I click the start button. However, I have been trying to implement a stop button, but the problem is that my code freezes when the loop is running. Any ideas?
I have heard to try threading but it seems very complicated and I have been unable to follow any tutorials properly in relation to my code. By the way, the image searched has been testing images I've been using stored inside the program files.
from imagesearch import *
import pyautogui
import tkinter as tk
from tkinter import *
from tkinter.ttk import *
import time
import threading
# ---Defined Programs---
def run():
global enterColor
enterColor = str(enterColorField.get())
program(enterColor)
def program(color):
whitePos = imagesearch_numLoop(str(color) + ".PNG", 0, 50)
pyautogui.moveTo(whitePos[0] + 20, whitePos[1] + 10)
pyautogui.click()
def stop():
print("Placeholder")
# ---Main Runner---
window = tk.Tk()
window.geometry("250x250")
window.configure(background="#181b54")
app = tk.Frame(window)
app.grid()
enterColorLabel = tk.Label(window, text="Enter Color:", bg="#181b54", fg="white")
enterColorLabel.place(x=10, y=50)
enterColorField = Combobox(window)
enterColorField['values'] = ("Black", "White")
enterColorField.current("0") # set the selected item
enterColorField.place(x=10, y=70)
submitButton = tk.Button(window, text="Start", bg="#66ff00", command=run)
submitButton.place(x=10, y=130)
stopButton = tk.Button(window, text="Stop", bg="red", command=stop)
stopButton.place(x=50, y=130)
window.mainloop()
#---New Python Script---
import cv2
import numpy as np
import pyautogui
import random
import time
def imagesearch_numLoop(image, timesample, maxSamples, precision=0.8):
pos = imagesearch(image, precision)
count = 0
while pos[0] == -1:
print(image+" not found, waiting")
count = count + 1
if count>maxSamples:
break
pos = imagesearch(image, precision)
return pos
Whenever clicking start, the whole code freezes. I can't even (x) out.
Here's a hopefully simple multiprocessing recipe that will work for you. We'll have three main functions. The first will be an example loop that you would put your processing inside. I included arguments in the function to show you that it's possible to pass args and kwargs while using multiprocessing.
def loop(a, b, c, d):
# Will just sleep for 3 seconds.. simulates whatever processing you do.
time.sleep(3)
return
Next is a function we will use to queue the multiprocessing process.
def queue_loop():
p = multiprocessing.Process(target = loop,
args = (1, 2),
kwargs = {"c": 3, "d": 4})
# You can pass args and kwargs to the target function like that
# Note that the process isn't started yet. You call p.start() to activate it.
p.start()
check_status(p) # This is the next function we'll define.
return
Then, you may be interested in knowing the status of your process throughout its execution. For example it is sometimes desirable to disable certain buttons while a command is being run.
def check_status(p):
""" p is the multiprocessing.Process object """
if p.is_alive(): # Then the process is still running
label.config(text = "MP Running")
mp_button.config(state = "disabled")
not_mp_button.config(state = "disabled")
root.after(200, lambda p=p: check_status(p)) # After 200 ms, it will check the status again.
else:
label.config(text = "MP Not Running")
mp_button.config(state = "normal")
not_mp_button.config(state = "normal")
return
Throwing this all together into one snippet:
import tkinter as tk
import multiprocessing
import time
def loop(a, b, c, d):
# Will just sleep for 3 seconds.. simulates whatever processing you do.
time.sleep(3)
return
def queue_loop():
p = multiprocessing.Process(target = loop,
args = (1, 2),
kwargs = {"c": 3, "d": 4})
# You can pass args and kwargs to the target function like that
# Note that the process isn't started yet. You call p.start() to activate it.
p.start()
check_status(p) # This is the next function we'll define.
return
def check_status(p):
""" p is the multiprocessing.Process object """
if p.is_alive(): # Then the process is still running
label.config(text = "MP Running")
mp_button.config(state = "disabled")
not_mp_button.config(state = "disabled")
root.after(200, lambda p=p: check_status(p)) # After 200 ms, it will check the status again.
else:
label.config(text = "MP Not Running")
mp_button.config(state = "normal")
not_mp_button.config(state = "normal")
return
if __name__ == "__main__":
root = tk.Tk()
mp_button = tk.Button(master = root, text = "Using MP", command = queue_loop)
mp_button.pack()
label = tk.Label(master = root, text = "MP Not Running")
label.pack()
not_mp_button = tk.Button(master = root, text = "Not MP", command = lambda: loop(1,2,3,4))
not_mp_button.pack()
root.mainloop()
The result is that when you click the "Using MP" button, the command buttons will be disabled and the process will be started without freezing your UI. Clicking the "Not MP" button will start the function like 'normal' and will freeze your UI as you noticed in your own code.
A simple answer is you cannot use while loop in GUI design.
But you can use the method .after(delay, callback=None) instead.
Here is an example:
from tkinter import *
root = Tk()
def loop():
print("Hi!")
root.after(1000, loop) # 1000 is equal to 1 second.
root.after(1000, loop) # This line is to call loop() in 1 second.
root.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