Skip to content
Advertisement

How to wait for result from Tkinter TopLevel window before continuing?

After a user clicks on a button, I want to create a new TopLevel window with suggestions and when user selects his/her suggestion on the toplevel window and clicks the button “Done” I want to destroy the toplevel window and pass the selected result to root window. This is what I want to achieve but until now I am not able to do it properly.

I tried using wait_window on the toplevel window but that didn’t work every time as sometimes it does not return anything or freezes indefinitely.

import tkinter as tk

root = None
BTN = None
listbox = None
selected = None
SUGGESTIONS = [(0, "level 1"), (11, "level 2"), (23, "level 3")]

def select():
    global listbox, SUGGESTIONS, selected

    selected = listbox.get(tk.ANCHOR)
    for (idd, info) in SUGGESTIONS:
        if selected == f_info:
                selected = idd

def show_suggestions():
    global SUGGESTIONS, listbox

    win = tk.TopLevel()
    win.title("Select suggestion")
    win.geometry("400x400")
    
    listbox = tk.Listbox(win, height=20, width=40)
    listbox.pack(pady=15)

    self.btn = tk.Button(win, text="Confirm selection", command=select)
    self.btn.pack(pady=10)

    for (idd, info) in SUGGESTIONS :
        self.listbox.insert(tk.END, f_info) 
    
    #TODO: wait for selected suggestion and assign it to global variable selected

def main():
    global root, BTN
    root = tk.Tk()
    root.title("Youtube to MP3")
    root.geometry("575x475")

    BTN = tk.Button(
        master=root,
        text="List suggestions",
        width=25,
        height=5,
        command=show_suggestions
    )
    BTN.pack(pady=15)
    
    root.mainloop()

Advertisement

Answer

The tkinter method wait_window does exactly what you want, though you could also use wait_visibility or even wait_variable. You claim wait_window is unreliable but that method has been a part of tk for decades and I’ve personally never seen it misbehave.

I recommend implementing this with two pieces of code: a class that implements the window itself, and a function which uses the class to display the window and return the selected item.

The following gives an example. Notice that the value self.selection is initialized to None, and then set to a value when the user clicks the “Confirm Selection” button. Also notice that the show method will get this value before destroying the widget so that it can be retrieved even after the widget has been destroyed.

class SuggestionPopup(tk.Toplevel):
    def __init__(self, parent, suggestions):
        super().__init__(parent)

        self.title("Select suggestion")

        self.listbox = tk.Listbox(self, height=10, width=20)
        self.listbox.pack(pady=15)

        self.btn = tk.Button(self, text="Confirm selection", command=self.select)
        self.btn.pack(pady=10)

        for (idd, info) in suggestions :
            self.listbox.insert(tk.END, info)

        self.selection = None

    def select(self):
        selection = self.listbox.curselection()
        if selection:
            self.selection = self.listbox.get(selection[0])
        self.destroy()

    def show(self):
        self.deiconify()
        self.wm_protocol("WM_DELETE_WINDOW", self.destroy)
        self.wait_window(self)
        return self.selection

The function to display it might look something like this:

def get_suggestion():
    suggestions = ((0, "Item 0"), (1, "Item 1"), (2, "Item 2"))
    popup = SuggestionPopup(root, suggestions)
    result = popup.show()
    return result
User contributions licensed under: CC BY-SA
3 People found this is helpful
Advertisement