如何使tkinter文本框成为stdin输入接收器?

问题描述

我制作了两个tkinter文本框,其中一个将您的python脚本作为输入,另一个显示了脚本执行的结果,但是当我使用input()命令时出现错误。下面给出的是stdout重定向器的类,以及在读取脚本后执行的execute函数效果很好。我没有包括Texttkinter等,因为我使用了与Text.get()Text.mark_set()Text.replace()等类似的所有通用方法也有些功能不在此处。除了脚本和输出框之外,我还尝试将整个控制台嵌入到带有InteractiveConsole的文本框中,但是在接收输入或标准输入的情况下,问题是相同的,但是在两种情况下,stdoutstderr正常。

from code import InteractiveConsole,InteractiveInterpreter


class StdoutRedirector(object):
    def __init__(self,text_widget):
        self.text_space = text_widget

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


##class StdinRedirector(object):
##    def __init__(self,text_widget):
##        self.text_space = text_widget
##
##    def readline(self) -> str:
##        t = self.text_space.get(INSERT,f"{int(text.index(INSERT).split('.')[0])}.{int(text.index(INSERT).split('.')[1])}")
##        return t


def execute(event=None):
    save()
    code = text.get('1.0',END+'-1c')
    stdin = sys.stdin
    stdout = sys.stdout 
    stderr = sys.stderr

    output.delete('1.0',END)
##    def a():
##        sys.stdin = StdinRedirector(output)
##    output.bind('<Return>',lambda: a)
    
    sys.stdout = StdoutRedirector(output)
    sys.stderr = StdoutRedirector(output)
    
    interp = InteractiveInterpreter()
    interp.runcode(code)

    sys.stdout = stdout
    sys.stderr = stderr
##    sys.stdin = stdin

此后,我尝试了重定向stdin,这显然不起作用,而是挂起了应用程序,并且即使一次又一次尝试后,窗口也停止响应。 请帮助我...我不知道它是否不可能,但是PyCharm和其他人内部都具有I / O流,因此控制台或执行窗口可以完全嵌入文本框中。

解决方法

好的,在对网络、文档以及队列、idlelib 和子进程模块的代码进行研究之后,我找到了使 tkinter 文本框作为 stdin、stdout 和 stderr 接收器与 python 控制台交互的最简单方法。代码如下:

# get context
$apimContext = New-AzApiManagementContext -ResourceGroupName "targetResourceGroup" -ServiceName "targetApimInstance"
$api = Get-AzApiManagementApi -Context $apimContext -ApiId "api-id"

# set subscriptionRequired to false
$api.SubscriptionRequired=$false
Set-AzApiManagementApi -InputObject $api

上面的代码使用了jupyter qtconsole(因为它非常好用),否则也可以使用import tkinter as tk import subprocess import queue import os from threading import Thread class Console(tk.Frame): def __init__(self,parent=None,**kwargs): tk.Frame.__init__(self,parent,**kwargs) self.parent = parent # create widgets self.ttytext = tk.Text(self,wrap=tk.WORD) self.ttytext.pack(fill=tk.BOTH,expand=True) self.ttytext.linenumbers.pack_forget() self.p = subprocess.Popen(["jupyter","qtconsole"],stdout=subprocess.PIPE,stdin=subprocess.PIPE,stderr=subprocess.PIPE,creationflags=subprocess.CREATE_NO_WINDOW) # 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 # 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() # key bindings for events self.ttytext.bind("<Return>",self.enter) self.ttytext.bind('<BackSpace>',self.on_bkspace) self.ttytext.bind('<Delete>',self.on_delete) self.ttytext.bind('<<Copy>>',self.on_copy) self.ttytext.bind('<<Paste>>',self.on_paste) self.ttytext.bind('<Control-c>',self.on_copy) self.ttytext.bind('<Control-v>',self.on_paste) 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,event): """The <Return> key press handler""" cur_ind = str(self.ttytext.index(tk.INSERT)) if int(cur_ind.split('.')[0]) < int(self.ttytext.search(': ',tk.END,backwards=True).split('.')[0]): try: selected = self.ttytext.get('sel.first','sel.last') if len(selected) > 0: self.ttytext.insert(tk.END,selected) self.ttytext.mark_set(tk.INSERT,tk.END) self.ttytext.see(tk.INSERT) return 'break' except: selected = self.ttytext.get( self.ttytext.search(': ',tk.INSERT,backwards=True),tk.INSERT) self.ttytext.insert(tk.END,selected.strip(': ')) self.ttytext.mark_set(tk.INSERT,tk.END) self.ttytext.see(tk.INSERT) return 'break' 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 on_bkspace(self,event): pass def on_delete(self,event): pass def on_key(self,event): """The typing control (<KeyRelease>) handler""" cur_ind = str(self.ttytext.index(tk.INSERT)) try: if int(cur_ind.split('.')[0]) < int(self.ttytext.search(r'In [0-9]?',backwards=True).split('.')[0]): return 'break' except: return def on_copy(self,event): """<Copy> event handler""" self.ttytext.clipboard_append(self.ttytext.get('sel.first','sel.last')) # I created this function because I was going to make a custom textbox def on_paste(self,event): """<Paste> event handler""" self.ttytext.insert(tk.INSERT,self.ttytext.clipboard_get()) # I created this function because I was going to make a custom textbox 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) self.ttytext.inst_trigger() if __name__ == '__main__': root = tk.Tk() main_window = Console(root) main_window.pack(fill=tk.BOTH,expand=True) main_window.ttytext.focus_force() root.mainloop() 模块中的InteractiveShell()来使用简单的python shell。 我还没有完全为 code 键、EnterUp 箭头键制作函数。这些可以由用户根据自己的选择进行。

这也可以在 Oli 的回答 here 中找到,并且可以自定义。