Tkinter/Canvas: Oval nesnenin rengi değişmiyor

Merhaba arkadaşlar,

Başlıkta belirtildiği gibi, oval bir nesnenin rengini değiştirmek istiyorum ama değişmiyor. Uygulamada kullanılabilecek şimdilik iki tane nesne tanımladım. Bunlardan birisi düz bir çizgi nesnesi, diğeri de yuvarlak bir nesne. Her iki nesne üzerinde değişiklik yapmaya yarayan bir kaç ortak işlem tanımladım. Bu işlemlerden birisi nesnelerin kalınlıklarını değiştirme, diğeri renklerini değiştirme, bir diğeri nesneleri widget içerisinde hareket ettirme ve seçilen nesneyi silme işlemleridir. Renk değiştirme işlemi dışındaki bütün işlemler, oluşturulan her bir nesnede sorunsuz çalışıyor. Renk değiştirme işlemi ise sadece çizgilerde çalışıyor. Neden böyle bir sorun verdiğini anlayamadım henüz. Yardımcı olmak isteyen arkadaşlar, önerilerinizi bekliyorum.

Kodlar:

#!/usr/bin/python3
# -*- coding: utf-8 -*-

import math
import tkinter as tk
import tkinter.colorchooser as colorchooser

root = tk.Tk()

canvas = tk.Canvas(master=root)
canvas.pack(fill="both", expand=True)

stringvar = tk.StringVar()

label = tk.Label(master=root, textvariable=stringvar)
label.pack()

menu = tk.Menu(master=root, tearoff=False)
root.configure(menu=menu)

tools = tk.Menu(master=root, tearoff=False)
menu.add_cascade(label="Tools", menu=tools)

objects = tk.Menu(master=root, tearoff=False)
menu.add_cascade(label="Objects", menu=objects)

MENU, ENTRY, TOOL = None, None, None
XY, LINE_POINTS, ALL_COORD = [], [], []
LINES, OVALS = {}, {}


def select_tool(tool, func):
    global XY, TOOL
    XY = []
    TOOL = tool
    canvas.bind("<Button-1>", func)


tools.add_command(
    label="Line",
    command=lambda: select_tool(tool="line", func=draw_line)
)
tools.add_command(
    label="Oval",
    command=lambda: select_tool(tool="oval", func=draw_oval)
)


def navigate(event):
    stringvar.set(f"x={event.x}, y={event.y}")


canvas.bind("<Motion>", navigate)


def remove_all_canvas():
    global LINES, OVALS
    for i in LINES.keys():
        canvas.delete(i)
    LINES = {}
    for i in OVALS.keys():
        canvas.delete(i)
    OVALS = {}


def return_all():
    remove_all_canvas()
    for i in ALL_COORD:
        if len(i) == 2:
            x1 = i[0] - 1
            y1 = i[1] - 1
            x2 = i[0] + 1
            y2 = i[1] + 1
            oval = canvas.create_oval(x1, y1, x2, y2, width=2, fill="black")
            canvas_tag_bind(dictionary=OVALS, tool=oval)
            OVALS[oval] = i
        elif len(i) == 4:
            line = canvas.create_line(i, width=2, fill="black")
            canvas_tag_bind(dictionary=LINES, tool=line)
            LINES[line] = i


objects.add_command(
    label="Remove All",
    command=remove_all_canvas)
objects.add_command(
    label="Return All",
    command=return_all
)


def canvas_tag_bind(dictionary, tool):
    canvas.tag_bind(
        tagOrId=tool,
        sequence="<B2-Motion>",
        func=lambda _event: move(_event, dictionary, tool)
    )
    canvas.tag_bind(
        tagOrId=tool,
        sequence="<Button-3>",
        func=lambda _event_: button_3_on_line(_event_, tool)
    )


def draw_line(event):
    global XY, ALL_COORD
    draw_oval(event=event)
    if len(XY) == 4:
        x1y1, x2y2 = ALL_COORD[-2], ALL_COORD[-1]
        ALL_COORD = ALL_COORD[:-2]
        ALL_COORD.append((x1y1 + x2y2))
        line = canvas.create_line(*XY, width=2, fill="black")
        canvas_tag_bind(dictionary=LINES, tool=line)
        LINES[line] = XY
        XY = []
        canvas.delete(LINE_POINTS[-1])
        canvas.delete(LINE_POINTS[-2])


