Bhupen
Bhupen

Reputation: 1320

Consume tkinter mouse event

I have 2 events on the canvas on mouse click.

one on the canvas ...

self.canvas.bind( '<ButtonPress-1>', self.left_mouse_down )

other on a shape in the canvas ...

self.canvas.tag_bind( self.shape, '<Button-1>', self.on_left_click )

The problem I am having is that both the events are getting fired. Is there a way to consume the click event on the shape itself (hopefully not using a global variable)?

Upvotes: 1

Views: 2075

Answers (2)

martineau
martineau

Reputation: 123541

Here's something that's very similar to the second technique mentioned in @Bryan Oakley's answer except that does bind an event handler to each canvas item, not the the Canvas widget itself.

To detect button clicks on widget itself that aren't on any item within it, a background rectangle item that fills the entire canvas is added first and given a special tag, which allows clicks which aren't on top of any other items on the canvas to be handled as special cases (such as calling a different event handler function).

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()

Upvotes: 0

Bryan Oakley
Bryan Oakley

Reputation: 386342

There is no mechanism to prevent the handling of the event by the widget, when both the canvas and a canvas item both are bound to an event.

From the canonical documentation:

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.

Since the binding for the item is invoked first, one solution is to use a variable that the widget binding can use to know it should ignore the event.

Another solution would be to not bind to the canvas items, and let all handling come from the binding to the widget. Within the bound function you can ask the canvas which item was clicked, and then do the item-specific function if something was clicked on.

Here's an example of the second technique:

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()

Upvotes: 1

Related Questions