2014-02-16 5 views
8

Ich möchte einen Steueranschluss Widget auf meine reine Python + tkinter Anwendung ähnlich dem Python-Interpreter in Blender bereitgestellt hinzuzufügen. Es sollte im selben Kontext (Prozess) ausgeführt werden, damit der Benutzer Features hinzufügen und die Anwendung steuern kann, die derzeit über das Steuerelement-Widget ausgeführt wird. Im Idealfall möchte ich, dass es auch stdout und stderr der aktuellen Anwendung "entführt", so dass es Probleme oder Debugging-Informationen innerhalb der laufenden Anwendung melden wird.Wie kann ich einen Python-Interpreter-Frame in Python mit tkinter einbetten?

Dies ist, was ich mit so weit gekommen sind. Die einzigen Probleme sind, dass es nicht auf Befehle reagiert und der Thread nicht stoppt, wenn der Benutzer das Fenster schließt.

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

mögliches Duplikat von http://stackoverflow.com/questions/21603038/python-compiler-connected-to-a-button – markcial

+0

Dieses Problem ist ähnlich, aber diese Frage ist, wie man ein interaktives Terminal in einem tkinter Rahmen macht, und stdout und stderr abzufangen, stdin zu erwähnen war ein Tippfehler, den ich beheben werde. –

+2

IDLE hat so etwas genannt [_Python Shell window_] (http://docs.python.org/2/library/idle.html#python-shell-window), und Sie können die [Quellcode] lesen (http : //hg.python.org/cpython/file/a87f284e14ea/Lib/idlelib). – martineau

Antwort

2

Ich habe die Antwort, falls sich jemand noch interessiert! (Ich habe auch auf Python 3 geändert, daher der import tkinter statt import Tkinter)

ich den Ansatz geändert haben etwas von der ursprünglichen durch eine separate Datei mit dem InteractiveConsole, laufen und macht dann die Hauptdatei, um diese andere Datei öffnen (die ich console.py genannt und ist im selben Verzeichnis) in einem Teilprozess, der stdout Verknüpfung, stderr und stdin dieser subprocess zum tkinter Text-Widget programmatisch. Hier

ist der Code in der für die Konsole-Datei (falls dies in der Regel ausgeführt wird, wirkt es wie eine normale Konsole):

# console.py 
import code 

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

Und hier ist der Code für den Python-Interpreter, dass die Konsole läuft innerhalb des Text-Widget:

# 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() 

der Grund, dass ist in separate Threads von stdout und stderr zu lesen ist, weil das Leseverfahren blockiert, die das Programm bewirkt einzufrieren, bis die console.py subprocess mehr Leistung gibt, es sei denn, diese sind in separaten Threads. Die writeLoop-Methode und die Warteschlangen werden benötigt, um in das Text-Widget zu schreiben, da tkinter nicht Thread-sicher ist.

Dies hat sicherlich noch Probleme ausgebügelt, wie die Tatsache, dass jeder Code auf dem Text-Widget noch einmal bearbeitet wird bereits eingereicht, aber hoffentlich beantwortet sie Ihre Frage.

EDIT: Ich habe auch einige der tkinter, so dass die Konsole verhält sich mehr wie ein Standard-Widget neatened.

+0

Ich habe es getestet, es funktioniert super! Eine Frage, die ich habe, ist, wie hast du herausgefunden, welche Funktionen du außer Kraft setzen musst?Wie für readFromProcessOut sehe ich nicht, wie es funktioniert, wo der Einstiegspunkt ist. Ich frage mich auch, ob es möglich ist, separate Eingabe- und Ausgabe-Widgets zu haben. –

+1

@Sertalp Bilal Die einzigen Methoden, die außer Kraft gesetzt werden, sind "__init__" und "destroy". Alle anderen Methoden werden aufgerufen, an Schlüssel gebunden oder in neuen Threads in meinem Code gestartet. z.B. 'readFromProcessOut' wird in einem neuen Thread nahe dem Ende der' __init__' Methode gestartet. Aufgrund der Art und Weise, wie ich das gemacht habe, wäre es durchaus möglich, separate Eingabe- und Ausgabe-Widgets zu haben - es würde ein wenig Codeumstrukturierung erfordern, aber solange beide Widgets einen Verweis auf das gleiche "Subprocess" -Objekt haben würde funktionieren (du könntest eine neue Frage dafür stellen, ich hätte nichts dagegen, sie zu beantworten) – Oli

Verwandte Themen