def destroy():
    global MENU, ENTRY
    if MENU is not None:
        MENU.destroy()
    if ENTRY is not None:
        ENTRY.destroy()


def draw_oval(event):
    global XY
    destroy()
    ALL_COORD.append((event.x, event.y))
    XY.append(event.x)
    XY.append(event.y)
    oval = canvas.create_oval(
        event.x - 1, event.y - 1, event.x + 1, event.y + 1,
        fill="red"
    )
    canvas_tag_bind(dictionary=OVALS, tool=oval)
    OVALS[oval] = XY
    LINE_POINTS.append(oval)


def button_3_on_line(event, tool):
    global MENU
    destroy()
    MENU = tk.Menu(master=None, tearoff=False)
    options = tk.Menu(master=MENU, tearoff=False)
    MENU.add_cascade(label="Options", menu=options)
    options.add_command(
        label="Width",
        command=lambda: create_entry(tool)
    )
    options.add_command(
        label="Color",
        command=lambda: canvas.itemconfig(
            tool, fill=colorchooser.askcolor()[-1]
        )
    )
    MENU.add_command(
        label="Remove",
        command=lambda: canvas.delete(tool)
    )
    MENU.post(event.x_root, event.y_root)


def create_entry(tool):
    global ENTRY
    destroy()
    ENTRY = tk.Entry(master=root, width=5)
    if TOOL == "line":
        ENTRY.place(x=LINES[tool][-2], y=LINES[tool][-1])
    elif TOOL == "oval":
        ENTRY.place(x=OVALS[tool][-2], y=OVALS[tool][-1])
    ENTRY.bind(
        "<KeyPress-Return>",
        lambda event: change_width(event, tool)
    )


def change_width(event, tool):
    canvas.itemconfig(tool, width=int(ENTRY.get()))
    event.widget.destroy()


def calculate_new_coordinates(event, tool, x1, y1, x2, y2):
    distance = ((y1 - y2) ** 2 + (x1 - x2) ** 2) ** 0.5
    slope_angle = math.degrees(math.atan((y2 - y1) / (x2 - x1)))
    new_x2 = event.x - (distance * math.cos(math.radians(slope_angle)))
    new_y2 = event.y - (distance * math.sin(math.radians(slope_angle)))
    canvas.coords(tool, event.x, event.y, new_x2, new_y2)


def move(event, dictionary, tool):
    destroy()
    if len(dictionary) != 0:
        if dictionary == LINES:
            x1, y1, x2, y2 = dictionary[tool][:]
            calculate_new_coordinates(event, tool, x1, y1, x2, y2)
        else:
            x1, y1 = dictionary[tool][:]
            x1 = x1 - 1
            y1 = y1 - 1
            x2 = x1 + 2
            y2 = y1 + 2
            calculate_new_coordinates(event, tool, x1, y1, x2, y2)


root.mainloop()

Sorun çözülmüştür.

Sorun, oval nesnenin renginin değişmemesiyle ilgili değilmiş, width ile ilgiliymiş. width özelliği, bir oval nesnenin çerçevesinin kalınlığı ile ilgili olduğu için, width özelliği arttırılan bir oval nesnenin rengi de gözükmeyecektir, çünkü çerçeve oval nesneyi kaplayacaktır. Yani oval nesnenin yeni boyutunu ayarlamak için, yeni boyutu, nesnenin eski koordinatlarına eklemek gerekiyor.

Kodlar:

#!/usr/bin/python3
# -*- coding: utf-8 -*-

import math
import tkinter as tk
import tkinter.colorchooser as colorchooser

root = tk.Tk()
root.title("")

canvas = tk.Canvas(master=root)
canvas.pack(fill="both", expand=True)

menu = tk.Menu(master=root, tearoff=False)
root.configure(menu=menu)


