I'm doing Tkinter. My functions run correctly (playing a bell sound once a minute), but it's not running on a thread. Whenever I click the Start button, the program window grays out and the top says "Not responding" (because my recursive calls to start() are locking it, I'm assuming.)
Why am I not threading correctly? Thanks.
def start():
now = tt1.time()
listOfTimes = []
for t in timeSlotEFs:
listOfTimes.append(t.get())
now = datetime.now()
timee = now.strftime('%H%M')
print('here')
for t in listOfTimes:
if t==timee:
winsound.PlaySound('newhourlychimebeg.wav',winsound.SND_FILENAME)
s.enterabs(now+60,1,start) #I want to call recursively every 60 seconds
s.run()
def start2():
t = threading.Thread(target=start)
t.run()
startBtn = Button(ssFrame, text='Start', command=start2)
startBtn.grid(row=0,column=0,padx=paddX,pady=paddY)
It feels like you mixed between the definitions that threading.Thread imports from.
You should create run function and then start the thread.
This way i see the result and it works:
from tkinter import *
import threading
root = Tk()
def run():
print('hello Thread')
def start2():
t = threading.Thread(target=run)
t.start()
print(t) # Output: <Thread(Thread-1, stopped 10580)>
startBtn = Button(root, text='Start', command=start2)
startBtn.grid(row=0,column=0)
root.mainloop()
In start2(), I changed t.run() to t.start(). That was it, and it now works. Thx Jim.
Related
I am writing a function which should return two other functions in it until I decide to stop it. Maybe I even want the function to run 5 hours. I write my code, and it runs perfectly except for one problem: when I click on the starting button, the button stays pressed and I cannot close the infinite loop. I want a way to stop my program without doing key-interrupting or something else. I think a button which can stop my started process would be fine.
Here is my button:
self.dugme1 = Button(text="Start ", command=self.start, fg="black", bg="green", font="bold")
self.dugme1.place(relx=0.05, rely=0.65)
Here are my functions:
def greeting(self):
print("hello")
def byee (self):
print("bye")
def start(self):
while True:
self.greeting()
self.byee()
When I click the button these will be run in the terminal infinitely until I stop them using keyboard interrupting. Is there any way to stop it using an elegant way such as a stop button?
You can achieve this with threading. The following code demonstrates a start/stop button for an infinitely running process:
import tkinter as tk
import threading
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.stop_event = threading.Event()
self.thread = threading.Thread(target = self.start, args = (self.stop_event,))
self.btn = tk.Button(self, text = "Start", command = self.start_cmd)
self.btn.pack()
def start_cmd(self):
self.btn.config(text = "Stop", command = self.stop_cmd)
self.thread.start()
def stop_cmd(self):
self.stop_event.set()
self.destroy()
def start(self, stop_event):
while not stop_event.is_set():
self.greeting()
self.byee()
def greeting(self):
print("hello")
def byee(self):
print("bye")
app = App()
app.mainloop()
You can use threading on start function. I am sure you'll find more information about this here on over the internet.
Example below:
import threading
self.dugme1 = Button(text="Start ",
command=lambda: threading.Thread(name='start', target=self.start, daemon=True).start(),
fg="black",
bg="green",
font="bold"
)
then you can have your start function as:
def start(self):
if self.start_running == False:
self.start_running = True
while self.start_running:
self.greeting()
self.byee()
where self.start_running is a variable that is initially set to false at the beginning of your program (or you can set declare it first in start function with a try-except block)
the first if avoids multiple calls to the same button, calling the function more than once.
You can turn "false" the variable self.start_running to stop the execution of start function.
We want the thread to be daemon, so that the function is terminated whenever main terminates.
If you don't want those function to exit abruptly, turn daemon=False, but make sure to signal self.start_running=False, before your program exits.
I have a problem (Python just hang) if I enable the line "thread.join()" after redirected output to tkinter.
Here is the code:
import tkinter as tk
import sys
import threading
def run():
thread = threading.Thread(target=test)
thread.start()
# thread.join()
print('Want to add some code here')
def test():
print('Process thread...')
class Redirect():
def __init__(self, widget):
self.widget = widget
def write(self, text):
self.widget.insert('end', text)
#def flush(self):
# pass
root = tk.Tk()
text = tk.Text(root)
text.pack()
button = tk.Button(root, text='TEST', command=run)
button.pack()
old_stdout = sys.stdout
sys.stdout = Redirect(text)
root.mainloop()
sys.stdout = old_stdout
Current it prints out:
Want to add some code here
Process thread...
And what I expected (when using thread.join(after finished the thread):
Process thread...
Want to add some code here
If I don't use Redirect function, then the case work well, so I guess I need to add something into the class Redirect above.
Can you please help to take a look and give yur comment?
Thanks
If you join the thread, tkinter will be held up until the thread is finished, but you can't start the thread either because it won't give the desired output. The solution is to use a global variable to keep track of whether the thread has finished or not, then check it periodically with root.after.
def run():
global completed
thread = threading.Thread(target=test)
thread.start()
completed = False
wait_for_finish()
def wait_for_finish():
global completed
if completed:
print('Want to add some code here')
else:
root.after(100, wait_for_finish)
def test():
global completed
print('Process thread...')
completed = True
This calls wait_for_finish every 100ms to check if completed is True. When it is, it runs the next part of the code.
I am fairly new to Python and this is my first project using tkinter. I have my entire project working fine with one exception. I have built the tkinter code into a class and it all works but I cannot figure out how to call the methods from outside of the class.
When I create the object on the following line in main, I get NameError: name 'robotGUI' is not defined
class botGUI:
def __init__(self):
#Init Code
def updateStatus(self):
#Code Here
robotGUI = botGUI()
If I initialize the variable "robotGUI" to None, the code runs but when I later try to access one of its methods I get AttributeError: 'NoneType' object has no attribute 'doSomething'. It appears that the robotGUI object is not being created but I do not understand why.
I have searched everywhere and have found some close answers but nothing that exactly pertains to this issue. I have a handfull of other classes that are working perfect in this program so I am sure it has to do with tkinter and its internal mainloop just havent been able to pin point it.
Here is my greatly reduced and simplified code showing the problem:
#!/usr/bin/env python3
#Imports
import socket, select, errno, sys, queue, time, threading, cv2
from tkinter import *
from tkinter import font
from PIL import Image, ImageTk
#GUI
class botGUI:
def __init__(self):
#Create the Window Object and Setup the Window
self.window = Tk()
self.window.geometry("800x480+0+0")
self.window.overrideredirect(True)
self.window.fullScreenState = False
#Code to Generate Gaphics Here .....
#Call Repeating Status Update Script and Start the Main Loop
self.updateStatus()
self.window.mainloop()
def updateStatus(self):
#Code to Handle Updating Screen Objects Here ....
print("Update Status Running")
#Set this function to be called again
self.window.after(1000, lambda: self.updateStatus())
def doSomething(self, myStr):
#Code to change something on the screen ...
print(f"Command: {str(myStr)}")
def doSomethingElse(self, myStr):
#Code to change something on the screen ...
print(f"Command: {str(myStr)}")
#Main Task - Since tKinter is running in the main loop, all of the main loop code is moved to here
def main_loop():
global robotGUI
robotDataReceived = True #This is only for this posting
#Main Loop
while True:
#If Incoming Data from Robot, Get and Process It!
if robotDataReceived:
robotCmdHandler()
#Anti Blocking Delay (Much shorter, set higher for this post)
time.sleep(2)
#Robot Command Handler
def robotCmdHandler():
global robotGUI
#Code to get a command string and process it goes here .....
cmd = "dosomething" #Temporary for this post
#Handle command
if (cmd == "dosomething"):
print("Processing Command")
robotGUI.doSomething("Do This")
if __name__ == '__main__':
global robotGUI
robotGUI = None
#Create and Start Threads
t1 = threading.Thread(target=main_loop, name='t1')
t1.start()
#Create GUI Object
robotGUI = botGUI()
#Wait until threads are finished
t1.join()
Remove the call self.window.mainloop() from botGUI.__init__(), then you can:
create the instance of botGUI: robotGUI = botGUI()
create the thread and start it
call roboGUI.window.mainloop()
Below is the modified code:
#!/usr/bin/env python3
#Imports
import socket, select, errno, sys, queue, time, threading, cv2
from tkinter import *
from tkinter import font
from PIL import Image, ImageTk
#GUI
class botGUI:
def __init__(self):
#Create the Window Object and Setup the Window
self.window = Tk()
self.window.geometry("800x480+0+0")
self.window.overrideredirect(True)
self.window.fullScreenState = False
#Code to Generate Gaphics Here .....
#Call Repeating Status Update Script and Start the Main Loop
self.updateStatus()
#self.window.mainloop()
def updateStatus(self):
#Code to Handle Updating Screen Objects Here ....
print("Update Status Running")
#Set this function to be called again
self.window.after(1000, lambda: self.updateStatus())
def doSomething(self, myStr):
#Code to change something on the screen ...
print(f"Command: {str(myStr)}")
def doSomethingElse(self, myStr):
#Code to change something on the screen ...
print(f"Command: {str(myStr)}")
#Main Task - Since tKinter is running in the main loop, all of the main loop code is moved to here
def main_loop():
#global robotGUI
robotDataReceived = True #This is only for this posting
#Main Loop
while True:
#If Incoming Data from Robot, Get and Process It!
if robotDataReceived:
robotCmdHandler()
#Anti Blocking Delay (Much shorter, set higher for this post)
time.sleep(2)
#Robot Command Handler
def robotCmdHandler():
#global robotGUI
#Code to get a command string and process it goes here .....
cmd = "dosomething" #Temporary for this post
#Handle command
if (cmd == "dosomething"):
print("Processing Command")
robotGUI.doSomething("Do This")
if __name__ == '__main__':
#Create GUI Object
robotGUI = botGUI()
#Create and Start Threads
t1 = threading.Thread(target=main_loop, name='t1')
t1.start()
# start the GUI main loop
robotGUI.window.mainloop()
#Wait until threads are finished
t1.join()
You must define robotGUI outside all of the functions like that:
robotGUI = None
def main_loop():
global robotGUI
robotDataReceived = True #This is only for this posting
#Main Loop
while True:
#If Incoming Data from Robot, Get and Process It!
if robotDataReceived:
robotCmdHandler()
#Anti Blocking Delay (Much shorter, set higher for this post)
time.sleep(2)
I am new to python and trying to implement multiprocess with Tkinter. I am having a main GUI process, with two other "test" processes. This code works fine in Windows, and the main window is displayed and the other two processes are also running. However when I run this code in Ubuntu, it does not work, the two test processes are running but the main GUI window is not displayed.
Can anyone help me on this?
from Tkinter import *
from multiprocessing import Process
import time
def mywind():
root=Tk()
root.title = "MyWindow"
frame=Frame(root)
root.mainloop()
def test1():
while True:
print "In test1"
time.sleep(1)
def test2():
while True:
print "In test2"
time.sleep(1)
if __name__ == '__main__':
p1 = Process(target=test1)
p1.start()
p2 = Process(target=test2)
p2.start()
p = Process(target=mywind)
p.start()
while True:
time.sleep(1)
Try this:
from Tkinter import *
from multiprocessing import Process
import time
root = None
def mywind():
root=Tk()
root.title = "MyWindow"
frame=Frame(root)
return root
def test1():
while True:
print "In test1"
time.sleep(1)
def test2():
while True:
print "In test2"
time.sleep(1)
if __name__ == '__main__':
p1 = Process(target=test1)
p1.start()
p2 = Process(target=test2)
p2.start()
root = mywind()
root.mainloop()
I can't perfectly explain why putting the main loop into the main process rather than a subprocess works. I assume it has something to do with the way Tk resources (and underlying native windowing resources) are managed by the Tkinter library, and collisions with the idea of running them in a separate process.
For example:
import threading
import time
import Tkinter
class MyThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
print "Step Two"
time.sleep(20)
class MyApp(Tkinter.Tk):
def __init__(self):
Tkinter.Tk.__init__(self)
self.my_widgets()
def my_widgets(self):
self.grid()
self.my_button = Tkinter.Button(self, text="Start my function",
command=self.my_function)
self.my_button.grid(row=0, column=0)
def my_function(self):
print "Step One"
mt = MyThread()
mt.start()
while mt.isAlive():
self.update()
print "Step Three"
print "end"
def main():
my_app = MyApp()
my_app.mainloop()
if __name__ == "__main__":
main()
Well, if I start my example it works as expected. I click on a button, my_function starts and the GUI is responsive. But I have read that I should avoid the use of update(). So, it would be nice if some one could explain why and how I have to wait for a thread correctly? Step Two is in a thread because it takes a much longer time than Step One and Step Three, otherwise it would block the GUI.
I'm new to Python and I trying to write my first "program". Maybe I'm thinking in a wrong way since I'm not very experienced...
Regards,
David.
You need to remember that you have an event loop running, so all you need to do is check on the thread each time the event loop makes an iteration. Well, not every time, but periodically.
For example:
def check_thread(self):
# Still alive? Check again in half a second
if self.mt.isAlive():
self.after(500, self.check_thread)
else:
print "Step Three"
def my_function(self):
self.mt = MyThread()
self.mt.start()
self.check_thread()