2014-02-16 164 views
8

我想添加一個控制終端小部件到我的純python + tkinter應用程序,類似於Blender中提供的python解釋器。它應該在相同的上下文(進程)內運行,以便用戶可以添加功能並通過控件來控制當前正在運行的應用程序。理想情況下,我希望它能夠「劫持」當前應用程序的stdout和stderr,以便在運行的應用程序中報告任何問題或調試信息。如何使用tkinter在python中嵌入python解釋器框架?

這就是我到目前爲止所提出的。唯一的問題是它不響應命令,並且當用戶關閉窗口時線程不會停止。

import Tkinter as tk 
import sys 
import code 
from threading import * 

class Console(tk.Frame): 
    def __init__(self,parent=None): 
     tk.Frame.__init__(self, parent) 
     self.parent = parent 
     sys.stdout = self 
     sys.stderr = self 
     self.createWidgets() 
     self.consoleThread = ConsoleThread() 
     self.after(100,self.consoleThread.start) 

    def write(self,string): 
     self.ttyText.insert('end', string) 
     self.ttyText.see('end') 

    def createWidgets(self): 
     self.ttyText = tk.Text(self.parent, wrap='word') 
     self.ttyText.grid(row=0,column=0,sticky=tk.N+tk.S+tk.E+tk.W) 


class ConsoleThread(Thread): 

    def __init__(self): 
     Thread.__init__(self) 

    def run(self): 
     vars = globals().copy() 
     vars.update(locals()) 
     shell = code.InteractiveConsole(vars) 
     shell.interact() 

if __name__ == '__main__': 
    root = tk.Tk() 
    root.config(background="red") 
    main_window = Console(root) 
    main_window.mainloop() 
    try: 
     if root.winfo_exists(): 
      root.destroy() 
    except: 
     pass 
+0

posible重複http://stackoverflow.com/questions/21603038/python-compiler-connected-to-a-button – markcial

+0

這個問題是相似的,但這個問題是如何使一個tkinter框架中的交互式終端,和攔截stdout和stderr,提到stdin是我會修復的一個錯字。 –

+2

