我写了一个简单的基于Tkinter的Python应用程序,它从串行连接读取文本并将其添加到窗口,特别是文本小部件。
经过大量的调整和一些非常奇怪的例外,这是有效的。然后我通过这样做添加了自动滚动:
self.text.insert(END, str(parsed_line))
self.text.yview(END)
这些行在一个线程中运行。线程在从串行连接读取时阻塞,分割行,然后将所有行添加到窗口小部件。
这也有效。然后我想允许用户滚动,这应该禁用自动滚动,直到用户滚动回到底部。
我找到了这个 Stop Text widget from scrolling when content is changed 这似乎是相关的。特别是,我尝试了DuckAssasin评论中的代码:if self.myWidgetScrollbar.get() == 1.0:
self.myWidget.yview(END)
我也试过.get()[1]
,这实际上是我想要的元素(底部位置)。但是,这会崩溃,但有以下异常:
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
似乎tkinter某处返回None,然后将其解析为float。我读到某个地方,例如如果请求的位置不可见,则文本widged的索引方法有时会返回None。
希望有人可以帮我解决这个问题!
[编辑]
好的,我已经组装了一个演示脚本,可以在我的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)
我让这个scipt运行并开始向上和向下滚动,过了一段时间后,我得到了许多总是不同的异常,例如:
.\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
我希望这可以帮助你帮助我:)。
谢谢,
/ J
答案 0 :(得分:2)
很难说出真正发生了什么,但你考虑过使用队列吗?
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()
答案 1 :(得分:2)
答案 2 :(得分:2)
好的,
根据noob oddy的宝贵建议,我能够使用Tkinter.generate_event()
方法重写示例脚本来生成异步事件和队列来传递信息。
每次从流中读取一行(由常量字符串和延迟模拟)时,我将该行附加到队列(因为不支持将对象传递给事件方法AFAIK)然后创建一个新的事件
事件回调方法从队列中检索消息并将其添加到Text widged。这是有效的,因为这个方法是从Tkinter主循环调用的,因此它不会干扰其他工作。
这是脚本:
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)
再次感谢所有为您提供帮助的人!