带有Auto& amp;的Python Tkinter文本小部件自定义滚动

时间:2012-01-13 12:06:53

标签: python text scrollbar tkinter autoscroll

我写了一个简单的基于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

3 个答案:

答案 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)

关于您的演示脚本。

你正在从非GUI线程做GUI内容。这往往会导致问题。

请参阅:http://www.effbot.org/zone/tkinter-threads.htm

答案 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)     

再次感谢所有为您提供帮助的人!