2009-03-20 11 views
15

Tôi đã tạo một chương trình in kết quả trên dòng lệnh. (Đó là máy chủ và nó in nhật ký trên dòng lệnh.)Chuyển hướng kết quả dòng lệnh đến giao diện đồ họa tkẻ hoạ

Bây giờ, tôi muốn xem kết quả tương tự với GUI.

Làm cách nào để chuyển hướng kết quả dòng lệnh sang GUI?

Vui lòng đề xuất mẹo để dễ dàng chuyển đổi ứng dụng bảng điều khiển sang GUI đơn giản.

Lưu ý rằng nó sẽ hoạt động trên Linux và Windows.

+0

liên quan: [Hiển thị đầu ra thời gian thực của một tiến trình con trong tiện ích tkinter] (http://stackoverflow.com/q/15362372/4279) – jfs

Trả lời

9

Bạn có thể tạo trình bao bọc tập lệnh chạy chương trình dòng lệnh của bạn dưới dạng tiến trình con, sau đó thêm đầu ra vào thứ gì đó như tiện ích văn bản.

from tkinter import * 
import subprocess as sub 
p = sub.Popen('./script',stdout=sub.PIPE,stderr=sub.PIPE) 
output, errors = p.communicate() 

root = Tk() 
text = Text(root) 
text.pack() 
text.insert(END, output) 
root.mainloop() 

nơi tập lệnh là chương trình của bạn. Bạn rõ ràng có thể in các lỗi trong một màu khác nhau, hoặc một cái gì đó như thế.

+0

Tôi đã thử trên linux, không hoạt động, vui lòng đề xuất giải pháp .. –

+0

Nó thiếu một root.mainloop() ở cuối để bắt đầu vòng lặp sự kiện. ngoài ra, có vẻ như nó sẽ hoạt động. – mavnn

+0

Có, tôi không có hệ thống Linux để thử nó vì vậy tất cả đều là từ bộ nhớ ... –

4

Chuyển hướng stdout sang phương thức write() cập nhật gui của bạn là một cách để đi và có thể nhanh nhất - mặc dù chạy một tiến trình con có lẽ là giải pháp thanh lịch hơn.

Chỉ chuyển hướng sau khi bạn thực sự tự tin nó đang hoạt động!

Ví dụ implimentation (file gui và kịch bản thử nghiệm):

test_gui.py:

from Tkinter import * 
import sys 
sys.path.append("/path/to/script/file/directory/") 

class App(Frame): 
    def run_script(self): 
     sys.stdout = self 
     ## sys.stderr = self 
     try: 
      del(sys.modules["test_script"]) 
     except: 
      ## Yeah, it's a real ugly solution... 
      pass 
     import test_script 
     test_script.HelloWorld() 
     sys.stdout = sys.__stdout__ 
     ## sys.stderr = __stderr__ 

    def build_widgets(self): 
     self.text1 = Text(self) 
     self.text1.pack(side=TOP) 
     self.button = Button(self) 
     self.button["text"] = "Trigger script" 
     self.button["command"] = self.run_script 
     self.button.pack(side=TOP) 

    def write(self, txt): 
     self.text1.insert(INSERT, txt) 

    def __init__(self, master=None): 
     Frame.__init__(self, master) 
     self.pack() 
     self.build_widgets() 

root = Tk() 
app = App(master = root) 
app.mainloop() 

test_script.py:

print "Hello world!" 

def HelloWorld(): 
    print "HelloWorldFromDef!" 
+0

nó không chuyển hướng 'sys.stdout' ở mức mô tả tập tin tức là, nếu bạn gọi' os.write (1, b'not redirect ') 'trong' test_script.py' thì bạn sẽ không thấy nó trong GUI.Xem [Chuyển hướng stdout tới một tệp bằng Python?] (Http://stackoverflow.com/a/22434262/4279) – jfs

7

Để hiển thị tiến trình con đầu ra trong một giao diện khi nó vẫn đang chạy, một giải pháp stdlib chỉ hoạt động trên cả Python 2 và 3 phải sử dụng chuỗi nền:

#!/usr/bin/python 
""" 
- read output from a subprocess in a background thread 
- show the output in the GUI 
""" 
import sys 
from itertools import islice 
from subprocess import Popen, PIPE 
from textwrap import dedent 
from threading import Thread 

try: 
    import Tkinter as tk 
    from Queue import Queue, Empty 
except ImportError: 
    import tkinter as tk # Python 3 
    from queue import Queue, Empty # Python 3 

def iter_except(function, exception): 
    """Works like builtin 2-argument `iter()`, but stops on `exception`.""" 
    try: 
     while True: 
      yield function() 
    except exception: 
     return 

class DisplaySubprocessOutputDemo: 
    def __init__(self, root): 
     self.root = root 

     # start dummy subprocess to generate some output 
     self.process = Popen([sys.executable, "-u", "-c", dedent(""" 
      import itertools, time 

      for i in itertools.count(): 
       print("%d.%d" % divmod(i, 10)) 
       time.sleep(0.1) 
      """)], stdout=PIPE) 

     # launch thread to read the subprocess output 
     # (put the subprocess output into the queue in a background thread, 
     # get output from the queue in the GUI thread. 
     # Output chain: process.readline -> queue -> label) 
     q = Queue(maxsize=1024) # limit output buffering (may stall subprocess) 
     t = Thread(target=self.reader_thread, args=[q]) 
     t.daemon = True # close pipe if GUI process exits 
     t.start() 

     # show subprocess' stdout in GUI 
     self.label = tk.Label(root, text=" ", font=(None, 200)) 
     self.label.pack(ipadx=4, padx=4, ipady=4, pady=4, fill='both') 
     self.update(q) # start update loop 

    def reader_thread(self, q): 
     """Read subprocess output and put it into the queue.""" 
     try: 
      with self.process.stdout as pipe: 
       for line in iter(pipe.readline, b''): 
        q.put(line) 
     finally: 
      q.put(None) 

    def update(self, q): 
     """Update GUI with items from the queue.""" 
     for line in iter_except(q.get_nowait, Empty): # display all content 
      if line is None: 
       self.quit() 
       return 
      else: 
       self.label['text'] = line # update GUI 
       break # display no more than one line per 40 milliseconds 
     self.root.after(40, self.update, q) # schedule next update 

    def quit(self): 
     self.process.kill() # exit subprocess if GUI is closed (zombie!) 
     self.root.destroy() 


root = tk.Tk() 
app = DisplaySubprocessOutputDemo(root) 
root.protocol("WM_DELETE_WINDOW", app.quit) 
# center window 
root.eval('tk::PlaceWindow %s center' % root.winfo_pathname(root.winfo_id())) 
root.mainloop() 

Bản chất của giải pháp là:

  • đưa ra tiến trình con vào hàng đợi trong một thread nền
  • nhận được đầu ra khỏi hàng đợi trong thread GUI.

tức là, gọi process.readline() trong chuỗi nền -> queue -> cập nhật nhãn GUI trong chuỗi chính.