简体   繁体   English

Tkinter 读取子进程 output 并放入gui

[英]Tkinter read subprocess output and put it into the gui

I'm pretty new to python and tkinter so I'm struggling with creating a script that reads the terminal output into a label or Gui in tkinter. I'm pretty new to python and tkinter so I'm struggling with creating a script that reads the terminal output into a label or Gui in tkinter. I've searched around and can't find any tutorials on how to do it, a lot of forums have specific or old code which makes it really difficult to adapt especially for a beginner.我四处搜索,找不到任何关于如何做到这一点的教程,很多论坛都有特定或旧代码,这使得它很难适应,尤其是对于初学者。 The code that I found that looks like it will work best for what I'm trying to accomplish was made by jfs, the only problem is that I keep getting errors which for the life of me I cant figure out.我发现它看起来最适合我想要完成的代码是由 jfs 编写的,唯一的问题是我不断收到错误,这对于我的生活我无法弄清楚。

Here is the code:这是代码:

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')

Due to my lack of experience, i don't know how to fix the errors that keep being thrown.由于我缺乏经验,我不知道如何解决不断抛出的错误。 Here is a terminal output of what keeps occurring.这是不断发生的终端 output。

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'

Thanks to everyone that took the time to read this I really appreciate it.感谢所有花时间阅读本文的人,我真的很感激。

Also sorry if i didn't put this in the right forum I'm still trying to understand this website and am dedicated to improve.也很抱歉,如果我没有把它放在正确的论坛上,我仍在努力了解这个网站并致力于改进。

Thank You - Connor谢谢你-康纳

Try this:尝试这个:

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")

This code starts a new thread that reads from self.proc.stdout and writes the data into self.stdout_data in a while True loop.此代码启动一个新线程,该线程从self.proc.stdout读取数据并在while True循环中将数据写入self.stdout_data There is also a tkinter loop that takes the data out of the self.stdout_data and puts it in the Label widget.还有一个 tkinter 循环将数据从self.stdout_data中取出并放入Label小部件中。

I didn't directly set the Label 's text from the thread because sometimes tkinter can crash if you call it from a different thread.我没有直接从线程中设置Label的文本,因为如果您从不同的线程调用它,有时 tkinter 可能会崩溃。

Another thing: I removed the StringVar because I can just use: <tkinter.Label>.config(text=<new text>) instead.另一件事:我删除了StringVar因为我可以使用: <tkinter.Label>.config(text=<new text>)代替。

I have the problem too, this was my solution.我也有这个问题,这是我的解决方案。 I post it on [https://github.com/ianfun/notes/blob/main/editor/main.py].我将其发布在 [https://github.com/ianfun/notes/blob/main/editor/main.py] 上。

data.py was generate by build.py. data.py 由 build.py 生成。

I cannot found grate terminal like vscode in python.我在 python 中找不到像 vscode 这样的格栅终端。

note:use key right to select注意:使用 select 的密钥权限

you can read idlelib run.py你可以阅读 idlelib run.py

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM