2010-05-26 31 views
5

Tôi đang trong quá trình xây dựng một ứng dụng dựa trên GUI với Python/Tkinter được xây dựng trên đỉnh của mô-đun bdb Python hiện có. Trong ứng dụng này, tôi muốn tắt tất cả stdout/stderr từ giao diện điều khiển và chuyển hướng nó đến GUI của tôi. Để thực hiện mục đích này, tôi đã viết một đối tượng Tkinter.Text chuyên dụng (mã ở cuối bài viết).Lỗi phân đoạn khi chuyển hướng sys.stdout sang phụ kiện Tkinter.Text

Ý tưởng cơ bản là khi một cái gì đó được ghi vào sys.stdout, nó xuất hiện dưới dạng một dòng trong "Văn bản" với màu đen. Nếu một cái gì đó được ghi vào sys.stderr, nó hiển thị như một dòng trong "Văn bản" với màu đỏ. Ngay sau khi một cái gì đó được viết, Văn bản luôn cuộn xuống để xem dòng gần đây nhất.

Tôi đang sử dụng Python 2.6.1 tại thời điểm này. Trên Mac OS X 10.5, điều này dường như làm việc tuyệt vời. Tôi đã không có vấn đề với nó. Tuy nhiên, trên RedHat Enterprise Linux 5, tôi khá chắc chắn có được lỗi phân đoạn trong khi chạy tập lệnh. Lỗi phân đoạn không phải lúc nào cũng xảy ra ở cùng một nơi, nhưng nó luôn luôn xảy ra. Nếu tôi nhận xét các dòng sys.stdout=sys.stderr= từ mã của tôi, lỗi phân đoạn dường như biến mất.

Tôi chắc chắn có nhiều cách khác mà tôi có thể phải sử dụng, nhưng có ai có thể thấy bất cứ điều gì tôi đang làm sai ở đây có thể gây ra những lỗi phân đoạn này không? Nó làm tôi khốn khổ. Cảm ơn!

PS - Tôi nhận ra chuyển hướng sys.stderr sang GUI có thể không phải là một ý tưởng tuyệt vời, nhưng tôi vẫn nhận được lỗi phân đoạn ngay cả khi tôi chỉ chuyển hướng sys.stdout chứ không phải sys.stderr. Tôi cũng nhận ra rằng tôi cho phép Văn bản phát triển vô thời hạn vào lúc này.

class ConsoleText(tk.Text): 
    '''A Tkinter Text widget that provides a scrolling display of console 
    stderr and stdout.''' 

    class IORedirector(object): 
     '''A general class for redirecting I/O to this Text widget.''' 
     def __init__(self,text_area): 
      self.text_area = text_area 

    class StdoutRedirector(IORedirector): 
     '''A class for redirecting stdout to this Text widget.''' 
     def write(self,str): 
      self.text_area.write(str,False) 

    class StderrRedirector(IORedirector): 
     '''A class for redirecting stderr to this Text widget.''' 
     def write(self,str): 
      self.text_area.write(str,True) 

    def __init__(self, master=None, cnf={}, **kw): 
     '''See the __init__ for Tkinter.Text for most of this stuff.''' 

     tk.Text.__init__(self, master, cnf, **kw) 

     self.started = False 
     self.write_lock = threading.Lock() 

     self.tag_configure('STDOUT',background='white',foreground='black') 
     self.tag_configure('STDERR',background='white',foreground='red') 

     self.config(state=tk.DISABLED) 

    def start(self): 

     if self.started: 
      return 

     self.started = True 

     self.original_stdout = sys.stdout 
     self.original_stderr = sys.stderr 

     stdout_redirector = ConsoleText.StdoutRedirector(self) 
     stderr_redirector = ConsoleText.StderrRedirector(self) 

     sys.stdout = stdout_redirector 
     sys.stderr = stderr_redirector 

    def stop(self): 

     if not self.started: 
      return 

     self.started = False 

     sys.stdout = self.original_stdout 
     sys.stderr = self.original_stderr 

    def write(self,val,is_stderr=False): 

     #Fun Fact: The way Tkinter Text objects work is that if they're disabled, 
     #you can't write into them AT ALL (via the GUI or programatically). Since we want them 
     #disabled for the user, we have to set them to NORMAL (a.k.a. ENABLED), write to them, 
     #then set their state back to DISABLED. 

     self.write_lock.acquire() 
     self.config(state=tk.NORMAL) 

     self.insert('end',val,'STDERR' if is_stderr else 'STDOUT') 
     self.see('end') 

     self.config(state=tk.DISABLED) 
     self.write_lock.release() 
+3

