如何使 tkinter 文本框成为标准输入接收器?
How to make tkinter text box a stdin input receiver?
我制作了两个 tkinter 文本框,其中一个将您的 python 脚本作为输入,另一个显示脚本执行的结果但是当我使用 input()
命令时出现错误.下面给出的是标准输出重定向器的 class 以及在读取脚本后执行的执行函数,它工作正常。我没有包括 Text
、tkinter
等,因为我使用了所有适用于代码的通用方法,如 Text.get()
、Text.mark_set()
、Text.replace()
等,并且还有一些功能没有包括在这里。除了脚本和输出框之外,我还尝试将整个控制台嵌入到带有 InteractiveConsole
的文本框中,但是在接收输入或标准输入的情况下问题是相同的,但在这两种情况下都是 stdout
和stderr
工作正常。
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,这显然不起作用,应用程序挂起并且 window 即使在一次又一次尝试后也停止响应。
请帮助我...我不知道这是否不可能,但 PyCharm 和其他人内部有 I/O 流,所以也许控制台或执行 window 可以完全嵌入一个文本框。
好的,在网上、文档以及队列、idlelib 和子进程模块的代码中进行研究之后,我想出了使 tkinter 文本框与 python 控制台作为标准输入交互的最简单方法, stdout 和 stderr 接收器。这是代码:
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
键、Up
和 Down
箭头键的功能我还没有完全实现。这些可以由用户根据自己的选择进行。
这也可以在 Oli 的回答 here 中找到,并且可以自定义。
我制作了两个 tkinter 文本框,其中一个将您的 python 脚本作为输入,另一个显示脚本执行的结果但是当我使用 input()
命令时出现错误.下面给出的是标准输出重定向器的 class 以及在读取脚本后执行的执行函数,它工作正常。我没有包括 Text
、tkinter
等,因为我使用了所有适用于代码的通用方法,如 Text.get()
、Text.mark_set()
、Text.replace()
等,并且还有一些功能没有包括在这里。除了脚本和输出框之外,我还尝试将整个控制台嵌入到带有 InteractiveConsole
的文本框中,但是在接收输入或标准输入的情况下问题是相同的,但在这两种情况下都是 stdout
和stderr
工作正常。
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,这显然不起作用,应用程序挂起并且 window 即使在一次又一次尝试后也停止响应。 请帮助我...我不知道这是否不可能,但 PyCharm 和其他人内部有 I/O 流,所以也许控制台或执行 window 可以完全嵌入一个文本框。
好的,在网上、文档以及队列、idlelib 和子进程模块的代码中进行研究之后,我想出了使 tkinter 文本框与 python 控制台作为标准输入交互的最简单方法, stdout 和 stderr 接收器。这是代码:
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
键、Up
和 Down
箭头键的功能我还没有完全实现。这些可以由用户根据自己的选择进行。
这也可以在 Oli 的回答 here 中找到,并且可以自定义。