2015-01-15 51 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