Python Tkinter Text Widget con desplazamiento automático y personalizado
Escribí una aplicación Python simple basada en Tkinter que lee el texto de una conexión en serie y lo agrega a la ventana, específicamente un texto widged.
Después de muchos ajustes y algunas excepciones muy extrañas, esto funciona. Luego agregué el desplazamiento automático haciendo esto:
self.text.insert(END, str(parsed_line))
self.text.yview(END)
Estas líneas se ejecutan en un hilo. El hilo se bloquea al leer desde la conexión en serie, divide las líneas y luego agrega todas las líneas al widget.
Esto también funciona. Luego, quería permitir que el usuario se desplazara, lo que debería deshabilitar el desplazamiento automático hasta que el usuario vuelva a la parte inferior.
Encontré estoDetener el widget de texto de desplazamiento cuando se cambia el contenido que parece estar relacionado. Especialmente, probé el código del comentario de DuckAssasin:
if self.myWidgetScrollbar.get() == 1.0:
self.myWidget.yview(END)
También probé.get()[1]
, que en realidad es el elemento que quiero (posición inferior). Sin embargo, esto se bloquea con la siguiente excepción:
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
Parece que tkinter en algún lugar devuelve None, que luego se analiza como flotante. Leí en alguna parte, que p. el método de índice del texto anulado a veces devuelve Ninguno si la ubicación solicitada no es visible.
Afortunadamente, cualquiera puede ayudarme con este problema!
[EDITAR
Ok, he reunido un script de demostración que puede reproducir este problema en mi máquina Win XP:
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)
Dejo que este scipt se ejecute y empiece a desplazarse hacia arriba y hacia abajo, y después de un tiempo obtengo muchas excepciones siempre diferentes, como:
.\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
Espero que esto te ayude a ayudarme
Gracias
/ J