2012-01-13 48 views
6

Tôi đã viết một ứng dụng Python dựa trên Tkinter đơn giản đọc văn bản từ một kết nối nối tiếp và thêm nó vào cửa sổ, cụ thể là một văn bản được mở rộng.Python Tkinter Text Widget với Auto & Custom Scroll

Sau nhiều lần chỉnh sửa và một số ngoại lệ rất lạ, tính năng này hoạt động. Sau đó, tôi đã thêm tự động kiểm tra bằng cách thực hiện việc này:

self.text.insert(END, str(parsed_line)) 
self.text.yview(END) 

Những dòng này chạy trong một chuỗi. Các chuỗi chặn đọc từ kết nối nối tiếp, chia tách các dòng và sau đó thêm tất cả các dòng vào tiện ích.

Điều này cũng hoạt động. Sau đó, tôi muốn cho phép người dùng cuộn để tắt tự động cuộn cho đến khi người dùng cuộn xuống dưới cùng.

Tôi tìm thấy điều này Stop Text widget from scrolling when content is changed dường như có liên quan. Đặc biệt, tôi đã thử mã từ nhận xét của DuckAssasin:

if self.myWidgetScrollbar.get() == 1.0: 
    self.myWidget.yview(END) 

Tôi cũng đã cố gắng .get()[1] thực sự là phần tử tôi muốn (vị trí dưới cùng). Tuy nhiên, lỗi này xảy ra với ngoại lệ sau:

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 

Có vẻ như tkinter ở đâu đó sẽ trả về Không cái nào sau đó được phân tích cú pháp dưới dạng phao. Tôi đã đọc ở đâu đó, ví dụ: phương pháp chỉ mục của văn bản được mở rộng đôi khi trả về Không nếu vị trí được yêu cầu không hiển thị.

Hy vọng rằng, bất kỳ ai cũng có thể giúp tôi giải quyết vấn đề này!

[EDIT]

Ok, tôi đã tập hợp một kịch bản demo mà có thể tạo lại vấn đề này trên máy Win XP của tôi:

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)  

tôi để chạy scipt này và bắt đầu di chuyển lên xuống và sau một thời gian tôi nhận được rất nhiều trường hợp ngoại lệ luôn luôn khác nhau như:

.\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 

tôi hy vọng điều này sẽ giúp bạn để giúp tôi :)

Cảm ơn,

/J

+0

Bạn có chắc chắn rằng 'self.scrollbar' thực sự là tham chiếu đến tiện ích con cuộn không? 'get' sẽ không bao giờ trả về None. Tệ nhất, nó sẽ trả về '(0.0, 0.0, 0.0, 0.0)'. –

+0

Có, tôi chắc chắn rằng 'selfs.scrollbar' là tham chiếu chính xác. Tuy nhiên, tôi đã không nói rằng 'get()' đã thực sự trả về 'None', tôi chỉ nói rằng ở đâu đó trong ngăn xếp cuộc gọi, Tkinter đã làm (như bạn có thể thấy từ traceback' ValueError: chữ không hợp lệ cho float(): None Tôi không chắc liệu điều này có liên quan gì đến cách mà Tkinter xử lý nội bộ các cuộc gọi phương thức hay không. Theo như tôi hiểu, nó tạo ra một loại nhiệm vụ được gửi đến máy chủ Tkinter và được xử lý một cách đồng bộ. Để gọi 'update_idletask' nhưng điều này làm cho toàn bộ hệ thống bị treo sau một thời gian – jaw

Trả lời

2

OK,

dựa trên ý kiến ​​đóng góp của Noob Oddy tôi đã có thể viết lại kịch bản ví dụ bằng cách sử dụng phương pháp Tkinter.generate_event() để tạo ra sự kiện không đồng bộ và một hàng đợi để vượt qua các thông tin.

Mỗi khi một dòng được đọc từ luồng (được mô phỏng bằng chuỗi không đổi và độ trễ), tôi nối dòng vào hàng đợi (vì chuyển đối tượng đến phương thức sự kiện không được hỗ trợ AFAIK) và sau đó tạo sự kiện mới.

Phương thức gọi lại sự kiện truy xuất thư từ hàng đợi và thêm nó vào Văn bản được mở rộng. Điều này làm việc bởi vì phương pháp này được gọi từ mainchoop Tkinter, do đó nó không thể can thiệp vào các công việc khác.

Đây là kịch bản:

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)  

