为什么添加自定义菜单会破坏 Python3/tkinter 中的键盘事件绑定?

Why does adding a custom menu break keyboard event binding in Python3/tkinter?

在下面的代码中,当我按下按钮添加一些辅助 windows,然后尝试使用 "Command-w" 关闭 window 它并不总是关闭活动window。但是,如果我通过注释 self.gerar_menu() 行来禁用菜单创建,windows 将按预期打开和关闭(我的意思是,通过单击红色 'x' 按钮或在 OSX)。知道这里出了什么问题吗?

这是我当前的测试代码:

#!/usr/bin/env python3.6
# encoding: utf-8

import tkinter as tk
import tkinter.font
from tkinter import ttk


class baseApp(ttk.Frame):
    """
    Parent classe for main app window (will include some aditional methods and properties).
    """
    def __init__(self, master, *args, **kwargs):
        super().__init__(master, *args, **kwargs)
        self.master = master
        self.mainframe = ttk.Frame(master)
        self.mainframe.pack()


class App(baseApp):
    """ Base class for the main application window """
    def __init__(self, master, *args, **kwargs):
        super().__init__(master, *args, **kwargs)
        self.master = master

        #self.gerar_menu()  # This line breaks "Command-w" functionality

        self.lbl_text = ttk.Label(self.mainframe, text="This is the Main Window")
        self.lbl_text.pack()
        self.btn = ttk.Button(self.mainframe, text="Open Second window",
                              command=lambda: self.create_detail_window(self, number=0))
        self.btn.pack()
        self.newDetailsWindow = {}
        self.windows_count=0


    def gerar_menu(self):
        """ generate the application menu """
        self.menu = tk.Menu(root)
        root.config(menu=self.menu)

        self.fileMenu = tk.Menu(self.menu)
        self.menu.add_cascade(label="File", menu=self.fileMenu)
        self.fileMenu.add_command(label="New Document", command=None, accelerator="Command+n")


    def create_detail_window(self, *event, number=None):
        self.windows_count += 1
        self.newDetailsWindow[self.windows_count]=tk.Toplevel()
        self.newDetailsWindow[self.windows_count].geometry('900x600+80+130')
        self.newDetailsWindow[self.windows_count].title(f'Detail: {self.windows_count}')

        self.newDetailsWindow[self.windows_count].wm_protocol("WM_DELETE_WINDOW", self.newDetailsWindow[self.windows_count].destroy)
        self.newDetailsWindow[self.windows_count].bind("Command-w", lambda event: self.newDetailsWindow[-1].destroy())

        self.detail_window = detailWindow(self.newDetailsWindow[self.windows_count], self.windows_count)
        self.newDetailsWindow[self.windows_count].focus()
        print(self.newDetailsWindow)


class detailWindow(ttk.Frame):
    """ Base class for secondary windows """
    def __init__(self, master, rep_num, *args,**kwargs):
        super().__init__(master,*args,**kwargs)
        self.num_rep = rep_num
        self.master.minsize(900, 600)
        self.master.maxsize(900, 600)
        print(f"Showing details about nr. {self.num_rep}")
        self.mainframe = ttk.Frame(master)
        self.mainframe.pack()

        self.lbl_text = ttk.Label(self.mainframe,
                                  text=f"Showing details about nr. {self.num_rep}")
        self.lbl_text.pack()


if __name__ == "__main__":
    root = tk.Tk()
    janela_principal = App(root)
    root.title('Main Window')
    root.bind_all("<Mod2-q>", exit)
    root.mainloop()

如果您想创建一个绑定来关闭 window,您需要函数来对接收到事件的实际 window 起作用。无论哪个 window 收到事件,您的代码总是删除最后打开的 window。

第一步是绑定到函数而不是使用 lambda。虽然 lambda 有其用途,但绑定到命名函数更容易调试和维护。

调用函数后,事件对象可以通过 event 对象的 widget 属性告诉您哪个 window 获得了事件。鉴于此 window,您可以通过 winfo_toplevel 命令获取此 window 所在的顶层 window(或本身,如果它是顶层 window)。

例如:

window = tk.Toplevel(...)
...
window.bind("<Command-w>", self.destroy_window)
...
def destroy_window(self, event):
    window = event.widget.winfo_toplevel()
    window.destroy()