【问题标题】:How to wait for result from Tkinter TopLevel window before continuing?如何在继续之前等待 Tkinter TopLevel 窗口的结果?
【发布时间】:2021-08-02 02:26:20
【问题描述】:

用户单击按钮后,我想创建一个带有建议的新 TopLevel 窗口,当用户在顶级窗口上选择他/她的建议并单击“完成”按钮时,我想销毁顶级窗口并传递选择的结果到根窗口。这是我想要实现的目标,但直到现在我都无法正确地做到这一点。

我尝试在顶层窗口上使用wait_window,但每次都不起作用,因为有时它不会返回任何内容或无限期冻结。

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()

【问题讨论】:

  • 这不是最好的主意,但您可以使用 <tk.Toplevel>.mainloop(1) 来处理所有事件,直到只剩下 1 个窗口。如果要确保用户在顶层运行时不输入任何数据,可以使用全局变量作为标志。
  • wait_window 是正确的答案。它应该可靠地工作。如果不是,那么您的代码可能有问题。

标签: python user-interface tkinter


【解决方案1】:

好吧,我解决了这个问题,首先检查是否选择了某些东西,然后通过全局引用顶层窗口来销毁顶层窗口。我还使用wait_window 等待@Bryan Oakley 确认的选择。

最终工作代码:

import tkinter as tk

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

def select():
    global listbox, SUGGESTIONS, selected, toplvlwin

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

def show_suggestions():
    global SUGGESTIONS, listbox, selected, toplvlwin 

    toplvlwin = tk.TopLevel()
    toplvlwin.title("Select suggestion")
    toplvlwin.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) 
    
    toplvlwin.wait_window()

    print(selected) # confirm correct tuple id is returned
    
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()

【讨论】:

    【解决方案2】:

    tkinter 方法 wait_window 完全符合您的要求,但您也可以使用 wait_visibility 甚至 wait_variable。你声称 wait_window 不可靠,但这种方法几十年来一直是 tk 的一部分,我个人从未见过它行为不端。

    我建议使用两段代码来实现这一点:一个实现窗口本身的类,以及一个使用该类显示窗口并返回所选项目的函数。

    下面给出一个例子。请注意,值self.selection 被初始化为None,然后在用户单击“确认选择”按钮时设置为一个值。另请注意,show 方法将在销毁小部件之前获取此值,以便即使在小部件被销毁后也可以检索它。

    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
    

    显示它的函数可能如下所示:

    def get_suggestion():
        suggestions = ((0, "Item 0"), (1, "Item 1"), (2, "Item 2"))
        popup = SuggestionPopup(root, suggestions)
        result = popup.show()
        return result
    

    【讨论】:

    • 是的,这正是我想要的。非常感谢!我在我的回答中解决了这个问题,但你的问题更清晰易懂。但是有一件事,在select 中,无论是否进行了选择,您都会破坏顶层窗口。如果在做出有效选择后窗口被销毁会更好。因此,只有在if selection 为真时才调用 self.destroy() 而不是更好的 imo,并且当用户通过单击窗口上的“X”故意关闭窗口时忽略结果。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多