2015-05-23 107 views
1

我試圖在Tkinter python中創建一個GUI。我想顯示一個工具的輸出到我的Tkinter接口。該工具在命令行中運行良好,但它是一個連續的掃描器。有點像一個連續的ping(我的意思是在Linux中沒有選項的ping命令)。實時輸出到Tkinter GUI的運行過程

現在的問題是,因爲ping的輸出從未完成,因此我無法在Tkinter中打印輸出。它也使我的應用程序凍結。幾秒鐘後,我也無法停止命令以顯示輸出。 Run process with realtime output in PHP 我發現PHP中,這上面的鏈接有幫助的,但我怎樣才能把這段代碼轉換在python:

https://stackoverflow.com/a/6144213/4931414

這裏是我想要顯示Tkinter的框架上的一些示例代碼

#!/usr.... 

import subprocess 
x = subprocess.call(["ping", "127.0.0.1"]) 
print x 

這在命令行上很好用,但我沒有在tkinter接口上獲得輸出。

+0

你可能想發表一些簡單的代碼,顯示你的情況.. – nbro

+0

@Xenomorph:更新的代碼 –

+0

我認爲,解決辦法是使用線程的問題。你可以有一個負責後端工作的線程,然後你把結果放入一個隊列中,一旦它們可用,你就從GUI程序中獲取這個結果...... – nbro

回答

0

首先,我必須承認,我不是很熟悉的模塊subprocessthreading,但我試圖創建一個接受你寫的命令時,其輸出將在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() 
+0

只是一個附錄。您希望保留對psutil的引用,例如在使用shell = True時,至少有2個或可能需要3個進程要殺。示例代碼(適用於所有操作系統的AFAIK)http://stackoverflow.com/questions/1230669/subprocess-deleting-child-processes-in-windows –

+0

@CurlyJoe我不想使用shell = True,而應該避免它(主要在一個GUI中)。 'psutil'與我的答案有什麼關係?我只是問,因爲我不知道。 – nbro

+0

請原諒我笨拙的解釋。我只是想讓你知道它存在。如果您曾經遇到過希望關閉父母和子女流程的情況,那麼可以節省大量時間尋找解決方案,並且讓您頭疼。 –

0

如果你改變你的代碼下面你會看到平,而不是「打印x」是什麼控制檯

import subprocess 
x = subprocess.call(["ping", "127.0.0.1"]) 
print "x is", x ## or comment out this line 
上顯示

你將不得不使用管道和定期刷新stdout來獲得我想要的東西。見POPEN在道格·赫爾曼的Python模塊的周http://pymotw.com/2/subprocess/index.html#module-subprocess

+1

OP想要將命令的輸出顯示到tkinter.Frame中。這不回答OP的問題...... – nbro