我有答案,以防萬一還在乎! (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,使控制檯將表現得更像一個標準的部件。
來源
2017-10-03 13:10:56
Oli
posible重複http://stackoverflow.com/questions/21603038/python-compiler-connected-to-a-button – markcial
這個問題是相似的,但這個問題是如何使一個tkinter框架中的交互式終端,和攔截stdout和stderr,提到stdin是我會修復的一個錯字。 –
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