How to print messages in tkinter from subprocess? - python

everyone! I've run into a problem. Let's say I have a script that is launched using subprocess module from tkinter giu like this (params includes the name of the script):
p = Popen(['python.exe'] + params)
During being executed, my script has some messages that I want to be printed in my gui. While I was using console, I just did it with print function like this:
print(f'Connected to {ppm_file}')
What I now need to do, is to make this message be printed in a text widget in tkinter gui.
I suppose it implys using stdout, but I'm a newbie to this concept and find it a little bit hard to understand.
Thanks for your help!

You can capture the console output of the external script and insert the captured output into a Text widget.
Below is an example:
import tkinter as tk
import subprocess
import threading
root = tk.Tk()
logbox = tk.Text(root, width=80, height=20)
logbox.pack()
def capture_output():
# "python -u" make stdout stream to be unbuffered
p = subprocess.Popen(["python", "-u", "other.py"], stdout=subprocess.PIPE)
while p.poll() is None: # process is still running
logbox.insert("end", p.stdout.readline())
logbox.see("end")
logbox.insert("end", "--- done ---\n")
# use thread so that it won't block the application
tk.Button(root, text="Run", command=lambda: threading.Thread(target=capture_output, daemon=True).start()).pack()
root.mainloop()

In the text widget, you can insert by using following method:
txtbox.delete('1.0', END) # to remove any preceding text(ignore if empty)
txtbox.insert(END, 'Connected to {ppm_file}') # insert the text you want
txtbox.insert(END, '\n') # additional insertion for example a newline character

Related

How run a python console script from a hiding console python script?

I'm not very experienced in python and I start building an app with Tkinter.
Like it's a window project I open it with pythonw.exe but it call a console script and I can't display the console of the second script if the first is hidden ...
There is the piece of code which call the second script :
from selenium_script import main
self.btn_downloadAnime = tk.Button(self.frm_addAnime, text='Lunch download script with voiranime.com links',
bg=self.colorBTN, font=22, activebackground=self.colorBG, height=2, width=50,
command=main)
Is this what you are looking for:
main.py:
import tkinter as tk
import subprocess
import sys
COMMAND = "start python selenium_script.py"
def start_new_proc():
proc = subprocess.Popen(COMMAND, close_fds=False, shell=True)
root = tk.Tk()
button = tk.Button(root, command=start_new_proc, text="Start new process")
button.pack()
root.mainloop()
selenium_script.py:
user_input = input(">>> ")
print("You typed this: " + str(user_input))
input("Press enter to exit.")
I start a new process using subprocess.Popen. The new process starts with its own shell (because of the "start" in the COMMAND variable)
Yes, I was missing the notion of process ...
The previous solution works and I found one too :
def open_selenium(self):
subprocess.Popen(args=['python', 'selenium2.py'], stdout=sys.stdout)

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)

redirect stdout to tkinter text widget

I'm trying to redirect the stdout of a function to a tkinter text widget.
The problem I am running into is that it writes each line to a new window instead of listing everything in one.
The function scans a directory and lists any file that is 0k. If no files are 0k it prints that. So, the problem is that if there are 30 0k files in a directory, it will open 30 windows with a single line in each.
Now, I know what the problem is. If you look in my function code Zerok() I am telling it:
if os.stat(filename).st_size==0:
redirector(filename)
I know that every time os.stat sees a file that is 0k it is then sending that to redirector, that's why its a new window for each file.
I just have no idea how to fix it.
Complete code below.
Thanks for the help.
import Tkinter
from Tkinter import *
import tkFileDialog
class IORedirector(object):
'''A general class for redirecting I/O to this Text widget.'''
def __init__(self,text_area):
self.text_area = text_area
class StdoutRedirector(IORedirector):
'''A class for redirecting stdout to this Text widget.'''
def write(self,str):
self.text_area.write(str,False)
def redirector(inputStr):
import sys
root = Tk()
sys.stdout = StdoutRedirector(root)
T = Text(root)
T.pack()
T.insert(END, inputStr)
####This Function checks a User defined directory for 0k files
def Zerok():
import os
sys.stdout.write = redirector #whenever sys.stdout.write is called, redirector is called.
PATH = tkFileDialog.askdirectory(initialdir="/",title='Please select a directory')
for root,dirs,files in os.walk(PATH):
for name in files:
filename=os.path.join(root,name)
if os.stat(filename).st_size==0:
redirector(filename)
else:
redirector("There are no empty files in that Directory")
break
#############################Main GUI Window###########################
win = Tk()
f = Frame(win)
b1 = Button(f,text="List Size")
b2 = Button(f,text="ZeroK")
b3 = Button(f,text="Rename")
b4 = Button(f,text="ListGen")
b5 = Button(f,text="ListDir")
b1.pack()
b2.pack()
b3.pack()
b4.pack()
b5.pack()
l = Label(win, text="Select an Option")
l.pack()
f.pack()
b2.configure(command=Zerok)
win.mainloop()
The fix is simple: don't create more than one redirector. The whole point of the redirector is that you create it once, and then normal print statements will show up in that window.
You'll need to make a couple of small changes to your redirector function. First, it shouldn't call Tk; instead, it should create an instance of Toplevel since a tkinter program must have exactly one root window. Second, you must pass a text widget to IORedirector since it needs to know the exact widget to write to.
def redirector(inputStr=""):
import sys
root = Toplevel()
T = Text(root)
sys.stdout = StdoutRedirector(T)
T.pack()
T.insert(END, inputStr)
Next, you should only call this function a single time. From then on, to have data appear in the window you would use a normal print statement.
You can create it in the main block of code:
win = Tk()
...
r = redirector()
win.mainloop()
Next, you need to modify the write function, since it must write to the text widget:
class StdoutRedirector(IORedirector):
'''A class for redirecting stdout to this Text widget.'''
def write(self,str):
self.text_area.insert("end", str)
Finally, change your Zerok function to use print statements:
def Zerok():
...
if os.stat(filename).st_size==0:
print(filename)
else:
print("There are no empty files in that Directory")
break
The above solution is very complete; I was able to essentially copy and paste it into my code with only one small modification. I'm not entirely sure why, but the StdoutRedirector requires a flush method.
I'm guessing it's because sys.stdout calls a flush() method when it exits, but I'm haven't waded into the docs deep enough to actually understand what that means.
Running the above code in the Jupyter environment caused the code to hang indefinitely until the kernel was restarted. The console kicks the following errors:
sys.stdout.flush()
AttributeError: 'StdoutRedirector' object has no attribute 'flush'
ERROR:tornado.general:Uncaught exception, closing connection.
The simple solution is to make a small change to the StdoutRedirector class by adding a flush method.
class StdoutRedirector(IORedirector):
'''A class for redirecting stdout to this Text widget.'''
def write(self,str):
self.text_area.insert("end", str)
def flush(self):
pass
Thanks to the giants that came before me and offered this very clear explanation.

Display realtime output of a subprocess in a tkinter widget

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

Redirect command line results to a tkinter GUI

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:

Categories