IDLE有類似於[_Python Shell window_](http://docs.python.org/2/library/idle.html#python-shell-window)的東西,您可以閱讀它的[源代碼](http ://hg.python.org/cpython/file/a87f284e14ea/Lib/idlelib)。 – martineau

回答

2

我有答案,以防萬一還在乎! (I也改變到Python 3,因此import tkinter而非import Tkinter

我已經通過使用一個單獨的文件來運行InteractiveConsole,然後使主文件打開該其他文件改變從原始略的辦法(我稱之爲console.py,位於相同的目錄),它將子進程的stdout,stderr和stdin以編程方式鏈接到tkinter Text小部件。

下面是控制檯文件中的代碼(如果這是正常運行,它就像一個正常的控制檯):

# console.py 
import code 

if __name__ == '__main__': 
    vars = globals().copy() 
    vars.update(locals()) 
    shell = code.InteractiveConsole(vars) 
    shell.interact() 

這裏是Python解釋器的代碼,運行控制檯文本窗口小部件內:

# main.py 
import tkinter as tk 
import subprocess 
import queue 
import os 
from threading import Thread 

class Console(tk.Frame): 
    def __init__(self,parent=None): 
     tk.Frame.__init__(self, parent) 
     self.parent = parent 
     self.createWidgets() 

     # get the path to the console.py file assuming it is in the same folder 
     consolePath = os.path.join(os.path.dirname(__file__),"console.py") 
     # open the console.py file (replace the path to python with the correct one for your system) 
     # e.g. it might be "C:\\Python35\\python" 
     self.p = subprocess.Popen(["python3",consolePath], 
            stdout=subprocess.PIPE, 
            stdin=subprocess.PIPE, 
            stderr=subprocess.PIPE) 

     # make queues for keeping stdout and stderr whilst it is transferred between threads 
     self.outQueue = queue.Queue() 
     self.errQueue = queue.Queue() 

     # keep track of where any line that is submitted starts 
     self.line_start = 0 

     # make the enter key call the self.enter function 
     self.ttyText.bind("<Return>",self.enter) 

     # a daemon to keep track of the threads so they can stop running 
     self.alive = True 
     # start the functions that get stdout and stderr in separate threads 
     Thread(target=self.readFromProccessOut).start() 
     Thread(target=self.readFromProccessErr).start() 

     # start the write loop in the main thread 
     self.writeLoop() 

    def destroy(self): 
     "This is the function that is automatically called when the widget is destroyed." 
     self.alive=False 
     # write exit() to the console in order to stop it running 
     self.p.stdin.write("exit()\n".encode()) 
     self.p.stdin.flush() 
     # call the destroy methods to properly destroy widgets 
     self.ttyText.destroy() 
     tk.Frame.destroy(self) 
    def enter(self,e): 
     "The <Return> key press handler" 
     string = self.ttyText.get(1.0, tk.END)[self.line_start:] 
     self.line_start+=len(string) 
     self.p.stdin.write(string.encode()) 
     self.p.stdin.flush() 

    def readFromProccessOut(self): 
     "To be executed in a separate thread to make read non-blocking" 
     while self.alive: 
      data = self.p.stdout.raw.read(1024).decode() 
      self.outQueue.put(data) 

    def readFromProccessErr(self): 
     "To be executed in a separate thread to make read non-blocking" 
     while self.alive: 
      data = self.p.stderr.raw.read(1024).decode() 
      self.errQueue.put(data) 

    def writeLoop(self): 
     "Used to write data from stdout and stderr to the Text widget" 
     # if there is anything to write from stdout or stderr, then write it 
     if not self.errQueue.empty(): 
      self.write(self.errQueue.get()) 
     if not self.outQueue.empty(): 
      self.write(self.outQueue.get()) 

     # run this method again after 10ms 
     if self.alive: 
      self.after(10,self.writeLoop) 

    def write(self,string): 
     self.ttyText.insert(tk.END, string) 
     self.ttyText.see(tk.END) 
     self.line_start+=len(string) 

    def createWidgets(self): 
     self.ttyText = tk.Text(self, wrap=tk.WORD) 
     self.ttyText.pack(fill=tk.BOTH,expand=True) 


if __name__ == '__main__': 
    root = tk.Tk() 
    root.config(background="red") 
    main_window = Console(root) 
    main_window.pack(fill=tk.BOTH,expand=True) 
    root.mainloop() 

的原因,從輸出和錯誤讀取是在單獨的線程是因爲讀取方法是阻塞,這導致程序凍結直到console.py子提供了更多的輸出,除非這些在不同的線程中。由於tkinter不是線程安全的,所以需要writeLoop方法和隊列來寫入Text小部件。

這當然還有問題需要解決,比如Text小部件上的任何代碼都是可編輯的,即使它已經提交了,但希望它能夠回答你的問題。

編輯︰我也neatened一些tkinter,使控制檯將表現得更像一個標準的部件。

+0

我測試過了,它效果很棒!我有一個問題,你是如何找出你需要覆蓋哪些功能的?如readFromProcessOut,我不知道它是如何工作的,入口點在哪裏。另外我想知道是否有可能擁有單獨的輸入和輸出小部件。 –

+1

@Sertalp Bilal被覆蓋的唯一方法是'__init__'和'destroy'。所有其他方法都被調用,綁定到鍵,或者在我的代碼中的新線程中啓動。 例如'readFromProcessOut'在'__init__'方法底部附近的一個新線程中啓動。 由於我這樣做的方式,完全有可能具有單獨的輸入和輸出小部件 - 它需要一點代碼重構,但只要這兩個小部件都具有對同一個「子進程」對象的引用,它就會會起作用(你可以提出一個新的問題,我不介意回答) – Oli

相關問題