Cảm ơn một lần nữa để tất cả mọi người đã đóng góp giúp đỡ của bạn!

+0

Tôi đã sử dụng tập lệnh chính xác như trên ngoại trừ việc tạo dữ liệu trong 'ReaderThread', đây thực sự là luồng đầu vào của giao diện nối tiếp. Thật không may, nó vẫn bị treo. Ít thường xuyên hơn trước nhưng vẫn còn, nó sụp đổ. Vì vậy, tôi chèn một sự chậm trễ (0,02) sau khi gọi 'self.root.event_generate'. Nó đã tốt hơn một chút nhưng nó vẫn bị treo: 'tên cửa sổ/số nhận dạng" 40034472set "' – jaw

+0

Ồ, chỉ để thông báo cho bạn, tôi vừa mới có một "thông báo lỗi" mới. Trên thực tế, python.exe, đang ở trong tcl85.dll, đã bị lỗi. Điều này cũng xảy ra ngẫu nhiên. Tóm lại: Tôi nghĩ (Nếu tôi không làm điều gì đó sai), phương thức 'event_generate' có vẻ không đủ ổn định để được sử dụng từ một luồng riêng biệt. – jaw

2

Thật khó để nói những gì đang thực sự xảy ra nhưng có bạn xem là sử dụng một Queue?

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

Tôi nghĩ rằng hàng đợi không phải là vấn đề bởi vì tôi có một luồng đang đọc từ một luồng và sau đó chèn nó và đợi cho đến khi dữ liệu mới đến. Điều duy nhất mà có thể giúp đỡ sẽ là sự chậm trễ bỏ phiếu.Nhưng tần số là cao hơn, đầu ra là tụt hậu – jaw

+0

Ahh, OK, tôi nhận được nó! Trong ví dụ này, 'self.after()' không phải là một Python được xây dựng trong bộ đếm thời gian nhưng Vì vậy, điều đó có nghĩa, tôi CẦN sử dụng bỏ phiếu? IMHO một số loại chống mẫu tôi muốn tránh. – jaw

2

Về tập lệnh demo của bạn.

Bạn đang làm công cụ GUI từ chuỗi không phải GUI. Điều đó có xu hướng gây ra vấn đề.

xem: http://www.effbot.org/zone/tkinter-threads.htm

+0

Cảm ơn gợi ý nhưng tôi đã đọc nó. Và tôi không nhận được sự khác biệt. "Chuỗi GUI" trong cả hai kịch bản của tôi và ví dụ này thực sự là chủ đề chính bởi vì bạn gọi 'root.mainloop()' mà sau đó thực hiện các nhiệm vụ GUI bên trong. Sau đó, bạn cần ít nhất một luồng khác để tương tác với Tkinter. Đó là beeing được thực hiện bởi một thread trong trường hợp của tôi và bởi một thread timer trong trường hợp của ví dụ. Nhưng tôi không thấy sự khác biệt so với quan điểm luồng. – jaw

+0

Xin lỗi, tôi đã gửi trả lời thứ hai cho bài đăng sai. Nó gây nhầm lẫn với câu trả lời và bình luận;). Vì vậy, chỉ cần cho hồ sơ đó (cùng) bình luận một lần nữa: – jaw

+0

Ahh, OK, tôi hiểu rồi! Trong ví dụ này, ´self.after() ´ không phải là một Python được xây dựng trong bộ đếm thời gian mà là một hàm Tkinter. Vậy có nghĩa là, tôi CẦN sử dụng bỏ phiếu? Đây là IMHO một số loại chống mẫu mà tôi muốn tránh. – jaw