0

Hello I am trying to make a simple script which on key press x starts a timer of 45 sec after that when it reaches 10 sec color text changes to red and when countdown comes 0 I want to destroy the timer gui but not the program, and when i press x again program starts doing stuff again and repeats the process

So far I managed this I tried all day I also tried adding on keypress but it become so complicated and didn't worked so I am asking help here

from tkinter import *

root = Tk()

root.geometry("112x55")
root.overrideredirect(True)
root.lift()
root.wm_attributes("-topmost", True)
root.wm_attributes("-disabled", True)
root.wm_attributes("-transparentcolor", "white")
root.resizable(0, 0)

seconds = 45

def timer():

    global seconds
    if seconds > 0:
        seconds = seconds - 1
        mins = seconds // 60
        m = str(mins)

        if mins < 10:
            m = '0' + str(mins)
        se = seconds - (mins * 60)
        s = str(se)

        if se < 10:
            s = '0' + str(se)
        time.set(m + ':' + s)
        timer_display.config(textvariable=time)
        # call this function again in 1,000 milliseconds
        root.after(1000, timer)

    elif seconds == 0:
        seconds.delete("1.0","end")

frames = Frame(root, width=500, height=500)

frames.pack()

time = StringVar()

timer_display = Label(root, font=('Trebuchet MS', 30, 'bold'))

timer_display.place(x=0, y=0)

timer()  # start the timer


root.mainloop()
Owen Singh
  • 15
  • 7
  • What exactly is your problem? You just said "It's not working". It is hard for us to help with your problem if you don't describe your problem specifically. – M-Chen-3 Aug 07 '20 at 23:49
  • See [ask], [tour]. What does the code do that you don't like? – 10 Rep Aug 08 '20 at 06:33

3 Answers3

1

