У меня есть 2 события на холсте по щелчку мыши.
один на холсте ...
self.canvas.bind( '<ButtonPress-1>', self.left_mouse_down )
другое на фигуре на холсте ...
self.canvas.tag_bind( self.shape, '<Button-1>', self.on_left_click )
Проблема, с которой я столкнулся, заключается в том, что оба события запускаются. Есть ли способ использовать событие щелчка на самой фигуре (надеюсь, без использования глобальной переменной)?
Вместо того, чтобы связывать обработчики событий с каждым объектом, почему бы просто не привязать событие к холсту и проверить тег CURRENT
. См. Пример здесь
Я заметил, что в связанной документации написано: «Более простое решение - не допустить, чтобы Tkinter распространял событие на другие обработчики; просто верните строку “break”
из вашего обработчика событий». Это тоже звучит многообещающе.
@martineau Да, похоже, сначала последовательно обрабатывается событие tag_bind.
@martineau, похоже, перерыв не работает.
@martineau: возврат "break"
не работает для привязок к элементам на холсте, потому что привязки к элементам холста не используют механизм тегов привязки.
Хм, думаю, это снова план А (мое первое предложение), тогда ...
@martineau В обоих случаях event.widget возвращает только холст. Время для плана C. В любом случае спасибо!
Неудивительно, поскольку виджет, который получает событие, в обоих случаях является холстом.
Нет механизма, предотвращающего обработку события виджетом, когда и холст, и элемент холста привязаны к событию.
Из канонической документации:
If bindings have been created for a canvas window using the bind command, then they are invoked in addition to bindings created for the canvas's items using the bind widget command. The bindings for items will be invoked before any of the bindings for the window as a whole.
Поскольку привязка для элемента вызывается первой, одним из решений является использование переменной, которую привязка виджета может использовать, чтобы знать, что она должна игнорировать событие.
Другим решением было бы привязать нет к элементам холста и позволить всей обработке исходить от привязки к виджету. В рамках связанной функции вы можете спросить холст, какой элемент был нажат, а затем выполнить специфичную для элемента функцию, если что-то было нажато.
Вот пример второй техники:
import tkinter as tk
import random
def on_click(event):
current = event.widget.find_withtag("current")
if current:
item = current[0]
color = canvas.itemcget(item, "fill")
label.configure(text = "you clicked on item with id %s (%s)" % (item, color))
else:
label.configure(text = "You didn't click on an item")
root = tk.Tk()
label = tk.Label(root, anchor = "w")
canvas = tk.Canvas(root, background = "bisque", width=400, height=400)
label.pack(side = "top", fill = "x")
canvas.pack(fill = "both", expand=True)
for color in ("red", "orange", "yellow", "green", "blue", "violet"):
x0 = random.randint(50, 350)
y0 = random.randint(50, 350)
canvas.create_rectangle(x0, y0, x0+50, y0+50, outline = "black", fill=color)
canvas.bind('<ButtonPress-1>', on_click)
root.mainloop()
Это полезно знать. Кстати, а где «каноническая документация»? effbot? Исходный файл?
@martineau: каноническая документация по tk написана для языка Tcl, который довольно легко переводится на python. Вы можете найти его здесь: tcl.tk/man/tcl8.5/TkCmd/contents.htm. Документация о том, как перевести его на tkinter, находится в разделе Хранитель жизни Tkinter официальной документации python.
Спасибо за ссылки. Ваш второй вариант также работает наоборот. т.е. не привязывать обработчик событий к самому виджету Canvas
в целом, а вместо этого использовать canvas.tag_bind()
и привязывать его к каждому элементу холста индивидуально. Похоже, что в некоторых обстоятельствах это может быть лучше, в зависимости от того, что еще происходит.
@martineau: наоборот, это не сработает, потому что, если вы привяжетесь к объекту или тегу, он будет _только _ срабатывать, когда вы щелкаете по объекту холста. Он не сработает, если вы нажмете на холст в целом (если вы не нарисуете объект, который покрывает весь холст в качестве фона).
Да, я полагаю, вы правы, особенно с учетом того, что OP имеет разные обработчики для каждого типа событий (подразумевая, что они хотят различать два и обрабатывать их по-разному).
Оказывается, добавление фонового объекта, покрывающего весь холст, - очень простая вещь, позволяющая отличать клики самого виджета холста от щелчков любого из элементов, которые он содержит, поэтому я опубликовал код, показывающий, как это сделать, что способ.
Здесь есть что-то, что очень похоже на второй метод, упомянутый в @Bryan Oakley's отвечать, за исключением того, что делает связывает обработчик событий с каждым элементом холста, а не с самим виджетом Canvas
.
Чтобы обнаруживать нажатия кнопок на самом виджете, которых нет ни на одном элементе внутри него, сначала добавляется элемент прямоугольника фона, который заполняет весь холст, и ему присваивается специальный тег, который разрешает щелчки, которые не находятся поверх каких-либо других элементов на холст, который будет обрабатываться как особые случаи (например, вызов другой функции обработчика событий).
import tkinter as tk
import random
BKGR_TAG = '_background'
BKGR_COLOR = 'bisque'
RECT_SIZE = 50
WIDTH, HEIGHT = 400, 400
def on_click(event):
current = event.widget.find_withtag('current')
if current:
item = current[0]
tags = canvas.gettags(item)
if BKGR_TAG in tags:
msg = 'You clicked the background'
# Do other things like call event handler for whole canvas...
else:
color = canvas.itemcget(item, 'fill')
msg = 'You clicked on item with id %s (%s)' % (item, color)
label.configure(text=msg)
root = tk.Tk()
label = tk.Label(root, anchor='w')
canvas = tk.Canvas(root, width=WIDTH, height=HEIGHT)
label.pack(side='top', fill='x')
canvas.pack(fill='both', expand=True)
# Create background rect same size as canvas.
bkgr = canvas.create_rectangle(0, 0, WIDTH, HEIGHT, width=0, fill=BKGR_COLOR,
tags=BKGR_TAG) # Marked with special tag.
canvas.tag_bind(bkgr, '<ButtonPress-1>', on_click)
for color in ('red', 'orange', 'yellow', 'green', 'blue', 'violet'):
x0 = random.randint(RECT_SIZE, WIDTH-RECT_SIZE)
y0 = random.randint(RECT_SIZE, HEIGHT-RECT_SIZE)
id = canvas.create_rectangle(x0, y0, x0+RECT_SIZE, y0+RECT_SIZE,
outline='black', fill=color)
canvas.tag_bind(id, '<ButtonPress-1>', on_click)
root.mainloop()
Все функции обработчика событий tkinter передают аргумент
event
(согласно эта документация). Похоже, вы могли бы изменить свой, чтобы проверить атрибутevent.widget
переданного, чтобы увидеть, сгенерировал ли соответствующий виджет вызов к нему.