【问题标题】:how to access the GUI class attributes from a different class?如何从不同的类访问 GUI 类属性?
【发布时间】:2013-06-29 09:34:55
【问题描述】:

我想访问 MyApp 类中定义的文本字段,以便从 MyThread 类 def run(self) 中写入“步骤 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()

【问题讨论】:

  • 你的新问题很简单;你只是混淆了Thread.startThread.run。有关完整详细信息,请参阅我编辑的答案。
  • 它工作得很好,谢谢。如果我想在 GUI 类中添加另一个按钮来执行 MyThread 类中的命令我应该怎么做我想按下一个按钮来关闭套接字,即执行命令 self.sc.close
  • 如果你想以任何方式从主线程控制工作线程,你需要某种同步或消息传递。但这确实是一个新问题,在评论字段中解释起来肯定太难了,所以请打开一个关于它的新问题。

标签: python multithreading user-interface


【解决方案1】:

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

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

在您的代码中,您刚刚获得了一个静态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()

【讨论】:

  • 非常感谢,但 mtTkinter 已经可用,或者我应该先安装它
  • 如果我想从 gui 类中的条目中获取值到另一个类我应该怎么做
  • 你必须安装mtTkinter;它不是 Python 内置的。
  • 我不明白你的第二条评论。但是……除了 GUI 对象,您可以在线程之间共享 anything,就像我共享 text_value 一样。你只需要一个Lock(),每个人在修改或访问变量时总是抓取它,除此之外它与在单个线程中使用变量相同。
  • 安装后我应该怎么做,然后如何访问文本字段
猜你喜欢
  • 1970-01-01
  • 2018-09-13
  • 1970-01-01
  • 1970-01-01
  • 2019-05-29
  • 2013-03-24
  • 2019-04-02
  • 1970-01-01
  • 2015-01-23
相关资源
最近更新 更多