如何将“stdout”重定向到标签小部件?

Posted

技术标签:

【中文标题】如何将“stdout”重定向到标签小部件?【英文标题】:How to redirecting "stdout" to a Label widget? 【发布时间】:2012-06-06 10:53:12 【问题描述】:

我正在尝试将标准输出重定向到标签小部件。目标是将脚本中的所有 Python 打印“打印”到 Label 中。

但是当我点击BUTTON1 时,什么都没有发生...

这是我的代码:

from Tkinter import *
import sys
import tkMessageBox


class App:

    def __init__(self, master):

        self.frame = Frame(master, borderwidth=5, relief=RIDGE)
        self.frame.grid()

        class IORedirector(object):
            def __init__(self,TEXT_INFO):
                self.TEXT_INFO = TEXT_INFO

        class StdoutRedirector(IORedirector):
            def write(self,str):
                self.TEXT_INFO.config(text=str)

        self.TEXT_HEADER = self.text_intro = Label(self.frame, bg="lightblue",text="MY SUPER PROGRAMM") ## HEADER TEXT
        self.TEXT_HEADER.grid(row=0, column=0, columnspan=2, sticky=W+E+N+S)

        self.MENU = Frame(self.frame, borderwidth=5, relief=RIDGE, height=12) 

        self.MENU.grid(row=1, column=0, sticky=N)

        self.button = Button(self.MENU, text="QUIT", fg="red", bg="red", command=self.frame.quit)
        self.button.grid(row=4, column=0)

        self.BUTTON1 = Button(self.MENU, text="BUTTON1", command=self.BUTTON1_CMD)
        self.BUTTON1.grid(row=0, column=0,sticky=W+E)

        self.TEXT_INFO = Label(self.frame, height=12, width=40, text="I WANT TO SEE THE STDOUT OUTPUT HERE", bg="grey",borderwidth=5, relief=RIDGE)
        self.TEXT_INFO.grid(row=1, column=1)

        sys.stdout = StdoutRedirector(self.TEXT_INFO)

    def BUTTON1_CMD(self):
        print "TEST NUMBER ONE"
        print "TEST NUMBER TWO"


root = Tk()
app = App(root)
root.mainloop()

【问题讨论】:

【参考方案1】:

您看不到文本集的原因是它在瞬间正确设置,然后立即设置为空白。这是因为 print 在 print 语句之后向 stdout 发送换行符。这是一个修改后的版本,它附加到标签上,而不是为每个打印语句覆盖它。

    class StdoutRedirector(IORedirector):
        def write(self,str):
           self.TEXT_INFO.config(text=self.TEXT_INFO.cget('text') + str)

【讨论】:

你太棒了!这是完美的工作!感谢您的帮助,我不知道 print 在 print 语句之后总是向标准输出发送换行符,很高兴知道 ;-) (我不能投票,因为我的声誉不超过 15,但是我一有它,我就会投票给你;))【参考方案2】:

我创建了一个类,它将标准输出写入调用复制到 tkinter 小部件,无论是标签还是文本。在 Python3.3.1/WindowsXp 上为我工作:

import sys

class StdoutToWidget:
    '''
    Retrieves sys.stdout and show write calls also in a tkinter
    widget. It accepts widgets which have a "text" config and defines
    their width and height in characters. It also accepts Text widgets.
    Use stop() to stop retrieving.

    You can manage output height by using the keyword argument. By default
    the class tries to get widget\'s height configuration and use that. If
    that fails it sets self.height to None which you can also do manually.
    In this case the output will not be trimmed. However if you do not
    manage your widget, it can grow vertically hard by getting more and
    more inputs.
    '''

    # Inspired by Jesse Harris and Stathis
    # http://***.com/a/10846997/2334951
    # http://***.com/q/14710529/2334951

    # TODO: horizontal wrapping
    #       make it a widget decorator (if possible)
    #       height management for Text widget mode

    def __init__(self, widget, height='default', width='default'):
        self._content = []
        self.defstdout = sys.stdout
        self.widget = widget

        if height == 'default':
            try:
                self.height = widget.cget('height')
            except:
                self.height = None
        else:
            self.height = height
        if width == 'default':
            try:
                self.width = widget.cget('width')
            except:
                self.width = None
        else:
            self.width = width   

    def flush(self):
        '''
        Frame sys.stdout's flush method.
        '''
        self.defstdout.flush()

    def write(self, string, end=None):
        '''
        Frame sys.stdout's write method. This method puts the input
        strings to the widget.
        '''

        if string is not None:
            self.defstdout.write(string)
            try:
                last_line_last_char = self._content[-1][-1]
            except IndexError:
                last_line_last_char = '\n'
            else:
                if last_line_last_char == '\n':
                    self._content[-1] = self._content[-1][:-1]

            if last_line_last_char != '\n' and string.startswith('\r'):
                self._content[-1] = string[1:]
            elif last_line_last_char != '\n':
                self._content[-1] += string
            elif last_line_last_char == '\n' and string.startswith('\r'):
                self._content.append(string[1:])
            else:
                self._content.append(string)

        if hasattr(self.widget, 'insert') and hasattr(self.widget, 'see'):
            self._write_to_textwidget()
        else:
            self._write_to_regularwidget(end)

    def _write_to_regularwidget(self, end):
        if self.height is None:
            self.widget.config(text='\n'.join(self.content))
        else:
            if not end:
                content = '\n'.join(self.content[-self.height:])
            else:
                content = '\n'.join(self.content[-self.height+end:end])
            self.widget.config(text=content)

    def _write_to_textwidget(self):
        self.widget.insert('end', '\n'.join(self.content))
        self.widget.see('end')        

    def start(self):
        '''
        Starts retrieving.
        '''
        sys.stdout = self

    def stop(self):
        '''
        Stops retrieving.
        '''
        sys.stdout = self.defstdout

    @property
    def content(self):
        c = []
        for li in self._content:
            c.extend(li.split('\n'))

        if not self.width:
            return c
        else:
            result = []
            for li in c:
                while len(li) > self.width:
                    result.append(li[:self.width])
                    li = li[self.width:]
                result.append(li)
            return result

    @content.setter
    def content(self, string):
        self._content = string.split('\n')

    @property
    def errors(self):
        return self.defstdout.errors

    @property
    def encoding(self):
        return self.defstdout.encoding

EDIT1:我收到了反对票,所以这是更新的。我在 Label 小部件中使用它,并且 print() 函数在我的小部件中顺利显示。此外,如果我将 None 传递给 write 调用并假设 -1 作为结束参数,作为一个额外的功能,那么它不会显示最后一行(小心索引)。我使用它是因为我在小部件上附加了一个滑块。我很快就会发布一个演示。

【讨论】:

以上是关于如何将“stdout”重定向到标签小部件?的主要内容,如果未能解决你的问题,请参考以下文章

如何将父小部件焦点重定向到子小部件?

将标准输出从多处理重定向到 Tkinter 文本小部件

如何在构建小部件中使用导航器进行重定向

如何从Tkinter,Python中的Text小部件中移除焦点

html ActionKit小部件重定向

将变量从 MaterialPageRoute 传递到自定义小部件