How to prevent not responding of tkinter program when running a while loop

Viewed 350

I have created a medicine reminder in tkinter.It works but as I click the button "Notify", my program freezes and says not responding but the program still works.

from tkinter import *
from plyer import notification
from datetime import datetime
import tkinter.messagebox as msg

root = Tk()
root.title("Medicine reminder")


def notify():
    msg.showinfo("Reminder Set",f"You will be notified at {timeSlider.get()}. Make sure that you don't close the program")
    while True:
        hour = datetime.now().hour
        if hour == timeSlider.get():
            notification.notify(
                title=f"Take your medicine {medicine.get()}",
                message=f"You have scheduled it for {timeSlider.get()}",
                # displaying time
                timeout=15)
            break


Label(text="Medicine Reminder", font="comicsans 20 bold").pack(anchor="w", padx=10, pady=10)
medicine = StringVar()
medicine_entry = Entry(font="comicsans 20", textvariable=medicine)
medicine_entry.insert(0, "Name of medicine")
medicine_entry.pack(anchor="w", padx=10, pady=5)
timeSlider = Scale(from_=0, to=24, orient=HORIZONTAL)
Label(text="Enter the time you want to get notified at:", font="comicsans 15").pack(anchor="w", padx=10)
timeSlider.pack(anchor="w", padx=10)
Button(text="Notify me", font="comicsans 15 bold", relief=SUNKEN, borderwidth=6, command=notify).pack(anchor="w",
                                                                                                      pady=15, padx=10)

root.mainloop()
1 Answers

The problem with your code was that, you used an infinity loop with while which runs on the same thread as tkinter, hence it cannot run the window as the window is busy doing the while loop, using after which tkinter has inbuilt is one of the ways to tackle this problem.

Method 1 (using after):

def notify():
    global hour
    msg.showinfo("Reminder Set",f"You will be notified at {timeSlider.get()}. Make sure that you don't close the program")
    hour = datetime.now().hour
    root.after(timeSlider.get()*3600000,alert) #converting hour to ms

def alert():    
    notification.notify(
        title=f"Take your medicine {medicine.get()}",
        message=f"You have scheduled it for {timeSlider.get()}",
        # displaying time
        timeout=15)

after() method takes two arguments mainly:

  • ms - time to be run the function
  • func - the function to run after the given ms is finished.

The remaining part of the code remains the same

Method 2 (Threading):

import threading
....
Button(text="Notify me", font="comicsans 15 bold", relief=SUNKEN, borderwidth=6, command=threading.Thread(target=notify).start).pack(anchor="w",

Just make the change to the buttons command argument and the rest of the code, stays still. This will do the trick, but then with threading it might still look inefficient, so I recommend using method 1.

Hope you got your doubts cleared, if any more errors, do let me know.

Cheers

Related