Python Tkinter Text Widget with Auto & Custom Scroll

后端 未结 3 945
天涯浪人
天涯浪人 2021-02-09 15:08

I wrote a simple Tkinter based Python application that reads text from a serial connection and adds it to the window, specifically a text widged.

After a lot of tweaks a

相关标签:
3条回答
  • 2021-02-09 15:09

    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,time
    from Tkinter import *
    import Tkinter
    import threading
    import traceback
    import Queue
    
    
    class 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!

    0 讨论(0)
  • 2021-02-09 15:12

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

    from Tkinter import *
    import time, Queue, thread
    
    def 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()
    
    0 讨论(0)
  • 2021-02-09 15:19

    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

    0 讨论(0)
提交回复
热议问题