Python Tkinter Text Widget with Auto & Custom Scroll Python Tkinter Text Widget with Auto & Custom Scroll tkinter tkinter

Python Tkinter Text Widget with Auto & Custom Scroll


It's hard to tell what's really going on but have you considered using a Queue?

from Tkinter import *import time, Queue, threaddef simulate_input(queue):    for i in range(100):        info = time.time()        queue.put(info)        time.sleep(0.5)class Demo:    def __init__(self, root, dataQueue):        self.root = root        self.dataQueue = dataQueue        self.text = Text(self.root, height=10)        self.scroller = Scrollbar(self.root, command=self.text.yview)        self.text.config(yscrollcommand=self.scroller.set)        self.text.tag_config('newline', background='green')        self.scroller.pack(side='right', fill='y')        self.text.pack(fill='both', expand=1)        self.root.after_idle(self.poll)    def poll(self):        try:            data = self.dataQueue.get_nowait()        except Queue.Empty:            pass        else:            self.text.tag_remove('newline', '1.0', 'end')            position = self.scroller.get()            self.text.insert('end', '%s\n' %(data), 'newline')                        if (position[1] == 1.0):                self.text.see('end')        self.root.after(1000, self.poll)q = Queue.Queue()root = Tk()app = Demo(root, q)worker = thread.start_new_thread(simulate_input, (q,))root.mainloop()


Regarding your demo script.

You're doing GUI stuff from the non-GUI thread. That tends to cause problems.

see: http://www.effbot.org/zone/tkinter-threads.htm


OK,

based on the valuable suggestions by noob oddy I was able to rewrite the example script by using the Tkinter.generate_event() method to generate asynchronous event and a queue to pass the information.

Every time a line is read from the stream (which is simulated by a constant string and a delay), I append the line to a queue (because passing objects to the event method is not supported AFAIK) and then create a new event.

The event callback method retrieves the message from the queue and adds it to the Text widged. This works because this method is called from the Tkinter mainloop an thus it cannot interfere with the other jobs.

Here is the script:

import re,sys,timefrom Tkinter import *import Tkinterimport threadingimport tracebackimport Queueclass ReaderThread(threading.Thread):     def __init__(self, root, queue):        print "Thread init"        threading.Thread.__init__(self)         self.root = root        self.running = True        self.q = queue    def stop(self):        print "Stopping thread"        running = False    def run(self):        print "Thread started"        time.sleep(5)        try:            while(self.running):                # emulating delay when reading from serial interface                time.sleep(0.05)                curline = "the quick brown fox jumps over the lazy dog\n"                try:                    self.q.put(curline)                    self.root.event_generate('<<AppendLine>>', when='tail')                # If it failed, the window has been destoyed: over                except TclError as e:                    print e                    break        except Exception as e:            traceback.print_exc(file=sys.stdout)            print "Exception in receiver thread, stopping..."            pass        print "Thread stopped"class Transformer:    def __init__(self):        self.q = Queue.Queue()        self.lineIndex = 1        pass    def appendLine(self, event):        line = self.q.get_nowait()        if line == None:            return        i = self.lineIndex        curIndex = "1.0"        lowerEdge = 1.0        pos = 1.0        # get cur position        pos = self.scrollbar.get()[1]        # Disable scrollbar        self.text.configure(yscrollcommand=None, state=NORMAL)        # Add to text window        self.text.insert(END, str(line))        startIndex = repr(i) + ".0"        curIndex = repr(i) + ".end"        # Perform colorization        if i % 6 == 0:            self.text.tag_add("warn", startIndex, curIndex)        elif i % 6 == 1:            self.text.tag_add("debug", startIndex, curIndex)                                    elif i % 6 == 2:            self.text.tag_add("info", startIndex, curIndex)                                 elif i % 6 == 3:            self.text.tag_add("error", startIndex, curIndex)                                    elif i % 6 == 4:            self.text.tag_add("fatal", startIndex, curIndex)                                    i = i + 1        # Enable scrollbar        self.text.configure(yscrollcommand=self.scrollbar.set, state=DISABLED)        # Auto scroll down to the end if scroll bar was at the bottom before        # Otherwise allow customer scrolling                                if pos == 1.0:            self.text.yview(END)        self.lineIndex = i    def start(self):        """starts to read linewise from self.in_stream and parses the read lines"""        count = 1        self.root = Tk()        self.root.title("Tkinter Auto-Scrolling Test")#        self.root.bind('<<AppendLine>>', self.appendLine)        self.topPane = PanedWindow(self.root, orient=HORIZONTAL)        self.topPane.pack(side=TOP, fill=X)        self.lowerPane = PanedWindow(self.root, orient=VERTICAL)        self.scrollbar = Scrollbar(self.root)        self.scrollbar.pack(side=RIGHT, fill=Y)        self.text = Text(wrap=WORD, yscrollcommand=self.scrollbar.set)        self.scrollbar.config(command=self.text.yview)        # Color definition for log levels        self.text.tag_config("debug",foreground="gray50")        self.text.tag_config("info",foreground="green")        self.text.tag_config("warn",foreground="orange")        self.text.tag_config("error",foreground="red")        self.text.tag_config("fatal",foreground="#8B008B")        # set default color        self.text.config(background="black", foreground="gray");        self.text.pack(expand=YES, fill=BOTH)               self.lowerPane.add(self.text)        self.lowerPane.pack(expand=YES, fill=BOTH)        t = ReaderThread(self.root, self.q)        print "Starting thread"        t.start()        try:            self.root.mainloop()        except Exception as e:            print "Exception in window manager: ", e        t.stop()        t.join()if __name__ == "__main__":    try:        trans = Transformer()        trans.start()    except Exception as e:        print "Error: ", e        sys.exit(1)     

Thanks again to everybody who contributed for your help!