def sub_menus(*args):
    for i in args:
        sub_menu = tk.Menu(master=menu, tearoff=False)
        menu.add_cascade(label=i, menu=sub_menu)
        yield sub_menu


objects_menu, options_menu = sub_menus(
    "Objects", "Options"
)

stringvar = tk.StringVar()

label = tk.Label(master=root, textvariable=stringvar)
label.pack()

MENU, ENTRY = None, None
OVALS, LINES, WIDTH, COLOR = {}, {}, {}, {}
LINE_COORDINATES, LINE_OVALS = [], []
COUNT, DEFAULT_WIDTH = 0, 5
DEFAULT_COLOR = "#000000"


def navigate(event):
    stringvar.set(f"x={event.x}, y={event.y}")


def create_width_entry():
    global ENTRY
    toplevel = tk.Toplevel(master=root)
    toplevel.resizable(width=False, height=False)
    toplevel.title("")
    width_label = tk.Label(master=toplevel, text="width = ")
    width_label.pack(side="left")
    ENTRY = tk.Entry(master=toplevel, width=5)
    ENTRY.pack(side="left")


def new_global_width(event):
    global DEFAULT_WIDTH
    DEFAULT_WIDTH = int(ENTRY.get())
    event.widget.master.destroy()


def change_global_width():
    destroy()
    create_width_entry()
    ENTRY.bind("<KeyPress-Return>", lambda event: new_global_width(event))


def change_global_color():
    global DEFAULT_COLOR
    DEFAULT_COLOR = colorchooser.askcolor()[-1]


def destroy():
    if MENU is not None:
        MENU.destroy()
    if ENTRY is not None:
        ENTRY.destroy()


def canvas_tag_bind(obj, dictionary):
    canvas.tag_bind(
        tagOrId=obj,
        sequence="<B2-Motion>",
        func=lambda _event: move(_event,  obj, dictionary)
    )
    canvas.tag_bind(
        tagOrId=obj,
        sequence="<Button-3>",
        func=lambda _event_: button_3_on_object(_event_, obj, dictionary)
    )


def draw_oval(event):
    destroy()
    oval = canvas.create_oval(
        event.x - DEFAULT_WIDTH,
        event.y - DEFAULT_WIDTH,
        event.x + DEFAULT_WIDTH,
        event.y + DEFAULT_WIDTH,
        fill=DEFAULT_COLOR,
        width=0
    )
    canvas_tag_bind(obj=oval, dictionary=OVALS)
    OVALS[oval] = [event.x, event.y]
    WIDTH[oval] = DEFAULT_WIDTH
    COLOR[oval] = DEFAULT_COLOR
    return OVALS[oval], oval


def draw_line(event):
    global COUNT, LINE_COORDINATES, LINE_OVALS
    oval = draw_oval(event=event)
    LINE_COORDINATES.extend(oval[0])
    LINE_OVALS.append(oval[1])
    COUNT += 1
    if COUNT == 2:
        line = canvas.create_line(
            *LINE_COORDINATES,
            width=DEFAULT_WIDTH,
            fill=DEFAULT_COLOR
        )
        canvas_tag_bind(obj=line, dictionary=LINES)
        COUNT = 0
        LINES[line] = LINE_COORDINATES
        COLOR[line] = DEFAULT_COLOR
        WIDTH[line] = DEFAULT_WIDTH
        LINE_COORDINATES = []
        for i in LINE_OVALS:
            canvas.delete(i)
            OVALS.pop(i)
        LINE_OVALS = []


def change_color(obj):
    color = colorchooser.askcolor()[-1]
    COLOR[obj] = color
    canvas.itemconfig(obj, fill=color)


def remove_canvas(obj):
    global COUNT, LINE_OVALS, LINE_COORDINATES
    canvas.delete(obj)
    COUNT = 0
    LINE_OVALS = []
    LINE_COORDINATES = []


