Skip to content

Closing main window throws error and crashes the app #673

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
robertalexa opened this issue May 29, 2025 · 10 comments
Closed

Closing main window throws error and crashes the app #673

robertalexa opened this issue May 29, 2025 · 10 comments
Labels
bug Something isn't working

Comments

@robertalexa
Copy link

Desktop (please complete the following information):

ttkbootstrap==1.13.8
Ubuntu 24.04
Python 3.11

Describe the bug

Currently bumping up from ttkbootstrap 1.10.1 to 1.13.8

When closing the root window (top corner X button), the application crashes with the following error:

https://pastebin.com/fvBxWwj7

(Had to upload it to pastebin because it was too long going over this textfield's max length)

Worth noting is that I use a confirmation window when pressing the X button, asking to confirm if the user wants to close the app - just in case this is meaningful.

I have noticed this PR #650 but I am unsure whether this could be the culprit.

Any input would be greatly appreciated

To Reproduce

Confirmation window code, if you believe it to be relevant:

    def on_closing(self):
        """A hook that is triggered on clicking 'X' icon."""
        # Prevent the closing button from spamming events
        self.protocol("WM_DELETE_WINDOW", lambda: None)
        confirmation_dialog = Messagebox.show_question(
            parent=self, title="Confirmation", message="Do you really want to exit?", position=self.get_screen_center()
        )

        if confirmation_dialog == "Yes":
            self.graceful_exit()
        else:
            # Re-create event hook
            self.protocol("WM_DELETE_WINDOW", self.on_closing)

    def graceful_exit(self):
        """Gracefully exits the application. All threads and queues are stopped."""
        # Signalling threads to stop
        self.stop_event.set()

        # Waiting for threads to stop and join
        for thread in self.threads.values():
            self.__stop_thread(thread, 1.0)

        # Flush queues to make them empty
        for this_queue in self.queues.values():
            self.__stop_queue(this_queue)

        # Destroying instance of the window
        self.destroy()

Expected behavior

No response

Screenshots

Screenshot showing the confimation modal

Image

Additional context

No response

@robertalexa robertalexa added the bug Something isn't working label May 29, 2025
@israel-dryer
Copy link
Owner

@robertalexa, hi, could you please post a small example app that demonstrates this crash. It doesn't have to be fancy, just something I can copy and paste and run to show the behavior you are experiencing.

@TheDemonGuard
Copy link
Contributor

I tried with create a sample app with the information given by @robertalexa as follows:
but most of the "graceful_exit" function is empty as other function such as "self.threads.values()", "self.__stop_thread", "self.queues.values()" are not provided.

The following code will not reproduce the issue with ttkbootstrap 1.13.8 on Windows 11.
Not sure whether this is due to missing functions or whether this issue is specific to Ubuntu.
If possible, I will setup Ubuntu later.

import tkinter as tk
from tkinter import ttk
import ttkbootstrap as ttkb          # pip install ttkbootstrap
from ttkbootstrap.constants import * # pip install ttkbootstrap
from ttkbootstrap.dialogs import *


class test_LabeledScale(ttkb.Window):
    def __init__(self):
        super().__init__()
        self.protocol("WM_DELETE_WINDOW", self.on_closing)
        self.title("Issue 673")
        self.resizable(False, False)

    def on_closing(self):
        """A hook that is triggered on clicking 'X' icon."""
        # Prevent the closing button from spamming events
        self.protocol("WM_DELETE_WINDOW", lambda: None)
        confirmation_dialog = ttkb.dialogs.Messagebox.show_question(
            parent=self, title="Confirmation", message="Do you really want to exit?" #, position=self.get_screen_center()
        )

        if confirmation_dialog == "Yes":
            self.graceful_exit()
        else:
            # Re-create event hook
            self.protocol("WM_DELETE_WINDOW", self.on_closing)

    def graceful_exit(self):
        """Gracefully exits the application. All threads and queues are stopped."""
        # Signalling threads to stop
        # self.stop_event.set()

        # Waiting for threads to stop and join
        # for thread in self.threads.values():
            # self.__stop_thread(thread, 1.0)

        # Flush queues to make them empty
        # for this_queue in self.queues.values():
            # self.__stop_queue(this_queue)

        # Destroying instance of the window
        self.destroy()

def main():
    app = test_LabeledScale()
    app.mainloop()

if __name__ == "__main__":
    main()

@TheDemonGuard
Copy link
Contributor

Desktop (please complete the following information):

ttkbootstrap==1.13.8 Ubuntu 24.04 Python 3.11

Describe the bug

Currently bumping up from ttkbootstrap 1.10.1 to 1.13.8

When closing the root window (top corner X button), the application crashes with the following error:

https://pastebin.com/fvBxWwj7

(Had to upload it to pastebin because it was too long going over this textfield's max length)

Worth noting is that I use a confirmation window when pressing the X button, asking to confirm if the user wants to close the app - just in case this is meaningful.

I have noticed this PR #650 but I am unsure whether this could be the culprit.

Any input would be greatly appreciated

To Reproduce

Confirmation window code, if you believe it to be relevant:

    def on_closing(self):
        """A hook that is triggered on clicking 'X' icon."""
        # Prevent the closing button from spamming events
        self.protocol("WM_DELETE_WINDOW", lambda: None)
        confirmation_dialog = Messagebox.show_question(
            parent=self, title="Confirmation", message="Do you really want to exit?", position=self.get_screen_center()
        )

        if confirmation_dialog == "Yes":
            self.graceful_exit()
        else:
            # Re-create event hook
            self.protocol("WM_DELETE_WINDOW", self.on_closing)

    def graceful_exit(self):
        """Gracefully exits the application. All threads and queues are stopped."""
        # Signalling threads to stop
        self.stop_event.set()

        # Waiting for threads to stop and join
        for thread in self.threads.values():
            self.__stop_thread(thread, 1.0)

        # Flush queues to make them empty
        for this_queue in self.queues.values():
            self.__stop_queue(this_queue)

        # Destroying instance of the window
        self.destroy()

Expected behavior

No response

Screenshots

Screenshot showing the confimation modal

Image

Additional context

No response

Please provide minimum working reproducible demo/sample script.
And any steps/instructions to reproduce the issue.
This will be helpful for anyone who will be interested in investigating the issue.

@robertalexa
Copy link
Author

robertalexa commented May 30, 2025

Hey @israel-dryer @TheDemonGuard

Thank you both for getting back to me.

I have now managed to put together a super trimmed down version and identified the culprit to be related to the Scrollble Frame.

I am attaching a zip file with the app.

scrollable.zip

Again, for transparency, here is the PR that tried to deal with some issues with Scrollable - i suspect this to have contributed to the issue.
#519

Let me know your thoughts

@TheDemonGuard
Copy link
Contributor

Hey @israel-dryer @TheDemonGuard

Thank you both for getting back to me.

I have now managed to put together a super trimmed down version and identified the culprit to be related to the Scrollble Frame.

I am attaching a zip file with the app.

scrollable.zip

Again, for transparency, here is the PR that tried to deal with some issues with Scrollable - i suspect this to have contributed to the issue. #519

Let me know your thoughts

Thank you for providing the reproducible working sample script.
It seems like some recursion, but still trying to figure it out.

@TheDemonGuard
Copy link
Contributor

TheDemonGuard commented May 30, 2025

Hey @israel-dryer @TheDemonGuard
Thank you both for getting back to me.
I have now managed to put together a super trimmed down version and identified the culprit to be related to the Scrollble Frame.
I am attaching a zip file with the app.
scrollable.zip
Again, for transparency, here is the PR that tried to deal with some issues with Scrollable - i suspect this to have contributed to the issue. #519
Let me know your thoughts

Thank you for providing the reproducible working sample script. It seems like some recursion, but still trying to figure it out.

I think the issue is that we are specifying "master=self.container" instead of "master=master" for the constructor "super().init"
If we fix as follows, the issue is fixed.
@israel-dryer , shall I make a PR for this?
Image

TheDemonGuard added a commit to TheDemonGuard/ttkbootstrap that referenced this issue May 30, 2025
Fix for israel-dryer#673.
Need to specify the root container for the super constructor.
(Not an instance frame.)
@TheDemonGuard
Copy link
Contributor

Hey @israel-dryer @TheDemonGuard
Thank you both for getting back to me.
I have now managed to put together a super trimmed down version and identified the culprit to be related to the Scrollble Frame.
I am attaching a zip file with the app.
scrollable.zip
Again, for transparency, here is the PR that tried to deal with some issues with Scrollable - i suspect this to have contributed to the issue. #519
Let me know your thoughts

Thank you for providing the reproducible working sample script. It seems like some recursion, but still trying to figure it out.

I think the issue is that we are specifying "master=self.container" instead of "master=master" for the constructor "super().init" If we fix as follows, the issue is fixed. @israel-dryer , shall I make a PR for this? Image

Made PR #674 for this fix.

@TheDemonGuard
Copy link
Contributor

By the way, for additional information,
this issue can be reproducible on Windows 11 as well.
Hence this is not specific to Ubuntu.

@israel-dryer
Copy link
Owner

@TheDemonGuard, self.container is the right master in this case. It is what makes the frame scrollable and allows it to controll the scrolled behavior. This fix would break the scrollable behavior of the scrolled frame. Here is an example to run to show the breaking behavior.

app = ttk.Window()

sf = ScrolledFrame(app, autohide=True)
sf.pack(fill=BOTH, expand=YES, padx=10, pady=10)

for x in range(20):
    ttk.Checkbutton(sf, text=f"Checkbutton {x}").pack(anchor=W)

app.mainloop()

This has come up before, and I think the problem is in the destroy method where the child is try to destroy the parent. I think I was trying to fix #656. Looking at it again, I think if you take all the destroy calls out except for super().destroy() the issue will be fixed. The question is, does this break anything else? I tried it on the #656 example and it works as well for that.

If you could make this update and test it, then let me know if you see any issues on your end. If you create a PR for it I will approve and merge.

Here is the #656 example:

import ttkbootstrap as ttk
from ttkbootstrap.dialogs.dialogs import Messagebox

def on_close():
    response=Messagebox.show_question("exit?", title="exit?", buttons=["Yes", "No"])
    if response == "Yes": # response = None immediately after prev line; msgbox still showing
        # will never get here, since after clicking Yes (or No), the above call will not return the clicked button,
        # since it already returned None
        root.destroy()

root=ttk.Window(title="test msgbox", resizable=(True, True),)
root.protocol('WM_DELETE_WINDOW', on_close)

root.mainloop()

@TheDemonGuard
Copy link
Contributor

@TheDemonGuard, self.container is the right master in this case. It is what makes the frame scrollable and allows it to controll the scrolled behavior. This fix would break the scrollable behavior of the scrolled frame. Here is an example to run to show the breaking behavior.

app = ttk.Window()

sf = ScrolledFrame(app, autohide=True)
sf.pack(fill=BOTH, expand=YES, padx=10, pady=10)

for x in range(20):
ttk.Checkbutton(sf, text=f"Checkbutton {x}").pack(anchor=W)

app.mainloop()
This has come up before, and I think the problem is in the destroy method where the child is try to destroy the parent. I think I was trying to fix #656. Looking at it again, I think if you take all the destroy calls out except for super().destroy() the issue will be fixed. The question is, does this break anything else? I tried it on the #656 example and it works as well for that.

If you could make this update and test it, then let me know if you see any issues on your end. If you create a PR for it I will approve and merge.

Here is the #656 example:

import ttkbootstrap as ttk
from ttkbootstrap.dialogs.dialogs import Messagebox

def on_close():
response=Messagebox.show_question("exit?", title="exit?", buttons=["Yes", "No"])
if response == "Yes": # response = None immediately after prev line; msgbox still showing
# will never get here, since after clicking Yes (or No), the above call will not return the clicked button,
# since it already returned None
root.destroy()

root=ttk.Window(title="test msgbox", resizable=(True, True),)
root.protocol('WM_DELETE_WINDOW', on_close)

root.mainloop()

Thank you for in-depth insight. I will change and check the behavior.

TheDemonGuard added a commit to TheDemonGuard/ttkbootstrap that referenced this issue May 30, 2025
Update the "destroy" function to fix israel-dryer#673 issue.
This prevents child component from destroying its super component.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants