繁体   English   中英

Python 记录到 Tkinter 文本小部件

[英]Python Logging to Tkinter Text Widget

有没有人有关于如何在 Python 中设置日志记录到 Tkinter 文本小部件的示例? 我已经看到它在几个应用程序中使用,但无法弄清楚如何将日志记录定向到日志文件以外的任何内容。

除了上述答案之外:尽管针对此问题提出了很多解决方案(此处和其他线程中),但我还是费了很大力气才让这项工作自己完成。 最终我遇到了 Moshe Kaplan 的这个文本处理程序类,它使用ScrolledText小部件(这可能比 ScrollBar 方法更容易)。

我花了一些时间才弄清楚如何在线程应用程序中实际使用 Moshe 的类。 最后我创建了一个最小的演示脚本,展示了如何让它全部工作。 因为它可能对其他人有帮助,所以我在下面分享它。 在我的特殊情况下,我想登录GUI 和文本文件; 如果您不需要,只需删除logging.basicConfig中的文件名属性。

import time
import threading
import logging
try:
    import tkinter as tk # Python 3.x
    import tkinter.scrolledtext as ScrolledText
except ImportError:
    import Tkinter as tk # Python 2.x
    import ScrolledText

class TextHandler(logging.Handler):
    # This class allows you to log to a Tkinter Text or ScrolledText widget
    # Adapted from Moshe Kaplan: https://gist.github.com/moshekaplan/c425f861de7bbf28ef06

    def __init__(self, text):
        # run the regular Handler __init__
        logging.Handler.__init__(self)
        # Store a reference to the Text it will log to
        self.text = text

    def emit(self, record):
        msg = self.format(record)
        def append():
            self.text.configure(state='normal')
            self.text.insert(tk.END, msg + '\n')
            self.text.configure(state='disabled')
            # Autoscroll to the bottom
            self.text.yview(tk.END)
        # This is necessary because we can't modify the Text from other threads
        self.text.after(0, append)

class myGUI(tk.Frame):

    # This class defines the graphical user interface 

    def __init__(self, parent, *args, **kwargs):
        tk.Frame.__init__(self, parent, *args, **kwargs)
        self.root = parent
        self.build_gui()

    def build_gui(self):                    
        # Build GUI
        self.root.title('TEST')
        self.root.option_add('*tearOff', 'FALSE')
        self.grid(column=0, row=0, sticky='ew')
        self.grid_columnconfigure(0, weight=1, uniform='a')
        self.grid_columnconfigure(1, weight=1, uniform='a')
        self.grid_columnconfigure(2, weight=1, uniform='a')
        self.grid_columnconfigure(3, weight=1, uniform='a')

        # Add text widget to display logging info
        st = ScrolledText.ScrolledText(self, state='disabled')
        st.configure(font='TkFixedFont')
        st.grid(column=0, row=1, sticky='w', columnspan=4)

        # Create textLogger
        text_handler = TextHandler(st)

        # Logging configuration
        logging.basicConfig(filename='test.log',
            level=logging.INFO, 
            format='%(asctime)s - %(levelname)s - %(message)s')        

        # Add the handler to logger
        logger = logging.getLogger()        
        logger.addHandler(text_handler)

def worker():
    # Skeleton worker function, runs in separate thread (see below)   
    while True:
        # Report time / date at 2-second intervals
        time.sleep(2)
        timeStr = time.asctime()
        msg = 'Current time: ' + timeStr
        logging.info(msg) 

def main():

    root = tk.Tk()
    myGUI(root)

    t1 = threading.Thread(target=worker, args=[])
    t1.start()

    root.mainloop()
    t1.join()

main()

Github Gist 链接到上面的代码在这里:

https://gist.github.com/bitsgalore/901d0abe4b874b483df3ddc4168754aa

你应该logging.Handler ,例如:

import logging
from Tkinter import INSERT

class WidgetLogger(logging.Handler):
    def __init__(self, widget):
        logging.Handler.__init__(self)
        self.widget = widget

    def emit(self, record):
        # Append message (record) to the widget
        self.widget.insert(INSERT, record + '\n')

我以 Yuri 的想法为基础,但需要进行一些更改才能使事情正常进行:

import logging
import Tkinter as tk

class WidgetLogger(logging.Handler):
    def __init__(self, widget):
        logging.Handler.__init__(self)
        self.setLevel(logging.INFO)
        self.widget = widget
        self.widget.config(state='disabled')

    def emit(self, record):
        self.widget.config(state='normal')
        # Append message (record) to the widget
        self.widget.insert(tk.END, self.format(record) + '\n')
        self.widget.see(tk.END)  # Scroll to the bottom
        self.widget.config(state='disabled')

