Как перехватить события на дочерних виджетах tkinter?

В следующем блоке, нажав наa_frame запускает обработчик событияon_frame_click, но нажав наa_label который является ребенкомa_frame не. Есть ли способ заставитьa_frame отлавливать и обрабатывать события, которые возникли у его потомков (предпочтительно без необходимости добавлять обработчики непосредственно к дочерним элементам)? Я использую Python 3.2.3.

import tkinter

def on_frame_click(e):
    print("frame clicked")

tk = tkinter.Tk()
a_frame = tkinter.Frame(tk, bg="red", padx=20, pady=20)
a_label = tkinter.Label(a_frame, text="A Label")
a_frame.pack()
a_label.pack()
tk.protocol("WM_DELETE_WINDOW", tk.destroy)
a_frame.bind("<Button>", on_frame_click)
tk.mainloop()

Ответы на вопрос(3)

я не могу найти прямой метод автоматической привязки к дочерним виджетам (хотя существуют методы привязки ко всему классу виджетов и ко всем виджетам в приложении), но что-то вроде этого было бы достаточно просто.

def bind_tree(widget, event, callback, add=''):
    "Binds an event to a widget and all its descendants."

    widget.bind(event, callback, add)

    for child in widget.children.values():
        bind_tree(child, event, callback, replace_callback)

Просто подумал об этом, но вы также можете поставить прозрачный виджет размеромa_frame вдобавок ко всему, как ребенокa_frame и связать<Button> событие, и тогда вы могли бы обратиться кa_frame какe.widget.master в обратном вызове, чтобы сделать его многоразовым при необходимости. Это, вероятно, сделает то, что вы хотите.

 16 февр. 2014 г., 03:48
Обратите внимание, дети должны быть уже созданы для этого, чтобы работать. В случае попытки поместить это в конструктор это проблема. Чтобы решить эту проблему, я переопределил метод обновления, чтобы выполнить обычное обновление и выполнить привязку. После создания всех детей я вызываю update, который заполняет привязки. Возможно, есть лучший способ, но это работает.
 12 июл. 2012 г., 20:19
@ Обезьяна, я вижу. В таком случае, я просто подумал о чем-то еще, что вы можете попробовать.
 Monkeyer12 июл. 2012 г., 20:08
Это, конечно, вызовет обратный вызов, ноsource события все равно будет дочерним элементом, а не родительским. В моем примере обратного вызова это не будет проблемой (поскольку на источник не ссылаются). Однако, если бы я хотел начать операцию перетаскивания на родительском объекте, это усложнило бы обратный вызов.
Решение Вопроса

вы можете делать то, что вы хотите, но это требует немного работы. Это не означает, что он не поддерживается, просто фактически довольно редко требуется что-то подобное, так что это не поведение по умолчанию.

TL;DR - research "tkinter bind tags"

Модель событий Tkinter включает понятие & quot;bind tags& Quot ;. Это список тегов, связанных с каждым виджетом. Когда событие получено в виджете, каждый тег привязки проверяется на наличие привязки к событию. Если это так, обработчик вызывается. Если нет, то это продолжается. Если обработчик возвращает «break», цепочка разрывается, и больше не учитываются теги.

По умолчанию тегами связывания для виджета являются сам виджет, класс виджета, тег для окна верхнего уровня, в котором находится виджет, и, наконец, специальный тег «все». Тем не менее, вы можете поместить туда любые теги, которые вы хотите, и вы можете изменить порядок.

Практический результат всего этого? Вы можете добавить свой уникальный тег к каждому виджету, а затем добавить одну привязку к этому тегу, которая будет обрабатываться всеми виджетами. Вот пример использования вашего кода в качестве отправной точки (я добавил виджет кнопки, чтобы показать, что это не что-то особенное только для рамок и меток):

import Tkinter as tkinter

def on_frame_click(e):
    print("frame clicked")

def retag(tag, *args):
    '''Add the given tag as the first bindtag for every widget passed in'''
    for widget in args:
        widget.bindtags((tag,) + widget.bindtags())

tk = tkinter.Tk()
a_frame = tkinter.Frame(tk, bg="red", padx=20, pady=20)
a_label = tkinter.Label(a_frame, text="A Label")
a_button = tkinter.Button(a_frame, text="click me!")
a_frame.pack()
a_label.pack()
a_button.pack()
tk.protocol("WM_DELETE_WINDOW", tk.destroy)
retag("special", a_frame, a_label, a_button)
tk.bind_class("special", "<Button>", on_frame_click)
tk.mainloop()

Для получения дополнительной информации о привязке тегов, вы можете быть заинтересованы вмой ответ на вопросКак связать собственные события в текстовом виджете Tkinter после того, как он будет связан текстовым виджетом?, Ответ касается другого вопроса, чем здесь, но он показывает еще один пример использования тегов связывания для решения реальных проблем.

 Monkeyer12 июл. 2012 г., 21:57
bindtags работать как чемпион! Единственное, что я сделал по-другому, былоa_label.bindtags((a_frame,) + a_label.bindtags()) который сделалb_frame.bind("<Button>", on_frame_click) связать метку и фрейм одновременно и дал мне идентификатор фрейма в обратном вызове какe.widget.bindtags()[0] в качестве дополнительного бонуса. Это позволяет мне различать разные иерархии. Спасибо
 12 июл. 2012 г., 21:26
+1: отличное объяснение и пример, ИМХО.

что говорится вУровни привязки раздел этогоонлайн ссылка на TkinterПохоже, это возможно, потому что вы можете привязать обработчик к трем различным уровням.
Подвести итоги:

Instance Level: Bind an event to a specific widget. Class Level: Bind an event to all widgets of a specific class. Application Level: Widget independent -- certain events always invoke a specific handler.

Для получения подробной информации, пожалуйста, обратитесь к первой ссылке.

Надеюсь это поможет.

 Monkeyer12 июл. 2012 г., 20:53
Переход с привязки уровня приложения означает, что обратный вызов должен различать три случая: 1) является ли исходный виджет рамкой? 2) является ли источник дочерним элементом фрейма? 3) является источником чего-то другого. Это может быть очень сложно, если у меня есть несколько различных типов иерархий содержания, которые должны иметь различное поведение.
 Monkeyer12 июл. 2012 г., 20:28
Благодарю. Привязка уровня экземпляра - это то, что я показал в первоначальном вопросе. Привязка на уровне класса имеет ту же проблему: нажатие на метку не считается щелчком по фрейму с точки зрения запуска обработчика события фрейма. Привязка на уровне приложения является более быстрым (но менее мелкозернистым) подходом, подобным решению JAB. В этом случае обратный вызов по-прежнему будет сообщать метку как источник события, и поэтому обратный вызов должен быть достаточно сложным, чтобы подняться вверх по иерархии содержания, чтобы найти рассматриваемого родителя, что я и сделаю, если Я не могу найти лучший вариант.
 12 июл. 2012 г., 21:20
«Уровни связывания» Страница рассказывает только часть истории. Там не просто три уровня, а четыре по умолчанию, и потенциально может быть добавлено бесконечное количество уровней (хотя "уровень" - это немного неправильное обозначение - это больше похоже на последовательность ). Это очень недокументированная особенность Tkinter, но та, которая ставит Tkinter на голову выше механизмов событий практически всех других наборов инструментов. Для получения дополнительной информации перейдите на страницу «Привязать теги». (или прочитайте мой ответ на этот вопрос)

Ваш ответ на вопрос