Reputation: 330
I'm trying to build a simple program to remind me to take breaks while using the computer. I have a reasonable understanding of python but have never played with GUI programming or threading before, so the following is essentially copying/pasting from stackoverflow:
import threading
import time
import Tkinter
class RepeatEvery(threading.Thread):
def __init__(self, interval, func, *args, **kwargs):
threading.Thread.__init__(self)
self.interval = interval # seconds between calls
self.func = func # function to call
self.args = args # optional positional argument(s) for call
self.kwargs = kwargs # optional keyword argument(s) for call
self.runable = True
def run(self):
while self.runable:
self.func(*self.args, **self.kwargs)
time.sleep(self.interval)
def stop(self):
self.runable = False
def microbreak():
root = Tkinter.Tk()
Tkinter.Frame(root, width=250, height=100).pack()
Tkinter.Label(root, text='Hello').place(x=10, y=10)
threading.Timer(3.0, root.destroy).start()
root.mainloop()
return()
thread = RepeatEvery(6, microbreak)
thread.start()
This gives me the first break notification but fails before giving me a second break notification.
Tcl_AsyncDelete: async handler deleted by the wrong thread
fish: Job 1, “python Documents/python/timer/timer.py ” terminated by signal SIGABRT (Abort)
Any ideas? I'm happy to use something other than tkinter for gui-stuff or something other than threading to implement the time stuff.
Based on the answers below, my new working script is as follows:
import Tkinter as Tk
import time
class Window:
def __init__(self):
self.root = None
self.hide = 10 #minutes
self.show = 10 #seconds
def close(self):
self.root.destroy()
return
def new(self):
self.root = Tk.Tk()
self.root.overrideredirect(True)
self.root.geometry("{0}x{1}+0+0".format(self.root.winfo_screenwidth(), self.root.winfo_screenheight()))
self.root.configure(bg='black')
Tk.Label(self.root, text='Hello', fg='white', bg='black', font=('Helvetica', 30)).place(anchor='center', relx=0.5, rely=0.5)
#Tk.Button(text = 'click to end', command = self.close).pack()
self.root.after(self.show*1000, self.loop)
def loop(self):
if self.root:
self.root.destroy()
time.sleep(self.hide*60)
self.new()
self.root.mainloop()
return
Window().loop()
Upvotes: 3
Views: 3215
Reputation: 94951
I think it would be easier for you to achieve this without threads, which Tkinter
does not integrate with very well. Instead, you can use the after
and after_idle
methods to schedule callbacks to run after a certain timeout. You can create one method that shows the window and schedules it to be hidden, and another that hides the window and schedules it to be shown. Then they'll just call each other in an infinite loop:
import tkinter
class Reminder(object):
def __init__(self, show_interval=3, hide_interval=6):
self.hide_int = hide_interval # In seconds
self.show_int = show_interval # In seconds
self.root = Tkinter.Tk()
tkinter.Frame(self.root, width=250, height=100).pack()
tkinter.Label(self.root, text='Hello').place(x=10, y=10)
self.root.after_idle(self.show) # Schedules self.show() to be called when the mainloop starts
def hide(self):
self.root.withdraw() # Hide the window
self.root.after(1000 * self.hide_int, self.show) # Schedule self.show() in hide_int seconds
def show(self):
self.root.deiconify() # Show the window
self.root.after(1000 * self.show_int, self.hide) # Schedule self.hide in show_int seconds
def start(self):
self.root.mainloop()
if __name__ == "__main__":
r = Reminder()
r.start()
Upvotes: 6
Reputation: 1269
I agree with dano. I thought i'd also contribute though, as my way is somewhat smaller than dano's, but uses time for the gap between when the window is visible. hope this helps @vorpal!
import Tkinter
import time
root = Tkinter.Tk()
def close():
root.destroy()
def show():
root.deiconify()
button.config(text = 'click to shutdown', command = close)
def hide():
root.withdraw()
time.sleep(10)
show()
button = Tkinter.Button(text = 'click for hide for 10 secs', command = hide)
button.pack()
root.mainloop()
Upvotes: 2