0

I am using these calendar modules found in this post for my program, with some slight modifications to the imports to make it work for the latest python version.

I'll just show the snippets of my code that I feel does matter to this problem.

So I have this pop up window that I made that I use for alerts:

#class for pop-up windows for alerts, errors etc.
class PopUpAlert():
    def __init__(self, alert='Alert!'):
        self.root = tk.Tk()

        tk.Label(self.root,
            text=alert,
            font="Verdana 15",
            fg='red',
            padx=10,
            pady=5).pack(side=tk.TOP)

        self.root.bind('<Return>', (lambda event: self.ok()))
        tk.Button(self.root,
            text='ok',
            pady=10,
            command=self.ok).pack(side=tk.TOP)

    def ok(self):
        print('ok clicked')
        self.root.destroy()

The function ok was made just for me to test if the function is even being called. This window works completely fine in my code, except when I try to implement with the calendar, where the "ok" button of my PopUpAlert (which is supposed to destroy the window) stops working:

class CalendarDialog(tkSimpleDialog.Dialog):
    """Dialog box that displays a calendar and returns the selected date"""
    def body(self, master):
        self.calendar = ttkcalendar.Calendar(master)
        self.calendar.pack()

    def apply(self):
        self.result = self.calendar.selection

    def validate(self):
        if self.calendar.selection == None:
            PopUpAlert(alert='Please select a date or click cancel!')
            return False
        return True

The calendar has an "ok" button that is used to confirm selection of the date and close the calendar window. What I was trying to do is make it such that the user cannot click "ok" to close the window if he/she has not picked a date. For that, I used the function validate which is pre-defined in the class tkSimpleDialog.Dialog which my CalendarDialog inherits from. I overwrote the function in my CalendarDialog class to call up PopUpAlert, then returned False to the parent function ok (which is called when the "Ok" button is pressed on the calendar window):

    def ok(self, event=None):

        if not self.validate():
            self.initial_focus.focus_set() # put focus back
            return

        self.withdraw()
        self.update_idletasks()

        self.apply()

        self.cancel()

    def cancel(self, event=None):

        # put focus back to the parent window
        self.parent.focus_set()
        self.destroy()

(The whole thing can be found in the tkSimpleDialog file that's linked in the other SO page that I linked above.)

After commenting out lines one by one I found that the "ok" button on my PopUpAlert only didn't work when self.root.destroy() isn't called on the calendar. Why? How do I fix this?

I already tried changing my PopUpAlert to be a Toplevel window, which also didn't work.

Nathan Tew
  • 432
  • 1
  • 5
  • 21

1 Answers1

0

It would be a lot nicer of you to provide a mcve instead of asking us to make it.

The problem is that a dialog by default disables clicks to other windows, including windows it spawns. To fix this you need to use a Toplevel instead of Tk (as mentioned) AND add this line of code to the end of PopUpAlert.__init__:

self.root.grab_set() 

It would be a lot neater if you subclassed Toplevel rather than that weird wrapper. Here's a mcve:

try:
    import Tkinter as tk
    import tkSimpleDialog as sd
except:
    import tkinter as tk
    from tkinter import simpledialog as sd

#class for pop-up windows for alerts, errors etc.
class PopUpAlert(tk.Toplevel):
    def __init__(self, master, alert='Alert!', **kwargs):
        tk.Toplevel.__init__(self, master, **kwargs)

        tk.Label(self,
            text=alert,
            font="Verdana 15",
            fg='red',
            padx=10,
            pady=5).pack(side=tk.TOP)

        self.bind('<Return>', self.ok)
        tk.Button(self,
            text='ok',
            pady=10,
            command=self.ok).pack(side=tk.TOP)

        self.grab_set() # this window only gets commands

    def ok(self, *args):
        print('ok clicked')
        self.destroy()

class CalendarDialog(sd.Dialog):
    """Dialog box that displays a calendar and returns the selected date"""
    def body(self, master):
        self.calendar = tk.Label(master, text="Whatever you do, don't click 'OK'!")
        self.calendar.pack()

    def validate(self):
        PopUpAlert(self, alert='Please select a date or click cancel!')

def display():
    CalendarDialog(root)

root = tk.Tk()
tk.Button(root, text='data data data', command=display).pack()
root.mainloop()

Note I also got rid of that useless lambda, which happens to be a pet peeve of mine. lambda is great in some cases, but it's very rarely needed.

Novel
  • 13,406
  • 2
  • 25
  • 41
  • Sorry, I didn’t provide a MCVE because I couldn’t even figure out how to make one for this problem which is why I asked here at all. As for the `lambda` does adding `*args` replace the need for that? Also where can I find some explanation as to why subclassing Toplevel and other tkinter widgets is better than what I did? (and thus why my method was "weird") – Nathan Tew Dec 10 '18 at 06:13
  • Here is a neat explanation about why it is discouraged to use multiple instances of Tk - [Why are multiple instances of Tk discouraged?](https://stackoverflow.com/questions/48045401/why-are-multiple-instances-of-tk-discouraged) – BoobyTrap Dec 10 '18 at 08:13
  • @NathanTew the *args are needed for the bind to work, with or without lambda. They also allow a trace to work, should you ever add one. Subclassing is only better because it's neater. You can work with your custom objects just like native objects instead of having to make and use various hooks. Once you work with OOP for a while it will become obvious. – Novel Dec 10 '18 at 20:59