I wrote a simple Tkinter-based Python application that reads text from a serial connection and adds it to a window, in particular text with an extension.
After a lot of tweaks and some very strange exceptions, this works. Then I added autoscrolling by doing the following:
self.text.insert(END, str(parsed_line)) self.text.yview(END)
These lines are executed in a thread. The thread blocks reading from the serial connection, breaks the lines, and then adds all the lines to the widget.
This also works. Then I wanted to allow the user to scroll, which should turn off auto-scrolling until the user scrolls down the page.
I found this to Leave the βStop Textβ widget while scrolling the page while changing the content, which seems to be related. Especially, I tried the code from DuckAssasin's comment:
if self.myWidgetScrollbar.get() == 1.0: self.myWidget.yview(END)
I also tried .get()[1] , which is actually the element I want (bottom position). However, this results in a failure with the following exception:
Traceback (most recent call last): File "transformer-gui.py", line 119, in run pos = self.scrollbar.get()[1] File "C:\Python26\lib\lib-tk\Tkinter.py", line 2809, in get return self._getdoubles(self.tk.call(self._w, 'get')) File "C:\Python26\lib\lib-tk\Tkinter.py", line 1028, in _getdoubles return tuple(map(getdouble, self.tk.splitlist(string))) ValueError: invalid literal for float(): None
It seems that tkinter is returning None somewhere, which is then parsed as a float. I read somewhere that, for example, the index method of text that is tagged sometimes returns None if the requested location is not displayed.
Hope anyone can help me with this problem!
[EDIT]
Ok, I put together a demo script that can reproduce this problem on my Win XP machine:
import re,sys,time from Tkinter import * import Tkinter import threading import traceback class ReaderThread(threading.Thread): def __init__(self, text, scrollbar): print "Thread init" threading.Thread.__init__(self) self.text = text self.scrollbar = scrollbar self.running = True def stop(self): print "Stopping thread" running = False def run(self): print "Thread started" time.sleep(5) i = 1 try: while(self.running): # emulating delay when reading from serial interface time.sleep(0.05) line = "the quick brown fox jumps over the lazy dog\n" 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) #if(lowerEdge == 1.0): # print "is lower edge!" #self.text.see(curIndex) #else: # print "Customer scrolling", lowerEdge # Get current scrollbar position before inserting #(upperEdge, lowerEdge) = self.scrollbar.get() #print upperEdge, lowerEdge #self.text.update_idletasks() 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): pass def start(self): """starts to read linewise from self.in_stream and parses the read lines""" count = 1 root = Tk() root.title("Tkinter Auto-Scrolling Test") topPane = PanedWindow(root, orient=HORIZONTAL) topPane.pack(side=TOP, fill=X) lowerPane = PanedWindow(root, orient=VERTICAL) scrollbar = Scrollbar(root) scrollbar.pack(side=RIGHT, fill=Y) text = Text(wrap=WORD, yscrollcommand=scrollbar.set) scrollbar.config(command=text.yview) # Color definition for log levels text.tag_config("debug",foreground="gray50") text.tag_config("info",foreground="green") text.tag_config("warn",foreground="orange") text.tag_config("error",foreground="red") text.tag_config("fatal",foreground="#8B008B") # set default color text.config(background="black", foreground="gray"); text.pack(expand=YES, fill=BOTH) lowerPane.add(text) lowerPane.pack(expand=YES, fill=BOTH) t = ReaderThread(text, scrollbar) print "Starting thread" t.start() try: 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)
I skipped this scipt and started to scroll up and down, and after a while I get many different exceptions, such as:
.\source\testtools\device-log-transformer>python tkinter-autoscroll.py Thread init Starting thread Thread started Traceback (most recent call last): File "tkinter-autoscroll.py", line 59, in run self.text.configure(yscrollcommand=self.scrollbar.set, state=DISABLED) File "C:\Python26\lib\lib-tk\Tkinter.py", line 1202, in configure Stopping thread return self._configure('configure', cnf, kw) File "C:\Python26\lib\lib-tk\Tkinter.py", line 1193, in _configure self.tk.call(_flatten((self._w, cmd)) + self._options(cnf)) TclError: invalid command name ".14762592" Exception in receiver thread, stopping... Thread stopped .\source\testtools\device-log-transformer>python tkinter-autoscroll.py Thread init Starting thread Thread started Stopping thread Traceback (most recent call last): File "tkinter-autoscroll.py", line 35, in run pos = self.scrollbar.get()[1] File "C:\Python26\lib\lib-tk\Tkinter.py", line 2809, in get return self._getdoubles(self.tk.call(self._w, 'get')) TclError: invalid command name ".14762512" Exception in receiver thread, stopping... Thread stopped .\source\testtools\device-log-transformer>python tkinter-autoscroll.py Thread init Starting thread Thread started Traceback (most recent call last): File "tkinter-autoscroll.py", line 65, in run self.text.yview(END) File "C:\Python26\lib\lib-tk\Tkinter.py", line 3156, in yview self.tk.call((self._w, 'yview') + what) Stopping threadTclError: invalid command name ".14762592" Exception in receiver thread, stopping... Thread stopped .\source\testtools\device-log-transformer>python tkinter-autoscroll.py Thread init Starting thread Thread started Traceback (most recent call last): File "tkinter-autoscroll.py", line 35, in run pos = self.scrollbar.get()[1] File "C:\Python26\lib\lib-tk\Tkinter.py", line 2809, in get return self._getdoubles(self.tk.call(self._w, 'get')) File "C:\Python26\lib\lib-tk\Tkinter.py", line 1028, in _getdoubles return tuple(map(getdouble, self.tk.splitlist(string))) ValueError: invalid literal for float(): None Exception in receiver thread, stopping... Thread stopped Stopping thread .\source\testtools\device-log-transformer>python tkinter-autoscroll.py Thread init Starting thread Thread started Traceback (most recent call last): File "tkinter-autoscroll.py", line 53, in run self.text.tag_add("error", startIndex, curIndex) File "C:\Python26\lib\lib-tk\Tkinter.py", line 3057, in tag_add (self._w, 'tag', 'add', tagName, index1) + args) TclError: bad option "261.0": must be bbox, cget, compare, configure, count, debug, delete, dlineinfo, dump, edit, get, image, index, insert, mark, pe er, replace, scan, search, see, tag, window, xview, or yview Exception in receiver thread, stopping... Thread stopped
Hope this helps you help me :)
Thanks,
/ J