As you used wm_attributes('-transparentcolor', 'white') (but you never set the background color of root and the timer_display to white, so it don't have effect) and overrideredirect(True), that means you want the window totally transparent and borderless. However this has side effect that you may never get the window focus back after the window loses focus.

Also you used wm_attributes('-disabled', True), then you can't have any key press event triggered. It should not be used.

Suggest to use wm_attributes('-alpha', 0.01) to simulate the transparent effect without the above issues.

Below is an example:

from tkinter import *

root = Tk()

root.geometry("+0+0")
root.overrideredirect(True)
root.wm_attributes("-topmost", True)
root.wm_attributes("-alpha", 0.01)
root.resizable(0, 0)

seconds = 45

def countdown(seconds):
    if seconds > 0:
        mins, secs = divmod(seconds, 60)
        timer_display.config(text="{:02d}:{:02d}".format(mins, secs),
                             fg='red' if seconds <= 10 else 'white')
        root.after(1000, countdown, seconds-1)
    else:
        root.wm_attributes('-alpha', 0.01) # make the window "disappear"

def start_countdown(event):
    root.wm_attributes('-alpha', 0.7) # make the window "appear"
    countdown(seconds)

timer_display = Label(root, font=('Trebuchet MS', 30, 'bold'), bg='black')
timer_display.pack()

root.bind('x', start_countdown)
root.bind('q', lambda e: root.destroy()) # provide a way to close the window

root.mainloop()

NOTE #1: If the window loses focus, you can still click somewhere near the top-left corner of the screen to resume window focus, although you cannot see the window.

NOTE #2: If you want system-wise key handler, tkinter does not support it. You need to use other module, like pynput.

acw1668
  • 40,144
  • 5
  • 22
  • 34
  • Thanks sir it worked like charm but the problem is when I press q it get destroy all I wanted was after 45 seconds the timer gets hidden after that when I press again x the timer start again the rest of the script is fine I am really grateful to u – Owen Singh Aug 08 '20 at 07:03
  • Also when I am on some other window or app (for eg I am using chrome) the code doesn't work, then when I press x nothing happens, I want to be an overlay like when I press x even when i am on chrome or some other app it displays the timer on the top of chrome window or any other app – Owen Singh Aug 08 '20 at 07:22
  • As note #2 said, tkinter does not support system-wise key handler. – acw1668 Aug 08 '20 at 08:12
0

You can bind functions to keystroke events with root.bind(event, callback).

If you are using Linux or Mac, root.overrideredirect(True) will prevent your application from receiving keystroke events. You can read more here: Tkinter's overrideredirect prevents certain events in Mac and Linux

Example:
def keydown(e):
    print(f"Key pressed: ")
    print("Key code:", e.keycode)
    print("Key symbol:", e.keysym)
    print("Char:", e.char)

def keyup(e):
    print(f"Key '{e}' released")

root.bind("<KeyPress>", keydown)
root.bind("<KeyRelease>", keyup)
root.focus_set()

Alternatively, you can also bind to specific keys with <Key-KEYSYM>, e.g. <Key-space> for the spacebar. A list with all keysyms can be found here

Some more events are listed here


Implementation example

Here is an example with a custom CountdownLabel class that is derived from tkinter.Label and automatically binds to the spacebar key event.

app.py
from countdown import CountdownLabel
from tkinter import Frame, StringVar, Tk, Button

root = Tk()
root.geometry("120x60")
root.lift()
root.wm_attributes("-topmost", True)
root.resizable(0, 0)

# Not supported on Linux and MacOS
# root.overrideredirect(True)
# root.wm_attributes("-disabled", True)
# root.wm_attributes("-transparentcolor", "white")

timer_display = CountdownLabel(root, 10, 5)
timer_display.pack(fill="both", expand=True)
timer_display.configure(background="white")
timer_display.configure(font=('Trebuchet MS', 26, 'bold'))
timer_display.focus_set()

root.mainloop()
countdown.py
from tkinter import Label

class CountdownLabel(Label):
    # context : A reference to the Label in order to change the text and 
    #           to close it later on
    # duration: Total time in seconds
    # critical: Length of the last timespan before the countdown finishes
    #           in seconds
    def __init__(self, context, duration, critical):
        super().__init__(context)
        self.duration = duration
        self.critical = critical if duration >= critical else duration
        self.update_ui()
        self.bound_sequence = "<Key-space>"
        self.bound_funcid = self.bind(self.bound_sequence, self.get_handler())

    # Returns a function for the event binding that still has access to 
    # the instance variables
    def get_handler(self):

        # Gets executed once when the counter starts through handler() and calls 
        # itself every second from then on to update the GUI
        def tick():
            self.after(1000, tick)
            self.update_ui()
            self.duration -= 1
        
        # Gets executed when time left is less than <critical> (default = 10s)
        # Sets the font color to red
        def change_font_color():
            self.configure(foreground="red")
            # Destroys itself after the countdown finishes
            self.after((self.critical + 1) * 1000, lambda : self.destroy())

        def handler(event):
            self.unbind(self.bound_sequence, self.bound_funcid)
            self.bound_funcid = -1
            self.bound_sequence = None
            self.after((self.duration - self.critical) * 1000, change_font_color)
            tick()

        return handler

    # Updates the displayed time in the label
    def update_ui(self):
        mm = self.duration // 60
        ss = self.duration % 60
        self.config(text="%02d:%02d" % (mm, ss))

    def change_binding(self, sequence):
        if self.bound_funcid > 0:
            self.unbind(self.bound_sequence, self.bound_funcid)
        self.bound_sequence = sequence
        self.funcid = self.bind(self.bound_sequence, self.get_handler())
Don Foumare
  • 444
  • 3
  • 13
0

If you want to destroy the timer GUI, You'll need to make a class like this:

class Timer(Frame):
    def __init__(self, master):
        super().__init__(master)

        # Paste the code you want to run here, make sure you put "self." before it.
        # For example:
        def clicked():
            print('Clicked!')
        self.myButton = Button(self, text="Click me!", border=0, width=25, height=1, command=self.clicked)

        self.logbtn.grid(columnspan=2)

        self.pack()

        if seconds == 0:
            self.destroy() # by using self.destroy(), you tell it to delete the class.
        else:
            # You can put whatever you want it to do if it's not 0 here.
tm = Timer(root)


ineedhelp
  • 23
  • 1
  • 8