试试这个:
import tkinter as tk
# Patially taken from: https://stackoverflow.com/a/17985217/11106801
def create_circle(self, x:int, y:int, r:int, **kwargs) -> int:
return self.create_oval(x-r, y-r, x+r, y+r, **kwargs)
def resize_circle(self, id:int, x:int, y:int, r:int) -> None:
self.coords(id, x-r, y-r, x+r, y+r)
tk.Canvas.create_circle = create_circle
tk.Canvas.resize_circle = resize_circle
# Defining constants:
WIDTH:int = 400
HEIGHT:int = 400
SQUARES_WIDTH:int = 40
SQUARES_HEIGHT:int = 40
# Each square will be it's own class to make it easier to work with
class Square:
# This can cause problems for people that don't know `__slots__`
# __slots__ = ("canvas", "id", "x", "y", "filled")
def __init__(self, canvas:tk.Canvas, x:int, y:int):
self.canvas:tk.Canvas = canvas
self.x:int = x
self.y:int = y
self.id:int = None
self.filled:bool = False
def fill(self, for_loop_counter:int=0) -> None:
"""
This implements a tkinter friendly for loop with a delay of
10 milliseconds. It creates a grows a circle to `radius = 20`
"""
# If the square is already filled jsut return
if self.filled:
return None
x:int = self.x + SQUARES_WIDTH // 2
y:int = self.y + SQUARES_WIDTH // 2
# If this is the first time, create the circle
if for_loop_counter == 0:
self.id:int = self.canvas.create_circle(x, y, 0, outline="", fill="black")
# Grow the cicle
else:
self.canvas.resize_circle(self.id, x, y, for_loop_counter)
# If we reach the highest radius:
if for_loop_counter == 20:
self.fill_square()
# Otherwise call `self.fill` in 10 milliseconds with
# `for_loop_counter+1` as a parameter
else:
self.canvas.after(10, self.fill, for_loop_counter+1)
def fill_square(self) -> None:
"""
Removed the circle and fills in the square
"""
self.canvas.delete(self.id)
x2:int = self.x + SQUARES_WIDTH
y2:int = self.y + SQUARES_HEIGHT
self.id = self.canvas.create_rectangle(self.x, self.y, x2, y2, fill="black", outline="")
self.filled:bool = True
class App:
# This can cause problems for people that don't know `__slots__`
# __slots__ = ("root", "canvas", "squares")
def __init__(self):
self.root:tk.Tk = tk.Tk()
self.canvas:tk.Canvas = tk.Canvas(self.root, width=WIDTH, height=HEIGHT)
self.canvas.pack()
# Create the squares:
self.squares:list[Square] = []
for x in range(0, WIDTH, SQUARES_WIDTH):
for y in range(0, HEIGHT, SQUARES_HEIGHT):
square:Square = Square(self.canvas, x, y)
self.squares.append(square)
self.canvas.bind("<Button-1>", self.on_mouse_clicked)
self.canvas.bind("<B1-Motion>", self.on_mouse_clicked)
def on_mouse_clicked(self, event:tk.Event) -> None:
# Search for the square that was pressed
mouse_x:int = event.x
mouse_y:int = event.y
for square in self.squares:
if 0 < mouse_x - square.x < SQUARES_WIDTH:
if 0 < mouse_y - square.y < SQUARES_HEIGHT:
# Tell that square that it should fill itself
square.fill()
return None
def mainloop(self) -> None:
self.root.mainloop()
if __name__ == "__main__":
app = App()
app.mainloop()
这实现了一个 tkinter 友好的 for 循环,每 10 毫秒调度一次对 <Square>.fill 的调用,直到半径为 20。然后它会填满整个正方形。
要测试代码,只需按窗口上的任意位置。您也可以拖动鼠标。
也用于清除方块:
import tkinter as tk
# Patially taken from: https://stackoverflow.com/a/17985217/11106801
def create_circle(self, x:int, y:int, r:int, **kwargs) -> int:
return self.create_oval(x-r, y-r, x+r, y+r, **kwargs)
def resize_circle(self, id:int, x:int, y:int, r:int) -> None:
self.coords(id, x-r, y-r, x+r, y+r)
tk.Canvas.create_circle = create_circle
tk.Canvas.resize_circle = resize_circle
# Defining constants:
WIDTH:int = 400
HEIGHT:int = 400
SQUARES_WIDTH:int = 40
SQUARES_HEIGHT:int = 40
# Each square will be it's own class to make it easier to work with
class Square:
# This can cause problems for people that don't know `__slots__`
# __slots__ = ("canvas", "id", "x", "y", "filled")
def __init__(self, canvas:tk.Canvas, x:int, y:int):
self.canvas:tk.Canvas = canvas
self.x:int = x
self.y:int = y
self.id:int = None
self.filled:bool = False
def fill(self, for_loop_counter:int=0) -> None:
"""
This implements a tkinter friendly for loop with a delay of
10 milliseconds. It creates a grows a circle to `radius = 20`
"""
x:int = self.x + SQUARES_WIDTH // 2
y:int = self.y + SQUARES_WIDTH // 2
# If this is the first time, create the circle
if for_loop_counter == 0:
# If the square is already filled just return
if self.filled:
return None
self.filled:bool = True
self.id:int = self.canvas.create_circle(x, y, 0, outline="", fill="black")
# User wants to clear the square
elif self.id is None:
return None
# Grow the cicle
else:
self.canvas.resize_circle(self.id, x, y, for_loop_counter)
# If we reach the highest radius:
if for_loop_counter == 20:
self.fill_square()
# Otherwise call `self.fill` in 10 milliseconds with
# `for_loop_counter+1` as a parameter
else:
self.canvas.after(10, self.fill, for_loop_counter+1)
def fill_square(self) -> None:
"""
Removed the circle and fills in the square
"""
x2:int = self.x + SQUARES_WIDTH
y2:int = self.y + SQUARES_HEIGHT
self.canvas.delete(self.id)
self.id = self.canvas.create_rectangle(self.x, self.y, x2, y2, fill="black", outline="")
def clear(self) -> None:
"""
Clears the square
"""
self.filled:bool = False
self.canvas.delete(self.id)
self.id:int = None
class App:
# This can cause problems for people that don't know `__slots__`
__slots__ = ("root", "canvas", "squares")
def __init__(self):
self.root:tk.Tk = tk.Tk()
self.canvas:tk.Canvas = tk.Canvas(self.root, width=WIDTH, height=HEIGHT)
self.canvas.pack()
# Create the squares:
self.squares:list[Square] = []
for x in range(0, WIDTH, SQUARES_WIDTH):
for y in range(0, HEIGHT, SQUARES_HEIGHT):
square:Square = Square(self.canvas, x, y)
self.squares.append(square)
self.canvas.bind("<Button-1>", self.on_mouse_clicked)
self.canvas.bind("<B1-Motion>", self.on_mouse_clicked)
self.canvas.bind("<Button-3>", self.on_mouse_clicked)
self.canvas.bind("<B3-Motion>", self.on_mouse_clicked)
def on_mouse_clicked(self, event:tk.Event) -> None:
# Search for the square that was pressed
mouse_x:int = event.x
mouse_y:int = event.y
for square in self.squares:
if 0 < mouse_x - square.x < SQUARES_WIDTH:
if 0 < mouse_y - square.y < SQUARES_HEIGHT:
# If the right mouse button is pressed
if (event.state & 1024 != 0) or (event.num == 3):
# Tell that square that it should clear itself
square.clear()
else:
# Tell that square that it should fill itself
square.fill()
return None
def mainloop(self) -> None:
self.root.mainloop()
if __name__ == "__main__":
app = App()
app.mainloop()