def button_3_on_object(event, obj, dictionary):
    global MENU
    destroy()
    MENU = tk.Menu(master=None, tearoff=False)
    opt = tk.Menu(master=MENU, tearoff=False)
    MENU.add_cascade(label="Options", menu=opt)
    opt.add_command(
        label="Width",
        command=lambda: create_entry(obj, dictionary)
    )
    opt.add_command(
        label="Color",
        command=lambda: change_color(obj)
    )
    MENU.add_command(
        label="Remove",
        command=lambda: remove_canvas(obj))
    MENU.post(event.x_root, event.y_root)


def create_entry(obj, dictionary):
    destroy()
    create_width_entry()
    ENTRY.bind(
        "<KeyPress-Return>",
        lambda event: change_width(event, obj, dictionary)
    )


def change_width(event, obj, dictionary):
    width = int(ENTRY.get())
    WIDTH[obj] = width
    if dictionary == LINES:
        canvas.itemconfig(obj, width=width)
    elif dictionary == OVALS:
        x, y = OVALS[obj]
        x1 = x - width
        x2 = x + width
        y1 = y - width
        y2 = y + width
        canvas.coords(obj, x1, y1, x2, y2)
    event.widget.master.destroy()


def calculate_new_coordinates(event, obj, x1, y1, x2, y2):
    distance = ((y1 - y2) ** 2 + (x1 - x2) ** 2) ** 0.5
    slope_angle = math.degrees(math.atan((y2 - y1) / (x2 - x1)))
    new_x2 = event.x - (distance * math.cos(math.radians(slope_angle)))
    new_y2 = event.y - (distance * math.sin(math.radians(slope_angle)))
    canvas.coords(obj, event.x, event.y, new_x2, new_y2)
    return [event.x, event.y, new_x2, new_y2]


def move(event, obj, dictionary):
    destroy()
    if len(dictionary) != 0:
        if dictionary == LINES:
            x1, y1, x2, y2 = dictionary[obj][:]
            coordinates = calculate_new_coordinates(event, obj, x1, y1, x2, y2)
            LINES[obj] = coordinates
        else:
            x1 = event.x - WIDTH[obj]
            y1 = event.y - WIDTH[obj]
            x2 = event.x + WIDTH[obj]
            y2 = event.y + WIDTH[obj]
            calculate_new_coordinates(event, obj, x1, y1, x2, y2)
            OVALS[obj] = [x1, y1]


def remove_all_canvas():
    global LINES, OVALS
    for i in LINES.keys():
        canvas.delete(i)
    for i in OVALS.keys():
        canvas.delete(i)


def change_variables(**kwargs):
    canvas_tag_bind(dictionary=kwargs["dictionary"], obj=kwargs["obj"])
    kwargs["dictionary"][kwargs["obj"]] = kwargs["j"]
    WIDTH[kwargs["obj"]] = WIDTH[kwargs["i"]]
    COLOR[kwargs["obj"]] = COLOR[kwargs["i"]]


def return_all():
    global OVALS, LINES, WIDTH
    save_ovals = OVALS
    OVALS = {}
    for i, j in save_ovals.items():
        x1 = j[0] - WIDTH[i]
        y1 = j[1] - WIDTH[i]
        x2 = j[0] + WIDTH[i]
        y2 = j[1] + WIDTH[i]
        oval = canvas.create_oval(x1, y1, x2, y2, width=0, fill=COLOR[i])
        change_variables(dictionary=OVALS, obj=oval, save_object=save_ovals,
                         i=i, j=j)
    save_lines = LINES
    LINES = {}
    for i, j in save_lines.items():
        line = canvas.create_line(*j, width=WIDTH[i], fill=COLOR[i])
        change_variables(dictionary=LINES, obj=line, save_object=save_lines,
                         i=i, j=j)


objects_menu.add_command(
    label="Oval",
    command=lambda: canvas.bind("<Button-1>", draw_oval)
)
objects_menu.add_command(
    label="Line",
    command=lambda: canvas.bind("<Button-1>", draw_line)
)

options_menu.add_command(
    label="Color",
    command=change_global_color
)
options_menu.add_command(
    label="Width",
    command=change_global_width
)
options_menu.add_command(
    label="Remove All",
    command=remove_all_canvas
)
options_menu.add_command(
    label="Return All",
    command=return_all
)

canvas.bind("<Motion>", navigate)

root.mainloop()