首页 > 解决方案 > 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:\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'

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

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

谢谢你-康纳

标签: pythontkintersubprocess

解决方案


尝试这个:

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和写入数据。还有一个 tkinter 循环将数据从 中取出并放入小部件中。self.stdout_datawhile Trueself.stdout_dataLabel

我没有直接Label从线程设置 's 文本,因为如果您从不同的线程调用它,有时 tkinter 可能会崩溃。

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


推荐阅读