cũng giống như một sang một bên, tôi sẽ đề nghị * không * tự động cuộn xuống trong mọi trường hợp. Nếu người dùng đã cuộn lên để xem một cái gì đó và sau đó một mục mới được thêm vào, họ sẽ là một người dùng không hài lòng khi những gì họ đang xem xét thay đổi. Thuật toán tôi sử dụng là, nếu dòng cuối cùng hiển thị trước khi nhập văn bản mới, tôi sẽ tự động cuộn. Nếu không thì tôi thì không. –

+0

Cuộc gọi tốt. Tôi chắc chắn rằng một trong những sẽ được hiển thị trong danh sách "Để sửa chữa" của tôi đủ sớm. –

Trả lời

3

Được rồi, vì vậy tôi đã quản lý để theo dõi sự cố. Tôi chưa bao giờ có thể tạo lại vấn đề này trên Mac OS X 10.5.8 nơi tôi đã phát triển mã ban đầu. Các lỗi Phân khúc duy nhất dường như xảy ra trên RedHat Enterprise Linux 5.

Nó chỉ ra rằng đoạn mã này là thủ phạm:

def write(self,val,is_stderr=False): 

     #Fun Fact: The way Tkinter Text objects work is that if they're disabled, 
     #you can't write into them AT ALL (via the GUI or programatically). Since we want them 
     #disabled for the user, we have to set them to NORMAL (a.k.a. ENABLED), write to them, 
     #then set their state back to DISABLED. 

     self.write_lock.acquire() 
     self.config(state=tk.NORMAL) 

     self.insert('end',val,'STDERR' if is_stderr else 'STDOUT') 
     self.see('end') 

     self.config(state=tk.DISABLED) 
     self.write_lock.release() 

Tôi ước gì có một lời giải thích cho lý do tại sao các lỗi segmentation là xảy ra, nhưng tôi thấy rằng việc liên tục kích hoạt và vô hiệu hóa đối tượng Text là thủ phạm. Nếu tôi thay đổi đoạn mã trên thành điều này:

def write(self,val,is_stderr=False): 

     self.write_lock.acquire() 

     self.insert('end',val,'STDERR' if is_stderr else 'STDOUT') 
     self.see('end') 

     self.write_lock.release() 

Lỗi phân đoạn của tôi biến mất khi tôi xóa cuộc gọi self.config(state=...). Toàn bộ các điểm của cuộc gọi self.config(state=...) là để thực hiện nó để người dùng không thể chỉnh sửa trường Văn bản. Khi trường Văn bản ở trạng thái tk.DISABLED, các cuộc gọi đến self.insert(...) cũng không hoạt động.

Giải pháp khắc phục mà tôi đưa ra là để trường Văn bản được bật, nhưng làm trường Văn bản bỏ qua tất cả đầu vào bàn phím (do đó tạo ảo giác về hành vi chỉ đọc nếu người dùng cố gắng sử dụng bàn phím) .Cách đơn giản nhất để làm điều này là để thay đổi phương pháp __init__ trông như thế này (thay đổi trạng thái để tk.NORMAL và thay đổi các ràng buộc cho <Key> sự kiện):

def __init__(self, master=None, cnf={}, **kw): 
     '''See the __init__ for Tkinter.Text for most of this stuff.''' 

     tk.Text.__init__(self, master, cnf, **kw) 

     self.started = False 
     self.write_lock = threading.Lock() 

     self.tag_configure('STDOUT',background='white',foreground='black') 
     self.tag_configure('STDERR',background='white',foreground='red') 

     self.config(state=tk.NORMAL) 
     self.bind('<Key>',lambda e: 'break') #ignore all key presses 

Hy vọng rằng sẽ giúp bất cứ ai chạy vào cùng một vấn đề.

+1

Nash: Thay vì bỏ qua các lần nhấn phím, một giải pháp thay thế là xóa "Văn bản" khỏi thẻ bindtags cho tiện ích. –

3

Tôi giả định đây là một phần của chương trình có chuỗi lớn hơn.

Thay vì sử dụng khóa, hãy viết mã của bạn vào đối tượng hàng đợi an toàn theo chủ đề. Sau đó, trong chủ đề chính của bạn, bạn thăm dò ý kiến ​​hàng đợi và ghi vào tiện ích văn bản. Bạn có thể thực hiện việc bỏ phiếu bằng cách sử dụng vòng lặp sự kiện (so với viết vòng lặp của riêng bạn) bằng cách chạy công việc bỏ phiếu tự sắp xếp lại để chạy vài ms sau đó (sau vài trăm ms có thể là đủ).

+0

Thật không may tôi nghĩ rằng tôi có một vấn đề lớn hơn và xấu hơn nhiều mà tôi vẫn đang gỡ lỗi, nhưng tôi nghĩ rằng mô hình này là một cách sạch hơn và tốt hơn để làm màn hình. Cảm ơn bạn đã phản hồi. –

Các vấn đề liên quan