python-3.xtkinterradio-buttontoplevel

Tkinter Radiobuttons not working properly in Toplevel window


I'm building a GUI with Tkinter in Python and I'm having trouble with radio buttons. I have two radio buttons in a LabelFrame widget and I'm using a StringVar to track the selected value. However, when I run the program, both radio buttons are initially unselected, but after a few seconds both become selected. Additionally, changes to the selected value are not always reflected in the GUI.

Here is my code:

import tkinter as tk

class MenuSettings:
    def __init__(self, parent, settings):
        
        window = tk.Toplevel(parent)
        window.title("settings")

        database_frame = tk.LabelFrame(
            window, text="choose drive: ", font=("Arial", 12))
        database_frame.pack(fill=tk.X, padx=10, pady=10)

        database_var = tk.StringVar(value=settings.db_path)

        database_radio_g = tk.Radiobutton(
            database_frame, text="G:", value="PATH_G", variable=database_var, 
            font=("Arial", 12))
        database_radio_g.pack(side="left", padx=10)

        database_radio_server = tk.Radiobutton(
            database_frame, text="Server", value="PATH_SERVER", variable=database_var, 
            font=("Arial", 12))
        database_radio_server.pack(side="left", padx=10)

        #-----------------------------------------------------------------------------
        # following code is necessary
        #-----------------------------------------------------------------------------
        #def on_database_var_changed(*args):
        #    print('database_var changed:', database_var.get())

        #database_var.trace('w', on_database_var_changed)       


from wotan.CLS import cls

settings = cls.Settings.getInstance()

class Application(tk.Tk):
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.title("Application")
        self.configure(background='black')

        self._show_menu_settings()


    def _show_menu_settings(self):
        settings_window=MenuSettings(self, settings)



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

if __name__ == '__main__':
    main()

settings is a singleton class to read some variables from a locally stored textfile. Trace statement incl. function is commented out in class MenuSettings

I've tried several things, such as setting the value option for the radio buttons, removing other parts of my code to isolate the issue, use window.update() and window.update_idletasks(), but nothing seems to work.

The only solution I found is to add a trace with a function to print the StringVar, which is not an ideal solution for an App. Does anyone have any suggestions for how to fix this issue?


Solution

  • You need to use instance variable on database_var (in MenuSettings) and settings_window (in Application) to avoid garbage collection:

    class MenuSettings:
        def __init__(self, parent, settings):
            ...
            # use instance variable
            self.database_var = tk.StringVar(value=settings.db_path)
    
            database_radio_g = tk.Radiobutton(
                database_frame, text="G:", value="PATH_G", variable=self.database_var,
                font=("Arial", 12))
            database_radio_g.pack(side="left", padx=10)
    
            database_radio_server = tk.Radiobutton(
                database_frame, text="Server", value="PATH_SERVER", variable=self.database_var,
                font=("Arial", 12))
            database_radio_server.pack(side="left", padx=10)
    
    ...
    
    class Application(tk.Tk):
        ...
    
        def _show_menu_settings(self):
            # use instance variable
            self.settings_window = MenuSettings(self, settings)
    ...
    

    I would suggest that MenuSettings inherits form tk.Toplevel instead, then you don't need to use instance variable on settings_window.