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



我制作了两个tkinter文本框,其中一个将您的python脚本作为输入,另一个显示脚本的执行结果,但当我使用input()命令时,我遇到了一个错误。下面给出的是stdout重定向程序的类,以及在读取脚本后执行的execute函数,该函数运行良好。我没有包括Texttkinter等,因为我使用了与代码一起工作的所有通用方法,如Text.get()Text.mark_set()Text.replace()等,而且这里也没有包括一些函数。除了脚本和输出框之外,我还尝试将整个控制台嵌入到具有InteractiveConsole的文本框中,但在接收输入或stdin的情况下问题相同,但在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流,所以控制台或执行窗口可能完全嵌入文本框中。

好的,所以在对web、文档以及队列、idlelib和子流程模块的代码进行研究后,我找到了使tkinter-Textbox作为stdin、stdout和stderr接收器与python控制台交互的最简单方法。这是代码:

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]?', tk.END, 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()

上面的代码使用jupyter qtconsole(因为它非常方便(,否则简单的python shell也可以使用code模块中的InteractiveShell()。我还没有完全为Enter键、UpDown箭头键制作功能。这些可以由用户根据自己的选择进行。

这也可以在Oli的回答中找到,它是可定制的。

最新更新