Reputation: 35
i want to access the text field defined in the MyApp class to write in it 'step 2' from MyThread class def run(self): somethin like that :
self.text.insert(1.0,"Step Two")
code:
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()
i am not very familiar with threading so any help would be apreciated
@abarnet Using your answers down i did this but the GUI is not responding while it is waiting for connection
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()
Upvotes: 2
Views: 1488
Reputation: 365925
Tkinter is not thread-safe. This means that you cannot access Tkinter objects from another thread.
There are a variety of ways around this, but I think the simplest is to just create a plain old string, protected by a lock, and copy the Text
's contents into that variable whenever it changes.
In your code, you've just got a static Text
object that you write into in well-defined places, which makes this easy. If it were changing dynamically, you'd either want to bind an event, or attach a StringVar
and trace
it, but let's keep things simple here.
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
# ...
# ...
A different option is to use mtTkinter
, which gives you a thread-safe Tkinter by intercepting all GUI methods and passing them through a queue. If you don't understand what that means, it's magic. Your MyThread.run
method can just access app.text
the same way code in the main thread could.
Also, it's worth noting that your code probably doesn't really have any good reason to use threads.
If you want some code to run in about 5 seconds, instead of creating a thread to sleep for 5 seconds, just ask Tkinter to run it in about 5 seconds:
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
Of course, like any other event handler, if the stuff you want to do after 5 seconds takes a long time or needs to block or whatever, this won't work. But I doubt that's the case with your code.
In the new version of your code, you almost got everything right, except for one thing:
You put your background thread function in MyThread.start
instead of MyThread.run
.
As the documentation says:
No other methods (except for the constructor) should be overridden in a subclass. In other words, only override the
__init__()
andrun()
methods of this class.
The default start
method is a function that starts a new thread, and that new thread then executes self.run()
. So, if you override self.run
, whatever you put there gets run in the background thread. But if you override start
, instead of creating a new thread, it does whatever you implemented there—which, in your case, is a blocking function.
So, just rename start
to run
, and everything works.
As a side note, one thing that helps me to see whether I've accidentally blocked the main thread is to add a little clock in the corner. For example, in App
, add this method:
def update_clock(self):
self.clock.configure(text=time.strftime('%H:%M:%S'))
self.after(1000, self.update_clock)
Then, at the end of create_widgets
, add these lines:
self.clock = Label(self)
self.clock.grid(row=2, column=1)
self.update_clock()
Upvotes: 2