Tkinter读取子流程输出并将其放入gui中



我对python和tkinter还很陌生,所以我很难创建一个脚本,将终端输出读取到tkinter中的标签或Gui中。我到处找了找,找不到任何关于如何做到这一点的教程,很多论坛都有特定或旧的代码,这让它很难适应,尤其是对于初学者来说。我发现的代码看起来最适合我正在努力完成的任务,它是由jfs编写的,唯一的问题是我一直在犯错误,我一辈子都搞不清楚。

这是代码:

import logging
import os
import sys
from subprocess import Popen, PIPE, STDOUT
try:
import tkinter as tk
except ImportError: # Python 3
import tkinter as tk
info = logging.getLogger(__name__).info
# define dummy subprocess to generate some output
cmd = [sys.executable or "python", "-u", "-c", """
import itertools, time
for i in itertools.count():
print(i)
time.sleep(0.5)
"""]
class ShowProcessOutputDemo:
def __init__(self, root):
"""Start subprocess, make GUI widgets."""
self.root = root
# start subprocess
self.proc = Popen(cmd, stdout=PIPE, stderr=STDOUT)
# show subprocess' stdout in GUI
self.root.createfilehandler(
self.proc.stdout, tk.READABLE, self.read_output)
self._var = tk.StringVar() # put subprocess output here
tk.Label(root, textvariable=self._var).pack()
# stop subprocess using a button
tk.Button(root, text="Stop subprocess", command=self.stop).pack()
def read_output(self, pipe, mask):
"""Read subprocess' output, pass it to the GUI."""
data = os.read(pipe.fileno(), 1 << 20)
if not data:  # clean up
info("eof")
self.root.deletefilehandler(self.proc.stdout)
self.root.after(5000, self.stop) # stop in 5 seconds
return
info("got: %r", data)
self._var.set(data.strip(b'n').decode())
def stop(self, stopping=[]):
"""Stop subprocess and quit GUI."""
if stopping:
return # avoid killing subprocess more than once
stopping.append(True)
info('stopping')
self.proc.terminate() # tell the subprocess to exit
# kill subprocess if it hasn't exited after a countdown
def kill_after(countdown):
if self.proc.poll() is None: # subprocess hasn't exited yet
countdown -= 1
if countdown < 0: # do kill
info('killing')
self.proc.kill() # more likely to kill on *nix
else:
self.root.after(1000, kill_after, countdown)
return # continue countdown in a second
self.proc.stdout.close()  # close fd
self.proc.wait()          # wait for the subprocess' exit
self.root.destroy()       # exit GUI
kill_after(countdown=5)
logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s')
root = tk.Tk()
app = ShowProcessOutputDemo(root)
root.protocol("WM_DELETE_WINDOW", app.stop) # exit subprocess if GUI is closed
root.mainloop()
info('exited')

由于我缺乏经验,我不知道如何修复不断抛出的错误。这是不断发生的事情的终端输出。

Traceback (most recent call last):
File "d:codingOtherProjectstestservertkinter-read-async-subprocess-output.py", line 83, in <module>
app = ShowProcessOutputDemo(root)
File "d:codingOtherProjectstestservertkinter-read-async-subprocess-output.py", line 37, in __init__
self.root.createfilehandler(
File "C:Program FilesWindowsAppsPythonSoftwareFoundation.Python.3.9_3.9.1520.0_x64__qbz5n2kfra8p0libtkinter__init__.py", line 2354, in __getattr__
return getattr(self.tk, attr)
AttributeError: '_tkinter.tkapp' object has no attribute 'createfilehandler'

感谢所有花时间阅读这篇文章的人,我真的很感激。

也很抱歉,如果我没有把这个放在正确的论坛上,我仍然在努力理解这个网站,并致力于改进。

谢谢-Connor

试试这个:

import logging
import os
import sys
from subprocess import Popen, PIPE, STDOUT
from threading import Thread
try:
import tkinter as tk
except ImportError: # Python 3
import tkinter as tk
info = logging.getLogger(__name__).info
# define dummy subprocess to generate some output
cmd = [sys.executable or "python", "-u", "-c", """
import itertools, time
for i in itertools.count():
print(i)
time.sleep(0.5)
"""]
class ShowProcessOutputDemo:
def __init__(self, root):
"""Start subprocess, make GUI widgets."""
self.root = root
# start subprocess
self.proc = Popen(cmd, stdout=PIPE, stderr=STDOUT)
# stop subprocess using a button
tk.Button(root, text="Stop subprocess", command=self.stop).pack()
self.label = tk.Label(root) # put subprocess output here
self.label.pack()
# Create a buffer for the stdout
self.stdout_data = ""
# Create a new thread that will read stdout and write the data to
# `self.stdout_buffer`
thread = Thread(target=self.read_output, args=(self.proc.stdout, ))
thread.start()
# A tkinter loop that will show `self.stdout_data` on the screen
self.show_stdout()
def read_output(self, pipe):
"""Read subprocess' output and store it in `self.stdout_data`."""
while True:
data = os.read(pipe.fileno(), 1 << 20)
# Windows uses: "rn" instead of "n" for new lines.
data = data.replace(b"rn", b"n")
if data:
info("got: %r", data)
self.stdout_data += data.decode()
else:  # clean up
info("eof")
self.root.after(5000, self.stop) # stop in 5 seconds
return None
def show_stdout(self):
"""Read `self.stdout_data` and put the data in the GUI."""
self.label.config(text=self.stdout_data.strip("n"))
self.root.after(100, self.show_stdout)
def stop(self, stopping=[]):
"""Stop subprocess and quit GUI."""
if stopping:
return # avoid killing subprocess more than once
stopping.append(True)
info("stopping")
self.proc.terminate() # tell the subprocess to exit
# kill subprocess if it hasn't exited after a countdown
def kill_after(countdown):
if self.proc.poll() is None: # subprocess hasn't exited yet
countdown -= 1
if countdown < 0: # do kill
info("killing")
self.proc.kill() # more likely to kill on *nix
else:
self.root.after(1000, kill_after, countdown)
return # continue countdown in a second
self.proc.stdout.close()  # close fd
self.proc.wait()          # wait for the subprocess' exit
self.root.destroy()       # exit GUI
kill_after(countdown=5)
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s")
root = tk.Tk()
app = ShowProcessOutputDemo(root)
root.protocol("WM_DELETE_WINDOW", app.stop) # exit subprocess if GUI is closed
root.mainloop()
info("exited")

此代码启动一个新线程,该线程从self.proc.stdout读取数据,并在while True循环中将数据写入self.stdout_data。还有一个tkinter循环,它将数据从self.stdout_data中取出并放入Label小部件中。

我没有直接从线程中设置Label的文本,因为有时如果从其他线程调用tkinter,它可能会崩溃。

另一件事:我删除了StringVar,因为我可以改为使用:<tkinter.Label>.config(text=<new text>)

我也有问题,这是我的解决方案。我把它发布在[https://github.com/ianfun/notes/blob/main/editor/main.py]。

data.py由build.py.生成

我在python中找不到类似vscode的grate终端。

注意:使用右键选择

你可以阅读idlelib运行.py

最新更新