Python Tkinter文本小部件,带有自动&;自定义卷轴

Python Tkinter文本小部件,带有自动&;自定义卷轴,python,text,scrollbar,tkinter,autoscroll,Python,Text,Scrollbar,Tkinter,Autoscroll,我编写了一个简单的基于Tkinter的Python应用程序,它从串行连接读取文本并将其添加到窗口中,特别是文本窗口 经过大量的调整和一些非常奇怪的异常后,这是可行的。然后,我通过这样做添加了autoscrolling: self.text.insert(END, str(parsed_line)) self.text.yview(END) 这些线成一条线。线程在读取串行连接时阻塞,拆分行,然后将所有行添加到小部件 这也行得通。然后我想允许用户滚动,这将禁用自动滚动,直到用户滚动回底部 我找到了

我编写了一个简单的基于Tkinter的Python应用程序,它从串行连接读取文本并将其添加到窗口中,特别是文本窗口

经过大量的调整和一些非常奇怪的异常后,这是可行的。然后,我通过这样做添加了autoscrolling:

self.text.insert(END, str(parsed_line))
self.text.yview(END)
这些线成一条线。线程在读取串行连接时阻塞,拆分行,然后将所有行添加到小部件

这也行得通。然后我想允许用户滚动,这将禁用自动滚动,直到用户滚动回底部

我找到了这个 这似乎是相关的。特别是,我尝试了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的index方法有时会返回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
我希望这能帮助你帮助我:)

谢谢


/很难说到底发生了什么,但是你考虑过使用队列吗

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()

关于你的演示脚本

您正在从非GUI线程执行GUI内容。这往往会引起问题

请参阅:

根据noob oddy的宝贵建议,我能够通过使用
Tkinter.generate_event()
方法来生成异步事件和传递信息的队列来重写示例脚本

每次从流中读取一行(由常量字符串和延迟模拟),我都会将该行附加到队列中(因为AFAIK不支持将对象传递给事件方法),然后创建一个新事件

事件回调方法从队列中检索消息并将其添加到文本中。这是因为此方法是从Tkinter Main循环调用的,因此它不会干扰其他作业

以下是脚本:

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)     
导入re、sys、time
从Tkinter进口*
进口Tkinter
导入线程
导入回溯
导入队列
类ReaderThread(threading.Thread):
定义初始化(self、root、queue):
打印“线程初始化”
threading.Thread.\uuuuu init\uuuuuu(自)
self.root=根
self.running=True
self.q=队列
def停止(自):
打印“停止线程”
运行=错误
def运行(自):
打印“线程已启动”
时间。睡眠(5)
尝试:
同时(自运行):
#模拟从串行接口读取时的延迟
睡眠时间(0.05)
curline=“敏捷的棕色狐狸跳过懒狗\n”
尝试:
自我q.put(卷曲线)
self.root.event_generate(“”,when='tail')
#如果失败,窗口已被破坏:结束
除TCLE错误外:
打印e
打破
例外情况除外,如e:
traceback.print_exc(file=sys.stdout)
打印“接收器线程中的异常,正在停止…”
通过
打印“线程停止”
变压器等级:
定义初始化(自):
self.q=Queue.Queue()
self.lineIndex=1
通过
def追加行(自身、事件):
line=self.q.get_nowait()
如果行==无:
返回
i=自索引
curIndex=“1.0”
lowerEdge=1.0
位置=1.0
#获取当前位置
pos=self.scrollbar.get()[1]
#禁用滚动条
self.text.configure(yscrollcommand=None,state=NORMAL)
#添加到文本窗口
self.text.insert(结束,str(行))
startIndex=repr(i)+“0”
curIndex=repr(i)+“结束”
#着色
如果i%6==0:
self.text.tag_add(“警告”,startIndex,curIndex)
elif i%6==1:
self.text.tag_add(“调试”,startIndex,curIndex)
elif i%6==2:
self.text.tag_add(“info”,startIndex,curIndex)
elif i%6==3:
self.text.tag_add(“错误”,startIndex,curIndex)
elif i%6==4:
self.text.tag_add(“致命”,startIndex,curIndex)
i=i+1
#启用滚动条
self.text.configure(yscrollcommand=self.scrollbar.set,state=DISABLED)
#如果之前滚动条位于底部,则自动向下滚动到末尾
#否则允许客户滚动
如果pos==1.0:
self.text.yview(结束)
self.lineIndex=i
def启动(自):
“”“开始从self.in_流逐行读取并分析读取的行”“”
计数=1
self.root=Tk()
self.root.title(“Tkinter自动滚动测试”)#
self.root.bind(“”,self.appendLine)
self.topPane=平移窗口(self.root,orient=水平)
self.topPane.pack(侧面=顶部,填充=X)
self.lowerPane=PanedWindow(self.root,orient=VERTICAL)
self.scrollbar=滚动条(self.root)
self.scrollbar.pack(side=RIGHT,fill=Y)
self.text=text(wrap=WORD,yscrollcommand=self.scrollbar.set)
self.scrollbar.config(命令=self.text.yview)
#日志级别的颜色定义
self.text.tag_config(“调试”,前台=“灰色50”)
self.text.tag_config(“info”,前台=“绿色”)
self.text.tag_config(“警告”,前台=“橙色”)
self.text.tag_config(“错误”,前台)