vorpal
vorpal

Reputation: 330

Create a tkinter window every x minutes and then automatically close it after y seconds

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

Answers (2)

dano
dano

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

W1ll1amvl
W1ll1amvl

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

Related Questions