我试图在Tkinter python中创建一个GUI。我想将工具的输出显示给我的Tkinter界面。该工具在命令行中运行良好,但它是一个连续的扫描仪。有点像连续ping(我的意思是Linux中没有选项的ping命令)。
现在问题是因为ping的输出永远不会完成,因此我无法在Tkinter中打印输出。它还使我的应用程序冻结。几秒钟后我也无法停止命令显示输出。 Run process with realtime output in PHP 我发现上面这个链接对php很有用,但是如何在python中转换这段代码:
https://stackoverflow.com/a/6144213/4931414
以下是我想在tkinter frame
上显示的一些示例代码#!/usr....
import subprocess
x = subprocess.call(["ping", "127.0.0.1"])
print x
这在命令行上运行良好,但我没有在tkinter接口上获得输出。
答案 0 :(得分:3)
首先,我必须承认我对模块subprocess
和threading
不太熟悉,但我试图创建一个简单的控制台,接受你编写一个命令,其输出将显示在Text
窗口小部件中。
基本思想是在单击按钮Execute
时有一个新的运行并行线程来处理命令。我们不断迭代stdout
的行并将它们插入到Text小部件中。
它似乎适用于任何命令,但我很确定存在一些问题和错误。如果你们对我上面提到的模块更熟悉,看到我的代码有任何严重的问题,或者有任何改进建议,我肯定会听你的,以便改进这个例子。
现在,这是代码:
import tkinter as tk
from tkinter.scrolledtext import ScrolledText
import threading
from subprocess import Popen, PIPE
class Console(tk.Frame):
"""Simple console that can execute bash commands"""
def __init__(self, master, *args, **kwargs):
tk.Frame.__init__(self, master, *args, **kwargs)
self.text_options = {"state": "disabled",
"bg": "black",
"fg": "#08c614",
"insertbackground": "#08c614",
"selectbackground": "#f01c1c"}
self.text = ScrolledText(self, **self.text_options)
# It seems not to work when Text is disabled...
# self.text.bind("<<Modified>>", lambda: self.text.frame.see(tk.END))
self.text.pack(expand=True, fill="both")
# bash command, for example 'ping localhost' or 'pwd'
# that will be executed when "Execute" is pressed
self.command = ""
self.popen = None # will hold a reference to a Popen object
self.running = False # True if the process is running
self.bottom = tk.Frame(self)
self.prompt = tk.Label(self.bottom, text="Enter the command: ")
self.prompt.pack(side="left", fill="x")
self.entry = tk.Entry(self.bottom)
self.entry.bind("<Return>", self.start_thread)
self.entry.bind("<Command-a>", lambda e: self.entry.select_range(0, "end"))
self.entry.bind("<Command-c>", self.clear)
self.entry.focus()
self.entry.pack(side="left", fill="x", expand=True)
self.executer = tk.Button(self.bottom, text="Execute", command=self.start_thread)
self.executer.pack(side="left", padx=5, pady=2)
self.clearer = tk.Button(self.bottom, text="Clear", command=self.clear)
self.clearer.pack(side="left", padx=5, pady=2)
self.stopper = tk.Button(self.bottom, text="Stop", command=self.stop)
self.stopper.pack(side="left", padx=5, pady=2)
self.bottom.pack(side="bottom", fill="both")
def clear_text(self):
"""Clears the Text widget"""
self.text.config(state="normal")
self.text.delete(1.0, "end-1c")
self.text.config(state="disabled")
def clear_entry(self):
"""Clears the Entry command widget"""
self.entry.delete(0, "end")
def clear(self, event=None):
"""Does not stop an eventual running process,
but just clears the Text and Entry widgets."""
self.clear_entry()
self.clear_text()
def show(self, message):
"""Inserts message into the Text wiget"""
self.text.config(state="normal")
self.text.insert("end", message)
self.text.see("end")
self.text.config(state="disabled")
def start_thread(self, event=None):
"""Starts a new thread and calls process"""
self.stop()
self.running = True
self.command = self.entry.get()
# self.process is called by the Thread's run method
threading.Thread(target=self.process).start()
def process(self):
"""Runs in an infinite loop until self.running is False"""
while self.running:
self.execute()
def stop(self):
"""Stops an eventual running process"""
if self.popen:
try:
self.popen.kill()
except ProcessLookupError:
pass
self.running = False
def execute(self):
"""Keeps inserting line by line into self.text
the output of the execution of self.command"""
try:
# self.popen is a Popen object
self.popen = Popen(self.command.split(), stdout=PIPE, bufsize=1)
lines_iterator = iter(self.popen.stdout.readline, b"")
# poll() return None if the process has not terminated
# otherwise poll() returns the process's exit code
while self.popen.poll() is None:
for line in lines_iterator:
self.show(line.decode("utf-8"))
self.show("Process " + self.command + " terminated.\n\n")
except FileNotFoundError:
self.show("Unknown command: " + self.command + "\n\n")
except IndexError:
self.show("No command entered\n\n")
self.stop()
if __name__ == "__main__":
root = tk.Tk()
root.title("Console")
Console(root).pack(expand=True, fill="both")
root.mainloop()
答案 1 :(得分:0)
对@nbro 的回答的改进:
from tkinter.scrolledtext import ScrolledText
from subprocess import Popen, PIPE
from threading import Thread, Lock
import tkinter as tk
class Console(ScrolledText):
"""
Simple console that can execute commands
"""
def __init__(self, master, **kwargs):
# The default options:
text_options = {"state": "disabled",
"bg": "black",
"fg": "#08c614",
"selectbackground": "orange"}
# Take in to account the caller's specified options:
text_options.update(kwargs)
super().__init__(master, **text_options)
self.proc = None # The process
self.text_to_show = "" # The new text that we need to display on the screen
self.text_to_show_lock = Lock() # A lock to make sure that it's thread safe
self.show_text_loop()
def clear(self) -> None:
"""
Clears the Text widget
"""
super().config(state="normal")
super().delete("0.0", "end")
super().config(state="disabled")
def show_text_loop(self) -> None:
"""
Inserts the new text into the `ScrolledText` wiget
"""
new_text = ""
# Get the new text that needs to be displayed
with self.text_to_show_lock:
new_text = self.text_to_show.replace("\r", "")
self.text_to_show = ""
if len(new_text) > 0:
# Display the new text:
super().config(state="normal")
super().insert("end", new_text)
super().see("end")
super().config(state="disabled")
# After 100ms call `show_text_loop` again
super().after(100, self.show_text_loop)
def run(self, command:str) -> None:
"""
Runs the command specified
"""
self.stop()
thread = Thread(target=self._run, daemon=True, args=(command, ))
thread.start()
def _run(self, command:str) -> None:
"""
Runs the command using subprocess and appends the output
to `self.text_to_show`
"""
self.proc = Popen(command, shell=True, stdout=PIPE)
try:
while self.proc.poll() is None:
text = self.proc.stdout.read(1).decode()
with self.text_to_show_lock:
self.text_to_show += text
self.proc = None
except AttributeError:
# The process ended prematurely
pass
def stop(self, event:tk.Event=None) -> None:
"""
Stops the process.
"""
try:
self.proc.kill()
self.proc = None
except AttributeError:
# No process was running
pass
def destroy(self) -> None:
# Stop the process if the text widget is to be destroyed:
self.stop()
super().destroy()
if __name__ == "__main__":
def run_command_in_entry(event:tk.Event=None):
console.run(entry.get())
entry.delete("0", "end")
return "break"
root = tk.Tk()
root.title("Console")
console = Console(root)
console.pack(expand=True, fill="both")
entry = tk.Entry(root, bg="black", fg="white",
insertbackground="white")
entry.insert("end", "ping 8.8.8.8 -n 4")
entry.bind("<Return>", run_command_in_entry)
entry.pack(fill="x")
root.mainloop()
我们的答案之间的唯一区别是我删除了类中除 ScrolledText
之外的所有小部件,并确保我以线程安全的方式使用了 tkinter。 tkinter
的一部分不是线程安全的,并且不打算从不同的线程调用(可能会引发错误)。在更糟糕的情况下,tkinter 可能会崩溃而不会给出错误或回溯。
答案 2 :(得分:-1)
如果您将代码更改为以下内容,您将看到ping,而不是&#34; print x&#34;是控制台上显示的内容
import subprocess
x = subprocess.call(["ping", "127.0.0.1"])
print "x is", x ## or comment out this line
你必须定期使用管道和冲洗标准输出以获得我认为你想要的东西。请参阅Doug Hellmann的本周Python模块http://pymotw.com/2/subprocess/index.html#module-subprocess
中的popen