2

I have a ScrolledText widget where I output all my log messages. Since there are a lot, and this program runs for hours, it gets overloaded.

I need a way of limiting the number of lines, say to 400 lines.

I've tried to clear text like this:

self.mytext.delete(1.0, END)

But it doesn't do anything.

Heres my setup:

class XStatusFrame:

    def __init__(self, master, window):
        self.frame = Frame(master, relief="raised", width=950)
        self.frame.grid(row=4,column=0,sticky=(E,W),columnspan=20,rowspan=2)

        self.clear_btn = Button(self.frame,text="Clear Log", command=lambda: self.clear_log())
        self.clear_btn.pack(side=TOP, anchor=W)

        # text widget
        self.mytext = ScrolledText(self.frame, undo=True,state="disabled", fg="white", bg="black")
        #self.mytext = Text(self.frame, state="disabled", fg="white", bg="black")
        self.mytext.pack(fill=X)

        # Create textLogger
        text_handler = TextHandler(self.mytext)

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

    def clear_log(self):
        self.mytext.delete(1.0, END)



class TextHandler(logging.Handler):

    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 num_lines(self):
        return int(self.text.index('end').split('.')[0]) - 1

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

        # MY Try at limiting number of lines, which doesnt work...
        if self.num_lines() > 5:
            self.text.delete("1.0", END)

    def timestamp(self):
        ts = time.time()
        return datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')
nullwriter
  • 785
  • 9
  • 34

2 Answers2

2

You need to set the state of the Text widget to normal before you try to delete the lines, then disable it again, just like you do when you add a line. Your clear_log function should be:

def clear_log(self):
    self.mytext.configure(state='normal')
    self.mytext.delete(1.0, END)
    self.mytext.configure(state='disabled')
user2676699
  • 398
  • 1
  • 2
  • 9
1

Here's a modified example of a log display that I usually use and cut down to be relevant and should (hopefully) be helpful. I deleted the parts for saving / opening log files. If you need to dump / save log files I can update with the parts for that as well. I'm not familiar with logging so I simply illustrated deleting the text content when the upper bound is reached.

from queue import Queue, Empty
import tkinter as tk
from tkinter import ttk, scrolledtext as stxt
import threading, time

MESSAGES = Queue()
UPPER_BOUND = 400

#Just to randomly have some data so you can see deleting etc
for i in range(10000):
    MESSAGES.put(i)

class Log(tk.Frame):

    def __init__(self, master):

        tk.Frame.__init__(self, master)
        self.log = stxt.ScrolledText(self, bg="black", fg="green2")
        self.log.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
        thread = threading.Thread(target=self.update)
        thread.daemon = 1
        thread.start()

    def check_range(self):

        if float(self.log.index("end-1c")) == UPPER_BOUND:
            self.log.delete("1.0", "end-1c")
            #sleep to pause so you can see it did indeed delete
            time.sleep(5)

    def update(self):

        #I change states here since you typically want it to be read only
        try:
            self.log['state'] = 'normal'
            while True:
                self.check_range()
                data = MESSAGES.get_nowait()
                if not isinstance(data, str):
                    data = str(data)
                self.log.insert(tk.END, data+"\n")
                self.log.see(tk.END)
                self.update_idletasks()
        except Empty:
            self.log['state'] = 'disabled'
        self.after(100, self.update)

if __name__ == "__main__":

    root = tk.Tk()
    log = Log(root)
    log.pack(fill=tk.BOTH, side=tk.TOP, expand=1)
    root.mainloop()

Alternatively you can use another line count method located here

Community
  • 1
  • 1
Pythonista
  • 11,377
  • 2
  • 31
  • 50