【问题标题】:How to make tkinter text box a stdin input receiver?如何使 tkinter 文本框成为标准输入接收器?
【发布时间】:2021-01-13 17:52:53
【问题描述】:

我制作了两个 tkinter 文本框,其中一个将您的 python 脚本作为输入,另一个显示脚本的执行结果,但是当我使用 input() 命令时出现错误。下面给出的是标准输出重定向器的类以及在读取脚本后执行的执行函数,它工作正常。我没有包含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

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

【问题讨论】:

  • 也许你可以从stackoverflow.com/questions/59164314/…得到想法
  • @j_4321 我从上面提到的链接中尝试了所有这些,但它们都存在无法接收输入的相同问题。我建议你尝试所有,你也会注意到都有同样的问题
  • 好的,抱歉我没有仔细阅读这个问题,虽然通常命令行输入和 GUI 没有混合在一起,所以我误解了用户通过文本输入的输入。帮不了你。
  • 无论如何都可以使用awaitasync def 吗?因为在制作一个不和谐的机器人时,如果在不和谐中它等待用户输入然后可能直到用户输入任何东西直到那时它可以等待然后按下 Enter 键它就会向终端发送数据只是一个想法,我不知道是否可以吗?

标签: python tkinter console textbox stdin


【解决方案1】:

好的,所以在对网络、文档以及队列、idlelib 和子进程模块的代码进行研究之后,我想出了让 tkinter 文本框与 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(因为它非常好用),否则使用code模块中的InteractiveShell()也可以使用简单的python shell。 我还没有完全为Enter 键、UpDown 箭头键制作功能。这些可以由用户根据自己的选择进行。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多