如何从不同的类访问GUI类属性?

时间:2013-06-28 00:06:51

标签: python multithreading user-interface

我想访问MyApp类中定义的文本字段,从MyThread类def run(self)中写入“step 2”: 有点像:

self.text.insert(1.0,“第二步”)

代码:

import threading
import time
from Tkinter import *


class MyThread(threading.Thread):

    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
        time.sleep(5)
        #i want to access the text here

class MyApp(Frame):


    def __init__(self, master):
        Frame.__init__(self, master)

        self.my_widgets()

    def my_widgets(self):
        self.grid()

        self.my_button = Button(self, text="Start my function",
                                          command=self.my_function)
        self.my_button.grid(row=0, column=0)

        self.text = Text(self, width = 60, height = 5, wrap = WORD)
        self.text.grid(row = 10, column = 0, columnspan = 2, sticky = W)

    def my_function(self):
        self.text.insert(1.0,"Step one") 

        mt = MyThread()
        mt.start()


root = Tk()
root.title("Client")
root.geometry("500x500")
app = MyApp(root)

root.mainloop()

我对线程不是很熟悉所以任何帮助都会被贬低

@abarnet使用你的答案我做了这个,但GUI在等待连接时没有响应

from Tkinter import *
from mtTkinter import *
import socket
import sys


class Application(Frame):

    def __init__(self, master):

        Frame.__init__(self, master)
        self.grid()
        self.create_widgets()

    def create_widgets(self):

        self.submit_button = Button(self, text='start', command = self.my_function)
        self.submit_button.grid(row = 2, column = 0, sticky = W)

        self.text = Text(self, width = 60, height = 5, wrap = WORD)
        self.text.grid(row = 10, column = 0, columnspan = 2, sticky = W)

    def my_function(self):

        mt = MyThread()
        mt.start()


class MyThread(threading.Thread):

    def __init__(self):
        threading.Thread.__init__(self)


    def start(self):
        app.text.insert(6.0,'server started')
        app.text.update_idletasks()

        app.text.insert(6.0,'\n'+'waiting for client')

        self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.s.bind(('',1090))
        self.s.listen(1)



        self.sc, address = self.s.accept()
        address = str(address)

        self.instruction = Label(self, text = 'got connection from '+address)
        self.instruction.grid(row=7, column = 0, columnspan = 2, sticky = W)
        self.instruction.update_idletasks()

        msg = self.sc.recv(1024)
        s=msg.find('.')
        g=msg[s:]
        i=1
        f = open('file_'+ str(i)+g,'wb') #open in binary
        i=i+1
        while (True):       
            l = self.sc.recv(1024)

            while (l):

                f.write(l) 
                f.flush()
                l = self.sc.recv(1024)
        f.close()    

        self.sc.close()

        self.s.close()


root = Tk()
root.title("Server")
root.geometry("500x250")
app = Application(root)

root.mainloop()

1 个答案:

答案 0 :(得分:1)

Tkinter不是线程安全的。这意味着您无法从另一个线程访问Tkinter对象。

有很多方法可以解决这个问题,但我认为最简单的方法就是创建一个受锁定保护的普通旧字符串,并在发生变化时将Text的内容复制到该变量中。 / p>

在你的代码中,你只有一个静态Text对象,你可以在明确定义的位置写入,这使得这很容易。如果它是动态变化的,你要么想要绑定一个事件,要么附加一个StringVartrace它,但让我们在这里保持简单。

class MyThread(threading.Thread):

    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
        time.sleep(5)
        with app.text_lock:
            text_value = app.text_value

class MyApp(Frame):
    def __init__(self, master):
        Frame.__init__(self, master)
        self.text_value = ''
        self.text_lock = threading.Lock()
        self.my_widgets()

    def my_widgets(self):
        # ...

    def my_function(self):
        self.text.insert(1.0,"Step one")

        with self.text_lock:
            self.text_value = "Step one" + self.text_value
        # ...

# ...

另一个选择是使用mtTkinter,它通过拦截所有GUI方法并通过队列传递给您一个线程安全的Tkinter。如果你不明白这意味着什么,那就太神奇了。您的MyThread.run方法只能以与主线程中的代码相同的方式访问app.text


此外,值得注意的是,您的代码可能没有任何合理的理由来使用线程。

如果你想让一些代码在大约5秒钟内运行,而不是创建一个休眠5秒的线程,只需要让Tkinter在大约5秒内运行它:

class MyApp(Frame):
    # ...

    def my_function(self):
        self.text.insert(1.0,"Step one") 

        self.after(5000, self.my_thing_to_do_later)

    def my_thing_to_do_later(self):
        # same code you would put in MyThread.run, but now
        # you're on the main thread, so you can just access 
        # self.text directly

当然,和其他任何事件处理程序一样,如果你想在5秒后做的事情需要很长时间或需要阻止或其他什么,这将无法正常工作。但我怀疑你的代码就是这种情况。


在新版本的代码中,除了一件事之外,你几乎把所有事情都搞定了:

您将后台线程功能放在MyThread.start而不是MyThread.run

正如the documentation所说:

  

不应在子类中重写其他方法(构造函数除外)。换句话说,只覆盖此类的__init__()run()方法。

默认的start方法是一个启动新线程的函数,然后新线程执行self.run()。所以,如果你覆盖self.run,你放在那里的任何东西都会在后台线程中运行。但是如果你覆盖start,而不是创建一个新线程,它会执行你在那里实现的任何东西 - 在你的情况下,它是一个阻塞函数。

所以,只需将start重命名为run,一切正常。


作为旁注,有一件事让我看到我是否意外地阻止了主线程,就是在角落里添加一个小时钟。例如,在App中,添加此方法:

def update_clock(self):
    self.clock.configure(text=time.strftime('%H:%M:%S'))
    self.after(1000, self.update_clock)

然后,在create_widgets的末尾添加以下行:

self.clock = Label(self)
self.clock.grid(row=2, column=1)
self.update_clock()