2015-01-15 81 views
1

首先,我不得不說我不是Python的專家。我從例子中收集到的大部分代碼。從tkinter小部件中啓動線程

我做的研究公平一點,並一直沒能找到一個代碼配置就是喜歡什麼,我試圖做的:從自定義窗口小部件類中啓動一個線程。我知道tkinter有多個線程嘗試與單個窗口小部件進行通信的問題,但我沒有在這裏看到這種情況。

我想這樣做的原因是,每個插件可以啓動/停止和更新它自己。通過這種方式,我可以在同一根窗口內查看來自多個來源的數據,並且可以隨意添加/刪除每個來源。我知道我可以用不同的方式進行編碼(全部在一個班級中),但我想用這種方式嘗試。我認爲這會讓事情變得更清潔。在這個

我的思維過程如下:

  1. 創建一個可重複使用的自定義部件類。
  2. 能夠將多個小部件添加到根窗口(並且能夠將其刪除)
  3. 每個小部件都將獲取數據並獨立於其他部件更新數據。
  4. 使用線程,以便每個小部件都將在後臺執行所需的操作。這樣所有的小部件將同時更新。

我的代碼示例顯示了什麼,我試圖完成的基本知識。在完成的程序中,每個小部件將有一個文本框定義在哪裏獲取數據。

此測試代碼在WindowsXP中使用Python3可以正常工作。每個小部件都可以添加和刪除,並將獨立於其他部件進行更新。

當我在Fedora20中使用Python3運行相同的代碼時,按下開始按鈕時會崩潰。唯一的例外是:

Exception in thread Thread-1: 
Traceback (most recent call last): 
File "/usr/lib/python3.3/threading.py", line 637, in _bootstrap_inner 
    self.run() 
File "/usr/lib/python3.3/threading.py", line 594, in run 
    self._target(*self._args, **self._kwargs) 
File "dummy.py", line 44, in updlabel 
    self.label.config(text=number) 
File "/usr/lib/python3.3/tkinter/__init__.py", line 1263, in configure 
    return self._configure('configure', cnf, kw) 
File "/usr/lib/python3.3/tkinter/__init__.py", line 1254, in _configure 
    self.tk.call(_flatten((self._w, cmd)) + self._options(cnf)) 
_tkinter.TclError: None 

所以,最後,我有2個問題:

  1. 爲什麼不是在Linux下工作,而它在WindowsXP呢?
  2. 我的方法是否有效?如果是,我的代碼在哪裏?

代碼示例:

from threading import Thread 
import tkinter as tk 
import numpy as np 
import time 

class Widget(tk.Frame): 
    def __init__(self, master): 
     tk.Frame.__init__(self, master) 

     self.running = False 
     self.abort = True 

     labelfont = ('times', 20, 'bold') 

     self.label = tk.Label(self, text="---", font=labelfont) 
     self.startb = tk.Button(self, text="START", command=lambda: self.sbpressed()) 
     self.remove = tk.Button(self, text="-", command=lambda: self.rbpressed()) 

     self.startb.grid(row=0, column=0) 
     self.label.grid(row=0, column=1) 
     self.remove.grid(row=0, column=2) 

    def rbpressed(self): 
     self.abort = True 
     while self.running: 
      self.update() 
     self.destroy() 

    def sbpressed(self): 
     if self.running: 
      self.abort = True 
      self.update() 
     else: 
      self.startb["text"] = "ABORT" 
      self.running = True 
      self.abort = False 
      self.update() 
      self.t = Thread(target=self.updlabel, args=()) 
      self.t.start() 

    def updlabel(self): 
     while self.abort == False: 
      number = str(np.random.random_integers(100)) 
      self.label.config(text=number) 
      time.sleep(1) 
     self.startb["text"] = "START" 
     self.running = False 
     self.abort = False 

class Application(tk.Frame): 
    def __init__(self, master=None): 
     tk.Frame.__init__(self, master) 
     self.pack() 
     self.addb = tk.Button(self, text="+", command=lambda: self.addwidget()) 
     self.addb.pack() 
     Widget(self).pack() 

    def addwidget(self): 
     Widget(self).pack() 

root = tk.Tk() 
app = Application(master=root) 
app.mainloop() 
+0

對Arch Linux,Python 3.4.2適合我工作 – matsjoyce 2015-01-15 18:02:31

+0

你是什麼意思「我可以查看來自多個來源的數據」?這些來源是什麼?你的例子顯示了隨機的調用,你絕對不需要線程。你在輪詢其他設備或端口嗎? – 2015-01-15 18:41:42

+0

其他來源我的意思是來自互聯網的數據。示例代碼最簡單,只是爲了說明我在Fedora20中遇到的異常。 – Zalpho 2015-01-15 19:02:59

回答

2

你的基本設計在我看來,有缺陷的。正如你所提到的,tkinter不是設計用於多線程的。您不應該在創建根窗口的線程中調用tkinter函數。這幾乎肯定是你問題的根源。它可能可能工作,或者它可能不 - 這是非線程安全的本質。

此外,作爲一般規則,你不應該調用過update - 它比你更知道,通常是完全不必要的。

普遍接受的解決方案是,你的線程需要把信息到一個隊列,並且你的主線程可以拉動數據從隊列中,並就此採取行動。例如,您可以放置​​一個由小部件和應該顯示的字符串組成的元組。然後,您的主程序可以輪詢隊列,關閉項目並使用新文本配置小部件。

+0

感謝您的信息。我不會不同意你所說的話。儘管如此,如果我不在'rbpressed'中調用'update',中止信號將永遠不會被處理,並且代碼會掛起(卡在我想的'rbpressed'循環中。但是由於這個設計無論如何都是有缺陷的,這一點是沒有意義的。 – Zalpho 2015-01-16 13:31:38