-1

I just want to close/destroy an overriden TopLevel widget when the main close button ('X' button) is clicked. The overriden TopLevel widget is not created from the root of tKinter, but from a frame.

class MyToplevel(Tki.Toplevel):
    def __init__(self, parent, *args, **kwargs):  # parent is not the root of tKinter, it's a frame
        super().__init__(*args, **kwargs)
        self.protocol("WM_DELETE_WINDOW", self.on_closing)  # Not working
        self.wm_protocol("WM_DELETE_WINDOW", self.on_closing)  # Not working

    def on_closing(self):
        # The key is, how can I call this method when main close button is clicked?
        self.destroy()

I also have tried self.winfo_ismapped() and self.winfo_exists(), but when I click in the close button, nothing happens, because the main window exists.

Asi
  • 170
  • 2
  • 10
  • It is not clear what your problem is. Also the `event` argument of `on_closing()` is not required and should be removed based on your code. – acw1668 Dec 22 '22 at 14:35
  • You are right, in my example `event` argument is not needed, thanks! I forgot it because I also have tried to solve my problem with binding: `self.bind("", self.on_closing)`, but in that case I am detecting when the TopLevel is closed, not the main window. – Asi Dec 22 '22 at 14:49
  • Do you mean that you want to close the main window when the close button of the toplevel is clicked? – acw1668 Dec 22 '22 at 14:50
  • No, the other way around. Sorry, I don't know how to explain it better and simpler: I would like to close/destroy an overriden TopLevel widget when the main close button ('X' button) is clicked. – Asi Dec 22 '22 at 14:51
  • `on_closing()` will destroy the TopLevel, ok, it is what I want. So in other words, how can `on_closing()` be called when the close button of the main window is clicked? – Asi Dec 22 '22 at 14:55
  • I can destroy the widget in other ways: clicking outside the widget or seleting something for example. That's not the problem. The functionality I want to add to the TopLevel widget is to close/destroy when the main close button is clicked – Asi Dec 22 '22 at 15:08
  • Is the parent of the toplevel a frame inside the root window? – acw1668 Dec 22 '22 at 15:32
  • The toplevel should automatically be destroyed when you destroy the root window without any extra code. Can you please provide a complete [mcve] that shows that it does not? – Bryan Oakley Dec 22 '22 at 16:40
  • The widget is hundreds of lines of code long, too long to paste it. After simplifying the widget, is easy to achieve what I need, but removing other important functionalities of the widget. I will have to check and test the code slowly, it doesn't seem to be an easy way to add what I want. – Asi Dec 22 '22 at 19:05
  • We don't need the whole widget, we need a [mcve] that reproduces your problem or illustrates what you want to do. As I wrote earlier, a toplevel _will_ be destroyed when the main window is destroyed unless you're doing something to prevent it. – Bryan Oakley Dec 22 '22 at 21:56
  • I understand a minimal reproducible example would be ideal, and that's why I spent hours trying to do it. But as I wrote earlier, if I simplify the widget without other functionalities I don't have the issue any more. I know a simple toplevel will be destroyed when the main window is closed, but the toplevel is in a widget with multiple elements, and this widget is in a window with multiple frames. So I tried to simplify my question and the code. – Asi Dec 22 '22 at 23:58
  • Sorry, but I thought there may be an easy way to force to destroy an overriden TopLevel widget when the main close button ('X' button) was clicked, without taking into account the whole widget. That's why I have encouraged to ask my first question here, I really tried to do my best for hours. If I haven't explained good enough what I want, I am sorry. It doesn't seem it's possible to achieve what I asked in an easy way without having all the code. – Asi Dec 22 '22 at 23:58
  • *"the toplevel is in a widget with multiple elements"* - a `Toplevel` cannot be in a widget. – acw1668 Dec 23 '22 at 00:45
  • Why not? I have `MyToplevel` inside `MyComboBox` widget. And inside the TopLevel there is a scrollable `MyListbox`. So at the end I have a custom combobox with more features than the standard combobox because MyListbox is able to handle data in the way I need and the standard combobox can not, and the appearance is equal to the standard one. The only difference is what I want to achieve with this question. The standard combobox is closed when the "main close button" is clicked, but not in my custom MyComboBox. – Asi Dec 23 '22 at 00:58

1 Answers1

1

If you want to close the toplevel when the close button ("X") on the root window is clicked, then you need to bind the protocol "WM_DELETE_WINDOW" on the root window as well:

class MyToplevel(Tki.Toplevel):
    def __init__(self, parent, *args, **kwargs):  # parent is not the root of tKinter, it's a frame
        super().__init__(parent, *args, **kwargs)

        # find root window
        self.root = self.winfo_toplevel()
        while self.root.master:
            self.root = self.root.master.winfo_toplevel()

        self.handler = self.root.protocol("WM_DELETE_WINDOW")
        self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
        self.protocol("WM_DELETE_WINDOW", self.on_closing)

    def on_closing(self):
        # restore default handler for root window
        self.root.protocol("WM_DELETE_WINDOW", self.handler)
        self.destroy()

Note that this does not work when more than one toplevel is open simultaneously.

acw1668
  • 40,144
  • 5
  • 22
  • 34
  • Not working, but I really think you are leading me in the right direction. Debugging it, I see I have the root window in `self.root`. But when I click in the "main close button", nothing happens. If I close the TopLevel and then click in the "main close button", `on_closing()` is called. – Asi Dec 22 '22 at 16:55
  • I will try to add a simple reproducible code where `MyToplevel` is instantiated. – Asi Dec 22 '22 at 17:03
  • If root window is stored to `self.root`, then it should work. – acw1668 Dec 23 '22 at 00:00
  • Yes, I agree it should in a simple TopLevel, but the TopLevel is inside a bigger widget (too big to show), there are more elements that I guess are affecting, for example focuses of other elements. I was hoping something like your answer could force to do what I want, but doesn't seem so. I should check all the widget better by myself, and see what's happening. Thank you anyway, I really think your answer was so close, you have my positive vote :) – Asi Dec 23 '22 at 00:10
  • The protocol binding should not be affected by widgets inside the toplevel. – acw1668 Dec 23 '22 at 00:13
  • I can say the close button is affected by `self.grab_set()` (is inside `MyToplevel` class). When I simplify the widget and remove `self.grab_set()`, works, even without your code. But if I have `self.grab_set()`, doesn't work even with your code. – Asi Dec 23 '22 at 00:38
  • Yes, `self.grab_set()` in the toplevel will block the click outside the toplevel. – acw1668 Dec 23 '22 at 00:54