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

Tkinter read subprocess output and put it into the 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:\coding\OtherProjects\testserver\tkinter-read-async-subprocess-output.py", line 83, in <module>
    app = ShowProcessOutputDemo(root)
  File "d:\coding\OtherProjects\testserver\tkinter-read-async-subprocess-output.py", line 37, in __init__
    self.root.createfilehandler(
  File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.9_3.9.1520.0_x64__qbz5n2kfra8p0\lib\tkinter\__init__.py", line 2354, in __getattr__
    return getattr(self.tk, attr)
AttributeError: '_tkinter.tkapp' object has no attribute 'createfilehandler'

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

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

谢谢你 - 康纳

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

data.py 由 build.py 生成。

我在 python 中找不到像 vscode 这样的 grate 终端。

注意:使用右键到select

你可以阅读 idlelib run.py

试试这个:

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: "\r\n" instead of "\n" for new lines.
            data = data.replace(b"\r\n", 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>) 代替。