pythontkintertags

Change color or multiple shapes in tkinter independently


I want to create a program where I can toggle the color of many squares with a mouse click. I am able to create the shapes and toggle the color.

My problem is with many squares. My "change color" function toggles the color of ALL the squares simultaneously. I understand the reason is I am using the same tag but I do not know how to use many tags with the same function.

import tkinter
from tkinter import *
import math
from itertools import cycle

# Variables
shot_colors = ['red', 'blue']
scale = 4.04
radius = scale * 50
diameter = 2 * radius
chord_length = scale * 32.5
chord_angle = 37.93
chord_angle_compliment = 360 - chord_angle
x0 = 50
y0 = 50
x1 = x0 + diameter
y1 = y0 + diameter
start = 270 + 0.5 * chord_angle


# Define a function to change the state of the Widget
def change_color(*args, colors=cycle(shot_colors)):
    canvas.itemconfig(tagOrId="shot", fill=next(colors))


root = Tk()
canvas = Canvas(root, width=1000, height=750)

# Create wafer shape, circle with flat oriented down
wafer = canvas.create_arc(x0, y0, x1, y1, start=start, extent=chord_angle_compliment, style=tkinter.CHORD, outline="black",
                  fill="gray", width=2)

# Create shot shapes, rectangle, this will be a for loop to create many shot(s)
shot1 = canvas.create_rectangle(257, 257, 277, 277, outline="black", fill="blue", tag="shot")
shot2 = canvas.create_rectangle(277, 277, 297, 297, outline="black", fill="blue", tag="shot")

# Change color of shot when clicked with mouse
canvas.tag_bind("shot", "<Button-1>", change_color)

canvas.pack()

root.mainloop()

Solution

  • If you want the binding to only affect the item that was clicked on, you can use the tag "current".

    From the official canvas documentation:

    The tag current is managed automatically by Tk; it applies to the current item, which is the topmost item whose drawn area covers the position of the mouse cursor (different item types interpret this in varying ways; see the individual item type documentation for details). If the mouse is not in the canvas widget or is not over an item, then no item has the current tag.

    Your function can be written to use this tag, like in the following example:

    def change_color(*args, colors=cycle(shot_colors)):
        canvas.itemconfig(tagOrId="current", fill=next(colors))
    

    Note: you need to use current only in the function and not in the bind statement. When you bind to shot, the binding will work for all objects with the tag shot.