【问题标题】:Why does adding a custom menu break keyboard event binding in Python3/tkinter?为什么在 Python3/tkinter 中添加自定义菜单会破坏键盘事件绑定?
【发布时间】:2017-04-21 10:05:05
【问题描述】:

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

这是我当前的测试代码:

#!/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()

【问题讨论】:

  • 无法复制。我不知道 tkinter 中的 mod2 是什么,但 ctrl、alt、shift 和 super/windows + Q 都没有做任何事情。但是,如果我将热键更改为有效的东西,它可以在有菜单和没有菜单的情况下工作。 (“工作”的意思是“它抛出 SystemExit,但窗口保持打开直到 python 退出”。)
  • 我在 macOS 上运行这个,mod2 在这里代表 Apple 键盘中的 Command 键。另外,请注意在尝试关闭辅助窗口时会发生此问题。我已经编辑了我的问题以更好地反映这一点。
  • 你为什么在这里使用wm_protocol,如果你所做的只是破坏窗口?此外,您正在绑定到可能会删除其他一些窗口的特定窗口。这是有原因的,还是您打算通过绑定删除处理事件的窗口?
  • 最后,你的事件不正确。 Command-w 不是一个合适的事件。你应该使用&lt;Command-w&gt;
  • @BryanOakley 这是用于测试目的的应用程序的小版本。我希望以后能够在用户按下 command-n 或按下十字按钮时调用函数

标签: python tkinter menu keyboard-shortcuts window-management


【解决方案1】:

如果您想创建一个关闭窗口的绑定,您需要该函数作用于接收事件的实际窗口。无论哪个窗口收到事件,您的代码都会删除最后打开的窗口。

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

一旦函数被调用,事件对象可以通过event对象的widget属性告诉你哪个窗口得到了事件。给定这个窗口,你可以通过winfo_toplevel 命令获得这个窗口所在的顶层窗口(或者它本身,如果它是一个顶层窗口)。

例如:

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

【讨论】:

  • 好的,离解决这个问题又近了一步。但我无法在我的示例代码中插入它。我仍然需要保留Toplevel() windows 引用的列表,对吧?
  • @VictorDomingos:我不知道你有没有。你不要让这段代码工作。
  • 好吧,让我在这里多加一点上下文。我正在开始一个对我来说很长的 tkinter 项目。基本上,它将有一个主应用程序窗口,例如,可以通过按下主窗口中的按钮或菜单命令来打开和关闭一些 Toplevel。这些顶级窗口将具有可以与主窗口中的其他表单进行通信的表单,例如将最近创建的联系信息添加到另一个正在编辑的表单中。然后,我将有一些更简单的窗口来显示一些信息,例如单个数据库记录,而与其余部分的交互较少。
  • 我可以提供一个带有源代码的 github 项目的链接,但它可能超出了 StackOverflow 的范围(我可能会滥用你的善意和耐心的帮助)。
  • 我一直在尝试将与每种主要窗口相关的代码分开,但在主窗口类中完成所有绑定可能是一个更好的选择,因为绑定会传播到根。它比我预期的要复杂一些,但我会尝试这种方式。事实上,我并没有意识到我可以使用事件对象。我想这会让事情变得容易得多。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-08-20
  • 2021-05-27
  • 2023-02-10
  • 2011-08-25
  • 1970-01-01
  • 2017-09-15
  • 1970-01-01
相关资源
最近更新 更多