Python Tkinter:将按键事件绑定到ttk中的“活动”选项卡

Python Tkinter: Binding Keypress Event to Active Tab in ttk.Notebook

摘要

在Python Tkinter应用程序中,使用ttk.Notebook时,如何绑定按键事件,以便仅在包含生成事件的框架的选项卡处于活动状态时才触发(即,对于按钮热键,我如何仅捕获事件)当按钮位于活动标签上时)?

详情

我正在编写一个Tkinter应用程序(我的第一个应用程序),它使用ttk.Notebook对象管理接口的多个部分。我有多个选项卡,其中一些选项卡上具有"相同"按钮,但是根据激活的选项卡,它们具有不同的操作(即,一个选项卡上的"保存"按钮仅从该选项卡中保存项目,而不是从所有标签)。

做到这一点的直观方法是将事件绑定到框架,然后包含"活动"对象的框架将捕获该事件,但这似乎不起作用。但是,如果我将事件绑定到根窗口,则无论选项卡上下文如何,都将调用相同的处理程序。

我认为这将是一个普遍的要求,但是我找不到有关如何执行此操作的信息。

我正在使用Python 3.4.3。

MCVE

这是一个最小的示例,它演示了我观察到的行为。它会生成一个包含五个选项卡的主窗口,每个选项卡都具有Alt-t的事件绑定,这将为该选项卡中的框架触发事件处理程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import tkinter as tk
from tkinter import ttk

class MyTab(ttk.Frame):
   """Frame to be added to each tab of the notebook.

   """


    def __init__(self, master, idx, *args, **kwargs):
        super().__init__(master, *args, **kwargs)
        self._button = ttk.Button(self, text='Tab {}'.format(idx),
                                  command=lambda *args, x=idx: self._handle_button(x, *args),
                                  underline=0)
        self.bind('<Alt-t>', lambda *args, x=idx: self._handle_button(x, *args))
        self._button.pack()
        self.pack()


    def _handle_button(self, x, *args):
        print('Button: Tab {}'.format(x))



class MainWdw(ttk.Frame):
   """Main application window.

   """


    def __init__(self, master, *args, **kwargs):
        super().__init__(master, *args, **kwargs)
        self._nb = ttk.Notebook(self)

        # Generate several tabs and add a MyTab object to each.
        self._tabs = []
        for x in range(1, 6):
            t = MyTab(self, x)
            self._tabs.append(t)
            self._nb.add(t, text='Tab {}'.format(x))

        self._nb.pack(expand=1, fill='both')
        master.title('Sample')
        self.pack(expand=1, fill='both', padx=2, pady=2)



def main():
    root = tk.Tk()
    app = MainWdw(root)
    root.mainloop()



if __name__ == '__main__':
    main()


这实际上不是很常见的要求。大多数GUI不会在这样的页面之间切换。

绑定到根窗口似乎有效但绑定到框架无效的原因至少部分是因为根窗口是特殊的。将绑定添加到窗口小部件时,实际上并没有绑定到窗口小部件。而是将绑定与恰好与小部件具有相同名称的绑定标签相关联。

每个窗口小部件都有一组绑定标签:与窗口小部件同名的标签,还有窗口小部件的内部类的标签(几乎所有默认绑定都与之关联),顶层的标签(或root)窗口,以及特殊标记" all"。因此,当您绑定到根窗口时,所有小部件都将继承此绑定,因为它们都具有根窗口的绑定标签。

由于只希望绑定在框架上触发,或者当框架的任何后代具有焦点时,可以将bind标签添加到框架的所有子级,然后将绑定添加到该标签。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
class MyTab(ttk.Frame):
    def __init__(...):
        ...
        tag = str(self)
        self._add_bindtag(self, tag)
        self.bind_class(tag, '<Alt-t>', lambda *args, x=idx: self._handle_button(x, *args))

    def _add_bindtag(self, widget, tag):
        bindtags = widget.bindtags()
        if tag not in bindtags:
            widget.bindtags((tag,) + bindtags)
        for child in widget.winfo_children():
            self._add_bindtag(child, tag)

有关绑定标签的更多信息,请参见以下答案:

  • https://stackoverflow.com/a/11542200/7432
  • https://stackoverflow.com/a/3513906/7432

有关绑定标记的tcl / tk规范文档在这里:http://tcl.tk/man/tcl8.5/TkCmd/bindtags.htm