请注意,将状态从“正常”和“禁用”来回切换是使Text小部件变为只读的必要条件。

在福特的回答的基础上,这里有一个滚动的文本小部件,可以跟踪日志。 logging_handler成员是您添加到记录器的内容。

import logging
from Tkinter import END, N, S, E, W, Scrollbar, Text
import ttk

class LoggingHandlerFrame(ttk.Frame):

    class Handler(logging.Handler):
        def __init__(self, widget):
            logging.Handler.__init__(self)
            self.setFormatter(logging.Formatter("%(asctime)s: %(message)s"))
            self.widget = widget
            self.widget.config(state='disabled')

        def emit(self, record):
            self.widget.config(state='normal')
            self.widget.insert(END, self.format(record) + "\n")
            self.widget.see(END)
            self.widget.config(state='disabled')

    def __init__(self, *args, **kwargs):
        ttk.Frame.__init__(self, *args, **kwargs)

        self.columnconfigure(0, weight=1)
        self.columnconfigure(1, weight=0)
        self.rowconfigure(0, weight=1)

        self.scrollbar = Scrollbar(self)
        self.scrollbar.grid(row=0, column=1, sticky=(N,S,E))

        self.text = Text(self, yscrollcommand=self.scrollbar.set)
        self.text.grid(row=0, column=0, sticky=(N,S,E,W))

        self.scrollbar.config(command=self.text.yview)

        self.logging_handler = LoggingHandlerFrame.Handler(self.text)

也建立在福特的基础上,但添加了彩色文字!

class WidgetLogger(logging.Handler):
    def __init__(self, widget):
        logging.Handler.__init__(self)
        self.setLevel(logging.DEBUG)
        self.widget = widget
        self.widget.config(state='disabled')
        self.widget.tag_config("INFO", foreground="black")
        self.widget.tag_config("DEBUG", foreground="grey")
        self.widget.tag_config("WARNING", foreground="orange")
        self.widget.tag_config("ERROR", foreground="red")
        self.widget.tag_config("CRITICAL", foreground="red", underline=1)

        self.red = self.widget.tag_configure("red", foreground="red")
    def emit(self, record):
        self.widget.config(state='normal')
        # Append message (record) to the widget
        self.widget.insert(tk.END, self.format(record) + '\n', record.levelname)
        self.widget.see(tk.END)  # Scroll to the bottom
        self.widget.config(state='disabled') 
        self.widget.update() # Refresh the widget

我遇到了同样的问题。 找到的常见解决方案就像这个线程中提到的那样——将小部件从 GUI 模块带到 Logging.Handler。
它被批评为不安全,因为小部件被传递到另一个线程(日志记录)。
找到的最佳解决方案是使用 Benjamin Bertrand 制作的队列: https ://beenje.github.io/blog/posts/logging-to-a-tkinter-scrolledtext-widget/。
我对其进行了一些改进:队列应该在日志记录处理程序中创建,并传递给 GUI 小部件。

import logger
import queue


class QueueHandler(logging.Handler):
    def __init__(self):
        super().__init__()
        self.log_queue = queue.Queue()

    def emit(self, record):
        # put a formatted message to queue
        self.log_queue.put(self.format(record))


queue_handler = QueueHandler()
logger.addHandler(queue_handler)

所以我们有普遍可用的记录器,来自任何模块的每条日志消息都将与其他处理程序一起传递到 queue_handler - 到文件等。现在我们可以将 queue_handler 导入到小部件。
大部分代码都取自上面的链接,我只是更改了原始日志记录队列的位置。

from tkinter.scrolledtext import ScrolledText
from  some_logging_module import queue_handler

class ConsoleUi:
    """Poll messages from a logging queue and display them in a scrolled text widget"""

    def __init__(self, frame, queue):
        self.frame = frame
        # Create a ScrolledText wdiget
        self.console = ScrolledText(frame)
        self.console.configure(font='TkFixedFont')
        self.console.pack(padx=10, pady=10, fill=BOTH, expand=True)
        self.log_queue = queue
        # Start polling messages from the queue
        self.frame.after(100, self.poll_log_queue)

    def display(self, msg):
        self.console.configure(state='normal')
        self.console.insert(END, msg + '\n')
        self.console.configure(state='disabled')
        # Autoscroll to the bottom
        self.console.yview(END)

    def poll_log_queue(self):
        # Check every 100ms if there is a new message in the queue to display
        while not self.log_queue.empty():
            msg = self.log_queue.get(block=False)
            self.display(msg)
        self.frame.after(100, self.poll_log_queue)

结果,小部件显示了应用程序的所有日志记录数据。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM