Home > OS >  Python/tkinter: Pressing OK in dialog destroys the info in it
Python/tkinter: Pressing OK in dialog destroys the info in it

Time:06-29

I'm using Python/tkinter for a GUI and I found a strange behaviour.

I'm building a very small dialog for selecting the language for my app. Then when closing it, I find that my 'result' variable in the dialog is gone.

Here's the snippet:

import tkinter as tk
from tkinter.simpledialog import Dialog
from tkinter import ttk, LEFT, ACTIVE
from tkinter.ttk import Button, Frame

class LanguageDialog(Dialog):
    lang_dict = {
        'italiano': 'it',
        'español': 'es',
        'english': 'en',
        'galego': 'gl',
    }

    def __init__(self, parent, *args, **kwargs):
        super().__init__(parent, *args, **kwargs)
        self.selected_language = tk.StringVar()
        self.result = 'en'  # default value

    def _on_cmb_change(self, event):
        """
            Keeps updated the result variable with the code I want in the end
        """
        print(self.lang_dict[self.selected_language.get()])
        self.result = self.lang_dict[self.selected_language.get()]

    def body(self, master):
        self.selected_language = tk.StringVar()
        ops = tuple(self.lang_dict.keys())
        cmb_lang = ttk.Combobox(self, values=ops, state='readonly',
                                textvariable=self.selected_language)
        cmb_lang.pack(side=tk.TOP)

        cmb_lang.bind('<<ComboboxSelected>>', self._on_cmb_change)

    def buttonbox(self):
        """add standard button box.

        override if you do not want the standard buttons
        """

        box = Frame(self)

        w = Button(box, text="OK", width=10, command=self.ok, default=ACTIVE)
        w.pack(side=LEFT, padx=5, pady=5)

        self.bind("<Return>", self.ok)

        box.pack()

if __name__ == '__main__':
    root = tk.Tk()
    d = LanguageDialog(root)
    print(f'After the dialog, {d.result}')
    root.mainloop()

I believe I have some misconception on how to use the dialog. While debugging I saw the dialog triggers some destroy() method, but it seems like it is probably coming again to the initializer and executing the

self.result = 'en'

line, even though I didn't create or invoke the dialog again through LanguageDialog().

I searched the web for similar examples and found that they are using the dialog in the same way, for example, here

CodePudding user response:

Please and to root.mainloop to the end of your code.

if __name__ == '__main__':
   root = tk.Tk()
   d = LanguageDialog(root)
   print(f'After the dialog, {d.result}')
   root.mainloop()

CodePudding user response:

I solved it with a global variable not the best way but that's what I could come up now, the code below.Hope it helps you.

import tkinter as tk
from tkinter.simpledialog import Dialog
from tkinter import ttk, LEFT, ACTIVE
from tkinter.ttk import Button, Frame

value_global = 'en'

class LanguageDialog(Dialog):

    def __init__(self, parent, *args, **kwargs):
        super().__init__(parent, *args, **kwargs)
        self.selected_language = tk.StringVar()
        self.result = value_global  # default value

    lang_dict = {
        'italiano': 'it',
        'español': 'es',
        'english': 'en',
        'galego': 'gl',
    }



    def _on_cmb_change(self, event):
        """
            Keeps updated the result variable with the code I want in the end
        """
        print(self.lang_dict[self.selected_language.get()])

        self.result = self.lang_dict[self.selected_language.get()]
        global value_global
        value_global= self.result


    def body(self, master):
        self.selected_language = tk.StringVar()
        ops = tuple(self.lang_dict.keys())
        cmb_lang = ttk.Combobox(self, values=ops, state='readonly',
                                textvariable=self.selected_language)
        cmb_lang.pack(side=tk.TOP)

        cmb_lang.bind('<<ComboboxSelected>>', self._on_cmb_change)

    def buttonbox(self):
        """add standard button box.

        override if you do not want the standard buttons
        """

        box = Frame(self)

        w = Button(box, text="OK", width=10, command=self.ok, default=ACTIVE)
        w.pack(side=LEFT, padx=5, pady=5)

        self.bind("<Return>", self.ok)

        box.pack()

if __name__ == '__main__':
    root = tk.Tk()
    d = LanguageDialog(root)
    print(f'After the dialog, {d.result}')
    root.mainloop()

CodePudding user response:

If you look into the source code of simpledialog.Dialog class, you will find that wait_window() is executed at the end of Dialog.__init__() which tries to make the window like a modal dialog.

So super().__init__(...) inside LanguageDialog.__init__() will not return until the dialog is closed. When the dialog is closed, self.result is reset to 'en'.

You should move the line, self.result = 'en' into the beginning of body() (just like self.selected_language) and then remove the __init__() function.

Below is the modified code:

import tkinter as tk
from tkinter.simpledialog import Dialog
from tkinter import ttk, LEFT, ACTIVE
from tkinter.ttk import Button, Frame

class LanguageDialog(Dialog):
    lang_dict = {
        'italiano': 'it',
        'español': 'es',
        'english': 'en',
        'galego': 'gl',
    }

    def _on_cmb_change(self, event):
        """
            Keeps updated the result variable with the code I want in the end
        """
        print(self.lang_dict[self.selected_language.get()])
        self.result = self.lang_dict[self.selected_language.get()]

    def body(self, master):
        self.selected_language = tk.StringVar()
        # initial self.result to 'en' here
        self.result = 'en'  # default value

        ops = tuple(self.lang_dict.keys())
        cmb_lang = ttk.Combobox(self, values=ops, state='readonly',
                                textvariable=self.selected_language)
        cmb_lang.pack(side=tk.TOP)

        cmb_lang.bind('<<ComboboxSelected>>', self._on_cmb_change)

    def buttonbox(self):
        """add standard button box.

        override if you do not want the standard buttons
        """

        box = Frame(self)

        w = Button(box, text="OK", width=10, command=self.ok, default=ACTIVE)
        w.pack(side=LEFT, padx=5, pady=5)

        self.bind("<Return>", self.ok)

        box.pack()

if __name__ == '__main__':
    root = tk.Tk()
    root.withdraw() # hide the root window
    d = LanguageDialog(root)
    print(f'After the dialog, {d.result}')
    #root.mainloop() # don't need to call mainloop()
  • Related