Canvas. Идентификаторы, теги и анимация

Изучив размещение геометрических примитивов на экземпляре Canvas, в этом уроке рассмотрим, как можно обращаться к уже созданным фигурам для изменения их свойств, а также создадим анимацию.

В Tkinter существует два способа "пометить" фигуры, размещенные на холсте, – это идентификаторы и теги. Первые всегда уникальны для каждого объекта. Два объекта не могут иметь одни и тот же идентификатор. Теги не уникальны. Группа объектов на холсте может иметь один и тот же тег. Это дает возможность менять свойства всей группы. Отдельно взятая фигура на Canvas может иметь как идентификатор, так и тег.

Идентификаторы

Методы, создающие фигуры на холсте, возвращают численные идентификаторы этих объектов, которые можно присвоить переменным, через которые позднее обращаться к созданным фигурам.

from tkinter import *
root = Tk()
c = Canvas(width=300, height=300, bg='white')
c.focus_set()
c.pack()
 
ball = c.create_oval(140, 140, 160, 160, fill='green')
c.bind('<Up>', lambda event: c.move(ball, 0, -2))
c.bind('<Down>', lambda event: c.move(ball, 0, 2))
c.bind('<Left>', lambda event: c.move(ball, -2, 0))
c.bind('<Right>', lambda event: c.move(ball, 2, 0))
 
root.mainloop()

В данном примере круг двигается по холсту с помощью стрелок на клавиатуре. Когда создавался круг, его идентификатор был присвоен переменной ball. Метод move() объекта Canvas принимает идентификатор и смещение по осям.

С помощью метода itemconfig() можно изменять другие свойства. Метод coords() устанавливает новые координаты фигуры, если они заданы. Если указывается только идентификатор или тег, то coords() возвращает текущие координаты.

from tkinter import *
root = Tk()
c = Canvas(width=200, height=200, bg='white')
c.pack()
 
rect = c.create_rectangle(80, 80, 120, 120, fill='lightgreen')
 
def inFocus(event):
    c.itemconfig(rect, fill='green', width=2)
    c.coords(rect, 70, 70, 130, 130)
c.bind('<FocusIn>', inFocus)
 
root.mainloop()

Здесь при получении холстом фокуса (нажать Tab) изменится цвет и размер квадрата.

Теги

В отличие от идентификаторов, которые являются уникальными для каждого объекта, один и тот же тег может присваиваться разным объектам. Дальнейшее обращение к такому тегу позволит изменить все объекты, в которых он был указан. В примере ниже эллипс и линия содержат один и тот же тег, а функция color изменяет цвет всех объектов с тегом group1. Обратите внимание, что в отличие от имени идентификатора (переменная), имя тега заключается в кавычки (строковое значение).

… 
oval = c.create_oval(30,10,130,80,tag="group1")
c.create_line(10,100,450,100,tag="group1")
 
def color(event):
     c.itemconfig('group1',fill="red",width=3)
 
c.bind('<Button-3>',color)

Метод tag_bind() позволяет привязать событие (например, щелчок кнопкой мыши) к определенной фигуре на Canvas. Таким образом, можно реализовать обращение к различным областям холста с помощью одного и того же события. Пример ниже иллюстрирует, как изменения на холсте зависят от того, где произведен клик.

from tkinter import *
 
c = Canvas(width=460, height=100, bg='grey80')
c.pack()
 
oval = c.create_oval(30, 10, 130, 80, fill="orange")
c.create_rectangle(180, 10, 280, 80, 
                    tag="rect", fill="lightgreen")
trian = c.create_polygon(330, 80, 380, 10, 430, 80,
                    fill='white',outline="black")
 
def oval_func(event):
     c.delete(oval)
     c.create_text(80, 50, text="Круг")
def rect_func(event):
     c.delete("rect")
     c.create_text(230, 50, text="Прямоугольник")
def triangle(event):
     c.delete(trian)
     c.create_text(380, 50, text="Треугольник")
 
c.tag_bind(oval, '<Button-1>', oval_func)
c.tag_bind("rect", '<Button-1>', rect_func)
c.tag_bind(trian, '<Button-1>', triangle)
 
mainloop() 

Метод tag_bind()

Метод delete() удаляет объект. Если нужно очистить холст, то вместо идентификаторов или тегов используется константа ALL.

Практическая работа. Анимация в tkinter

В данной программе создается анимация круга, который движется от левой границы холста до правой:

from tkinter import *
 
root = Tk()
c = Canvas(root, width=300, height=200, bg="white")
c.pack()
 
ball = c.create_oval(0, 100, 40, 140, fill='green')
 
def motion():
    c.move(ball, 1, 0)
    if c.coords(ball)[2] < 300:
        root.after(10, motion)
 
motion()
 
root.mainloop()

Выражение c.coords(ball) возвращает список текущих координат объекта (в данном случае это ball). Третий элемент списка соответствует его второй координате x.

Метод after() вызывает функцию, переданную вторым аргументом, через количество миллисекунд, указанных первым аргументом.

Изучите приведенную программу и самостоятельно запрограммируйте постепенное движение фигуры в ту точку холста, где пользователь кликает левой кнопкой мыши. Координаты события хранятся в его атрибутах x и y (event.x, event.y).

Курс с примерами решений практических работ: android-приложение, pdf-версия.

Комментарии

Как так получается, что если надо фигуре сдвинуться только по оси Х, то тайминг в 10мс срабатывает хорошо, во всех других случаях, это почти! мгновенное движение.
def press(event):
    x = event.x
    y = event.y
    motion(x, y)
 
def motion(x, y):
 
    if c.coords(ball)[2] < x:
	c.move(ball, 1, 0)
	root.after(10, motion, x, y)
    if c.coords(ball)[3] < y:
	c.move(ball, 0, 1)
	root.after(10, motion, x, y)
 
    if c.coords(ball)[2] > x:
	c.move(ball, -1, 0)
	root.after(10, motion, x, y)
    if c.coords(ball)[3] > y:
	c.move(ball, 0, -1)
	root.after(10, motion, x, y)
 
c.bind('<Button-1>', press)

Ответ на от Константин

Если кликать четко не по вертикали или горизонтали, то в вашей программе будут срабатывать две ветки if. Например, шар был в (10, 10), а должен оказаться в (100, 100). Сработает первая и вторая ветка. Каждая со своим рекурсивным вызовом motion(). Должна быть одна рекурсия. Программа хитрее. В зависимости от места клика и нахождения шара надо рассчитать шаг по оси x и y, который будет делать шар на каждом вызове motion().

Ответ на от plustilino

Да я понял что нужно рассчитать сразу шаг по Х и У. Но в голову пришла вот по проще идея, где сначала в рекурсии он по Х доходит до значения а потом при возврате последней рекурсии (на одну предыдущую) он уходит в рекурсию по У. Потом остальные, возвраты рекурсии, просто не проходят уже по условиям. Но вот работает не так как ожидал. Это получается он не ждет выполнение другой рекурсии а почти одновременно везде запускается, это объясняет такое поведение.

Ответ на от FireFlowey

Добрый день. Посмотрела ваш код. Могли бы пояснить, для чего строка: canvas.unbind("") Я прочитала для чего unbind() нужен, но не понимаю, для чего он в данном коде. (просто учусь и пытаюсь разобраться)

Практическая работа.
from tkinter import *
root=Tk()
root.title("Мяч догоняет мышь")
c=Canvas(root, width=300, height=200, bg="white")
c.pack()
ball=c.create_oval(0,100,20,120, fill='green')
def mouse(event):
    x=event.x
    y=event.y
    move(x,y)   
def move(x,y):
    if (c.coords(ball)[2]+c.coords(ball)[0])/2<x:
        c.move(ball, 1,0)
    if (c.coords(ball)[2]+c.coords(ball)[0])/2>x:
        c.move(ball, -1,0)
    if (c.coords(ball)[3]+c.coords(ball)[1])/2<y:
        c.move(ball, 0,1)
    if (c.coords(ball)[3]+c.coords(ball)[1])/2>y:
        c.move(ball, 0,-1)
    if (c.coords(ball)[3]+c.coords(ball)[1])/2!=y or (c.coords(ball)[2]+c.coords(ball)[0])/2!=x:
        root.after(10, move, x,y)
root.bind('<Button-1>', mouse)
root.mainloop()

Ответ на от AnnaKo

from tkinter import *
root=Tk()
root.title("Мяч догоняет мышь")
c=Canvas(root, width=300, height=200, bg="white")
c.pack()
ball=c.create_oval(0,100,20,120, fill='green')
def mouse(event):
    x=event.x
    y=event.y
    root.unbind('<Button-1>')
    move(x,y)   
def move(x,y):
    if (c.coords(ball)[2]+c.coords(ball)[0])/2<x:
        c.move(ball, 1,0)
    if (c.coords(ball)[2]+c.coords(ball)[0])/2>x:
        c.move(ball, -1,0)
    if (c.coords(ball)[3]+c.coords(ball)[1])/2<y:
        c.move(ball, 0,1)
    if (c.coords(ball)[3]+c.coords(ball)[1])/2>y:
        c.move(ball, 0,-1)
    if (c.coords(ball)[3]+c.coords(ball)[1])/2!=y or (c.coords(ball)[2]+c.coords(ball)[0])/2!=x:
        root.after(10, move, x,y)
    else:
        root.bind('<Button-1>', mouse)
root.bind('<Button-1>', mouse)
root.mainloop()

from tkinter import *
 
def motion():
    c.move(ball, x_ball_speed, y_ball_speed)    
    if x_ball_speed > 0:
        if (c.coords(ball)[0] + c.coords(ball)[2])/2 < x:
            root.after(30, motion)
    elif x_ball_speed < 0:
        if (c.coords(ball)[0] + c.coords(ball)[2])/2 > x:
            root.after(30, motion)
 
def click_move(event):
    global x, y, x_ball_speed, y_ball_speed
    x = event.x
    y = event.y
    x_ball_speed = (x - (c.coords(ball)[0] + c.coords(ball)[2])/2) / 100
    y_ball_speed = (y - (c.coords(ball)[1] + c.coords(ball)[3])/2) / 100
 
    motion()
 
    return x, y, x_ball_speed, y_ball_speed
 
 
root = Tk()
c = Canvas(root, width=300, height=200, bg="white")
c.pack()
 
ball = c.create_oval(0, 100, 40, 140, fill='green', outline='green')
 
x = 300
y = 200
x_ball_speed = 1
y_ball_speed = 0
 
c.bind('<Button-1>', click_move)
motion()
 
root.mainloop()

from tkinter import *
import math as m
 
def motion(event):
        tempX = int(event.x)             ; tempY = int(event.y)
        ballX = can.coords(ball)[2] - 20 ; ballY = can.coords(ball)[3] - 20
        distance = m.sqrt((tempX-ballX)**2 + (tempY-ballY)**2)
 
        if distance > 2:
                can.move(ball, (tempX-ballX)/distance, (tempY-ballY)/distance)
                root.after(10, motion, event)
        else:
                return 0
 
root = Tk()
root.title("Test")
can = Canvas(width=300, height=200, bg="lightgreen")
can.pack()
 
ball = can.create_oval(0, 100, 40, 140, fill="green")
root.bind('<Button-1>', motion)
 
root.mainloop()

Ответ на от Kirill

А как сделать чтобы объект вращался по часовой стрелке? И еще круг разбить на сектора:?

from tkinter import *
import math
 
root = Tk()
c = Canvas(root, width=300, height=200, bg="darkgrey")
c.pack()
 
 
class Ball():
    def __init__(self, x1, y1, x2, y2, color, text):
        self.ball = c.create_oval(x1, y1, x2, y2, fill=color)
        self.text = c.create_text(self.get_x(), self.get_y(
        ), text=text, font=('Arial Bold', 9), fill='white')
        self.__state = 0
 
    def get_x(self):
        curCoords = self.__get_obj_coords()
        return curCoords[0]+(curCoords[2]-curCoords[0])/2
 
    def get_y(self):
        curCoords = self.__get_obj_coords()
        return curCoords[1]+(curCoords[3]-curCoords[1])/2
 
    def __get_obj_coords(self):
        return c.coords(self.ball)
 
    def set_target(self, targetX, targetY):
        self.__tX = targetX
        self.__tY = targetY
        self.__cX = self.get_x()
        self.__cY = self.get_y()
        self.__distanceToTarget = math.sqrt(
            ((self.__tX-self.__cX)**2) + ((self.__tY-self.__cY)**2))
        self.__state = 1
 
    def check_coords(self, targetX, targetY, centerX, centerY):
        distance = math.sqrt(((targetX-self.get_x())**2) +
                             ((targetY-self.get_y())**2))
        return distance < 1
 
    def motion(self):
        if self.__state == 0:
            pass
        if self.__state == 1:
            if not self.check_coords(self.__tX, self.__tY, self.__cX, self.__cY):
                c.move(self.ball, (self.__tX-self.__cX)/self.__distanceToTarget,
                       (self.__tY-self.__cY)/self.__distanceToTarget)
                c.move(self.text, (self.__tX-self.__cX)/self.__distanceToTarget,
                       (self.__tY-self.__cY)/self.__distanceToTarget)
            else:
                self.__state = 0
 
 
ball1 = Ball(0, 100, 40, 140, 'blue', 'motion')
ball2 = Ball(10, 10, 50, 50, 'red', 'click')
 
 
def controller(obj):
    obj.motion()
    root.after(10, lambda: controller(obj))
 
 
def handler_event(event, obj):
    obj.set_target(event.x, event.y)
 
 
c.bind('<Motion>', lambda event: handler_event(event, ball1))
c.bind('<Button-1>', lambda event: handler_event(event, ball2))
 
controller(ball1)
controller(ball2)
root.mainloop()

from tkinter import *
import math
 
 
class Ball():
    def __init__(self, x1, y1, x2, y2, color, text, text_color):
        self.ball = c.create_oval(x1, y1, x2, y2, fill=color,width = 3)
        self.text = c.create_text(self.get_x(), self.get_y(
        ), text=text, font=('Arial', 10,"bold"), fill=text_color)
        self.__state = 0
 
    def get_x(self):
        obj_coords = self.__get_obj_coords()
        return obj_coords[0]+(obj_coords[2]-obj_coords[0])/2
 
    def get_y(self):
        curCoords = self.__get_obj_coords()
        return curCoords[1]+(curCoords[3]-curCoords[1])/2
 
    def __get_obj_coords(self):
        return c.coords(self.ball)
 
    def set_target(self, targetX, targetY):
        self.__tX = targetX
        self.__tY = targetY
        self.__cX = self.get_x()
        self.__cY = self.get_y()
        self.__distanceToTarget = math.sqrt(
            ((self.__tX-self.__cX)**2) + ((self.__tY-self.__cY)**2))
        self.__state = 1
 
    def check_coords(self, targetX, targetY, centerX, centerY):
        distance = math.sqrt(((targetX-self.get_x())**2) +
                             ((targetY-self.get_y())**2))
        return distance < 1
 
    def motion(self):
        if self.__state == 0:
            pass
        if self.__state == 1:
            if not self.check_coords(self.__tX, self.__tY, self.__cX, self.__cY):
                c.move(self.ball, (self.__tX-self.__cX)/self.__distanceToTarget,
                       (self.__tY-self.__cY)/self.__distanceToTarget)
                c.move(self.text, (self.__tX-self.__cX)/self.__distanceToTarget,
                       (self.__tY-self.__cY)/self.__distanceToTarget)
            else:
                self.__state = 0
 
 
 
 
def handler_event(event, obj):
    obj.set_target(event.x, event.y)
 
def main():
    global root, c
    root = Tk()
    c = Canvas(root, width=500, height=500, bg="steelblue4")
    c.pack()
    c.create_text(500/2,500/2, text='Move mouse or click', font=('Arial', 15,"bold"), fill='white')
    ball1 = Ball(10, 10, 60, 60, 'red', 'click','white')
    ball2 = Ball(10, 10, 60, 60, 'greenyellow', 'motion','black')
 
    c.bind('<Button-1>', lambda event: handler_event(event, ball1))
    c.bind('<Motion>', lambda event: handler_event(event, ball2))
 
    controller(ball1)
    controller(ball2)
    root.mainloop()
 
def controller(obj):
    obj.motion()
    root.after(10, lambda: controller(obj))
 
if __name__ == "__main__":
    main()

from tkinter import *
 
def SNAKE():
 
    x = 1100
    y = 450
    x1 = 1120
    y1 = 430
    n = 6
    while n != 0:
        CANVAS.create_oval(x,y,x1,y1,tag = "BODY",fill = "green")
        y -= 20
        y1 -= 20
        n -=1
    global SNAKE_CREATE
    SNAKE_CREATE = True
    print(SNAKE_CREATE)
 
 
 
def MOVIE(SNAKE_CREATE):
 
    print("Это работает")
 
 
 
    def UP(event):
        print("Вв")
 
    def LEFT(event):
        print("Вл")
 
    def RIGHT(event):
        print("Вп")
 
    def DOWN(event):
        print("Вн")
 
    CANVAS.tag_bind("BODY",'<Up>',UP)
    CANVAS.tag_bind("BODY",'<Left>',LEFT)
    CANVAS.tag_bind("BODY",'<Right>',RIGHT)
    CANVAS.tag_bind("BODY",'<Down>',DOWN)
 
 
 
 
 
 
 
GAME_START = True
WINDOW_GAME = Tk()
WINDOW_GAME.attributes("-fullscreen",True)
BUTTON3 = Button(WINDOW_GAME, text = "Выйти", command = lambda: WINDOW_GAME.destroy())
BUTTON3.pack()
CANVAS = Canvas(WINDOW_GAME, width = 1440, height = 900)
CANVAS.pack()
SNAKE()
MOVIE(SNAKE_CREATE)
WINDOW_GAME.mainloop()

from tkinter import *
 
root = Tk()
c = Canvas(root, width=300, height=200, bg="white")
c.pack()
 
ball = c.create_oval(0, 100, 40, 140, fill='green')
 
def moveIt(x, y):
	moveX = x-c.coords(ball)[0]-20   
	moveY = y-c.coords(ball)[1]-20   
	if moveX!=0 and moveY!=0:
		if abs(moveX)==abs(moveY):
			dx = int(moveX/abs(moveX))
			dy = int(moveY/abs(moveY))
		elif abs(moveX)<abs(moveY):
			dx = int(moveX/abs(moveX))
			dy = int(moveY/abs(moveX))
		elif abs(moveX)>abs(moveY):
			dy = int(moveY/abs(moveY))   
			dx = int(moveX/abs(moveY))   
	elif moveX==0 and moveY!=0:
		dx = 0
		dy = dy = int(moveY/abs(moveY))
	elif moveY==0 and moveX!=0:
		dy = 0
		dx = dx = int(moveX/abs(moveX))
	else:
		dx=0
		dy=0
 
	c.move(ball, dx, dy)
	if abs(x-c.coords(ball)[0]-20)>abs(dx) or abs(y-c.coords(ball)[1]-20)>abs(dy):
		root.after(30, moveIt,x,y)
 
def motion(event):
	moveIt(event.x, event.y)
 
c.bind('<Button-1>', motion)
 
root.mainloop()

Прошу оценку и критику!! Потратил 11 часов! Очень важно каждое мнение! Спасибо)
from tkinter import *
 
root = Tk()
 
canv = Canvas(root, width=300, height=300)
canv.pack()
oval = canv.create_oval(150, 150, 180, 180, fill='green')
 
 
def motion_1(step_x, step_y, new_x, new_y):
    canv.move(oval, step_x, step_y*-1)
    if new_x > canv.coords(oval)[0] and new_y < canv.coords(oval)[1]:
        root.after(10, motion_1, step_x, step_y, new_x, new_y)
 
 
def motion_2(step_x, step_y, new_x, new_y):
    canv.move(oval, step_x*-1, step_y*-1)
    if new_x < canv.coords(oval)[0] and new_y < canv.coords(oval)[1]:
        root.after(10, motion_2, step_x, step_y, new_x, new_y)
 
 
def motion_3(step_x, step_y, new_x, new_y):
    canv.move(oval, step_x*-1, step_y)
    if new_x < canv.coords(oval)[0] and new_y > canv.coords(oval)[1]:
        root.after(10, motion_3, step_x, step_y, new_x, new_y)
 
 
def motion_4(step_x, step_y, new_x, new_y):
    canv.move(oval, step_x, step_y)
    if new_x > canv.coords(oval)[0] and new_y > canv.coords(oval)[1]:
        root.after(10, motion_4, step_x, step_y, new_x, new_y)
 
 
def movement_point(x, y, new_x, new_y):
    step_x = abs(x - new_x) / 100
    step_y = abs(y - new_y) / 100
    if new_x > x and new_y < y:
        motion_1(step_x, step_y, new_x, new_y)
    elif new_x < x and new_y < y:
        motion_2(step_x, step_y, new_x, new_y)
    elif new_x < x and new_y > y:
        motion_3(step_x, step_y, new_x, new_y)
    elif new_x > x and new_y > y:
        motion_4(step_x, step_y, new_x, new_y)
 
 
def get_new_coords(event):
    x = canv.coords(oval)[0]
    y = canv.coords(oval)[1]
    new_x = event.x - 15
    new_y = event.y - 15
    movement_point(x, y, new_x, new_y)
 
 
def start_event():
    canv.bind('<Button-1>', get_new_coords)
 
 
start_event()
 
root.mainloop()

from tkinter import *
 
root = Tk()
holst = Canvas(root, width=800, height=600, bg="white")
holst.pack()
 
ball = holst.create_oval(0, 50, 50, 100, fill='green')
 
recursFlag=False
 
def flagSlider(boo):
    global recursFlag
    recursFlag=boo
 
def getCoords(e):
    flagSlider(True)
    holst.after(12, motion, e.x, e.y)
    root.after(11, flagSlider, False)
 
def motion(X,Y):
    global recursFlag
    flagX=flagY=0
    if X > int(holst.coords(ball)[0]):
        holst.move(ball,1,0)
    elif X < int(holst.coords(ball)[0]):
        holst.move(ball,-1,0)
    else:
        flagX=1
 
    if Y > int(holst.coords(ball)[1]):
        holst.move(ball,0,1)
    elif Y < int(holst.coords(ball)[1]):
        holst.move(ball,0,-1)
    else:
        flagY=1
 
    if flagX==1 and flagY==1:
        return
    if recursFlag:
        return
    holst.after(10, motion, X, Y)
 
holst.bind('<Button-1>', getCoords)
 
root.mainloop()

неделю наверное пытаюсь понять алгоритм этой программы... Не понимаю, почему втором клике выполнение первого не прекращается полностью, если дистанция задана как глобальная и обнуляется во время второго клика?! Вектора движений первого и второго клика как будто складываются. Или это проблема в рекурсии? Объясните тупому человеку пожалуйста!
from tkinter import *
from math import sqrt
 
def click(event):
    global distance
    distance=0
    x=event.x
    y=event.y
    vector_x = x - c.coords(ball)[2] + 20
    vector_y = y - c.coords(ball)[1] - 20
    distance=sqrt(vector_x ** 2 + vector_y ** 2)
    dx = vector_x / distance
    if vector_y==0:
        dy=0
    else:
        dy = vector_y / distance
    motion(dx, dy)
 
def motion(dx,dy):
    global distance
    if distance<=1:
        return
    else:
        c.move(ball, dx, dy)
        distance-=1
        root.after(10, motion,dx,dy)
    return
 
root = Tk()
c = Canvas(root, width=500, height=500)
c.pack()
ball = c.create_oval(0, 100, 40, 140, fill='green')
c.bind('<Button-1>',click)
root.mainloop()
<python>

Ответ на от Вутмук

Допоможіть, БУДЬ ЛАСКА, в чому помилка
from tkinter import*
tk = Tk()
 
canvas = Canvas(tk, width=500, height=500)
canvas.pack()
ship_image=PhotoImage(file="ship.gif")
s=canvas.create_image(0,0,anchor=NW,image=ship_image)
    for y in range(200):
    canvas.move(s,-3,0)
    tk.update()
    time.sleep(0.02)

Помогите найти ошибку
from tkinter import*
tk = Tk()
 
canvas = Canvas(tk, width=500, height=500)
canvas.pack()
ship_image=PhotoImage(file="ship.gif")
s=canvas.create_image(0,0,anchor=NW,image=ship_image)
    for y in range(200):
    canvas.move(s,-3,0)
    tk.update()
    time.sleep(0.02)

Ответ на от Раиса

from tkinter import*
tk = Tk()
 
canvas = Canvas(tk, width=500, height=500)
canvas.pack()
ship_image=PhotoImage(file="D:\Pythonитон прожкибучалкиПрогиодуль tkinter\Screenshot_1.png")
s=canvas.create_image(0,0,anchor=NW,image=ship_image)
for y in range(200):
    canvas.move(s,-3,0)
    tk.update()
    time.sleep(0.02)
root.mainloop()

from tkinter import * 
root = Tk()
c = Canvas(root, width=400, height=600, bg="white")
c.pack()
ball = c.create_oval(10, 60, 50, 100, fill='green')
 
def pressing(event):
    global x, y
    x = event.x
    y = event.y
    vector_x = x - (c.coords(ball)[0] + c.coords(ball)[2]) / 2
    vector_y = y - (c.coords(ball)[1] + c.coords(ball)[3]) / 2
    motion(vector_x , vector_y)
 
def motion(v_x, v_y):
    k = v_y/v_x
    if v_x == 0:
        k += 0.01
    if v_x < 0:
        if k <= 1 and k >= -1:
            c.move(ball, -1,  -k)
            if (c.coords(ball)[0] + c.coords(ball)[2]) / 2 >= x:
                root.after(1, lambda : motion(v_x,v_y))
        else:
            if k >= 0:
                c.move(ball, -1/k, -1)
                if (c.coords(ball)[1] + c.coords(ball)[3]) / 2 >= y:
                    root.after(1, lambda : motion(v_x,v_y))
            else:
                c.move(ball, 1/k, 1)
                if (c.coords(ball)[1] + c.coords(ball)[3]) / 2 <= y:
                    root.after(1, lambda : motion(v_x,v_y))              
    else:
        if k >= -1 and k <= 1:
            c.move(ball, 1, k)
            if (c.coords(ball)[0] + c.coords(ball)[2]) / 2 <= x:
                root.after(1, lambda : motion(v_x,v_y))
        else:
            if k >= 0:
                c.move(ball, 1/k, 1)
                if (c.coords(ball)[1] + c.coords(ball)[3]) / 2 <= y:
                    root.after(1, lambda : motion(v_x,v_y))
            else:
                c.move(ball, -1/k, -1)
                if (c.coords(ball)[1] + c.coords(ball)[3]) / 2 >= y:
                    root.after(1, lambda : motion(v_x,v_y))
 
c.bind('<Button-1>', pressing)
root.mainloop()
<python>

Залип надолго, не понимал почему это у меня точка сразу перемещается..
root.after(30, self.moveToPoint(event)) 
from tkinter import Canvas, Tk
from math import sqrt
 
step = 1
dirs = {'<Up>': (0, -1*step), '<Right>': (1*step, 0), '<Down>': (0, 1*step), '<Left>': (-1*step, 0)}
 
 
class Obj:
    def __init__(self, canvas, spawnLoc, radius, tag, color="green"):
        self.x = spawnLoc[0]
        self.y = spawnLoc[1]
 
        self.canvas = canvas
        self.tag = tag
        self.radius = radius
        self.color = color
 
        self.canvas.create_rectangle(self.x - self.radius, self.y - self.radius,
                                     self.x + self.radius, self.y + self.radius,
                                     tag=self.tag, fill=self.color, width=0)
 
    def shift(self, dir):
        x, y = dirs[dir]
        for i in range(self.radius*2):
            self.canvas.move(self.tag, x, y)
 
        self.x = self.canvas.coords(self.tag)[0]+self.radius
        self.y = self.canvas.coords(self.tag)[1]+self.radius
 
    def getParams(self):
        return [self.x, self.y], self.radius, self.tag
 
    def setRadius(self, ch, amount=1):
        if ch == '+' and self.radius < 50:
            self.radius += amount
        elif ch == '-' and self.radius > 1:
            self.radius -= amount
 
        self.canvas.delete(self.tag)
        self.canvas.create_rectangle(self.x - self.radius, self.y - self.radius,
                                     self.x + self.radius, self.y + self.radius,
                                     tag=self.tag, fill=self.color, width=0)
#______________________________________________________________________________________________________________________
 
 
class Pane:
    def __init__(self, master, width=200, height=200):
        self.c = Canvas(master, width=width, height=height, bg='white')
        self.c.focus_set()
        self.c.pack()
 
        self.pTag = "point"
        self.o = Obj(self.c, [width // 2, height // 2], 8, "cube")
 
        for k, c in dirs.items():
            self.initKeys(k, self.o)
 
        self.c.bind('<Button-1>', lambda event: self.initPoint(event))
        self.c.bind('3', lambda event, obj=self.o: self.viewParams(event, obj))
        #self.c.bind('<B1-Motion>', lambda event: self.initPoint(event))
        self.c.bind('<Return>', self.delPoint)
 
        self.c.bind('1', lambda event, ch='-': self.o.setRadius(ch))
        self.c.bind('2', lambda event, ch='+': self.o.setRadius(ch))
 
    def viewParams(self, event, obj):
        s = obj.getParams()
        print(s[0][0], s[0][1])
 
    def moveToPoint(self, event):
        self.objx = self.o.getParams()[0][0]
        self.objy = self.o.getParams()[0][1]
        self.objr = self.o.getParams()[1]
        self.objt = self.o.getParams()[2]
 
        #print("obj x,y,r,t", self.objx, self.objy, self.objr, self.objt)
        #print("poi x,y", self.pointx, self.pointy)
 
        if not self.objx-self.objr < self.pointx < self.objx+self.objr \
                or not self.objy+self.objr < self.pointy < self.objy+self.objr: #fk my mosk
 
            distance = sqrt((self.pointx - self.objx) ** 2 + (self.pointy - self.objy) ** 2)
 
            if distance > self.objr*1.99:
                self.c.unbind('<Button-1>')
 
                disx = round((self.pointx - self.objx)/distance)
                disy = round((self.pointy - self.o.getParams()[0][1])/distance)
 
                if disx < 0:
                    self.o.shift('<Left>')
                elif disx > 0:
                    self.o.shift('<Right>')
                elif disy < 0:
                    self.o.shift('<Up>')
                elif disy > 0:
                    self.o.shift('<Down>')
 
                #print("nprav", disx, disy)
                root.after(30, self.moveToPoint, event)
                #root.after(30, self.moveToPoint(event)) Вся пакость в мире из-за этой строки!
 
            else:
                self.c.bind('<Button-1>', lambda event: self.initPoint(event))
                self.delPoint(event)
                return 0
 
    def initPoint(self, event):
        self.pointx = event.x
        self.pointy = event.y
 
        self.moveToPoint(event)
 
        self.createPoint(self.pointx, self.pointy, 1)
 
    def initKeys(self, key, obj):
        if key[0] != '<':
            key = f"<{key}>"
        self.c.bind(key, lambda event: obj.shift(key))
 
    def createPoint(self, x, y, radius, tag="point", color="lightgrey"):
        self.c.create_oval(x-radius, y-radius,
                           x+radius, y+radius,
                           fill=color, tag=tag, width=0)
        #print("creP", tag)
 
    def delPoint(self, event, tag="point"):
        self.c.delete(tag)
        #print("delP", tag)
 
global root
root = Tk()
root.title('Electronic-Ball')
root.resizable(0, 0)
 
Pane(root, 400, 400)
 
root.mainloop()

from tkinter import *
 
root = Tk()
c = Canvas(root, width=300, height=200, bg="white")
c.pack()
 
ball = c.create_oval(0, 100, 40, 140, fill='green')
 
def press(event):
    x = event.x
    y = event.y
    motion(x, y)
 
def motion(x, y):
 
    if c.coords(ball)[2] < x and c.coords(ball)[3] < y:
        c.move(ball, 1, 1)
        root.after(10, motion, x, y)
    if c.coords(ball)[2] > x and c.coords(ball)[3] < y:
        c.move(ball, -1, 1)
        root.after(10, motion, x, y)
 
    if c.coords(ball)[2] > x and c.coords(ball)[3] > y:
        c.move(ball, -1, -1)
        root.after(10, motion, x, y)
    if c.coords(ball)[2] < x and c.coords(ball)[3] > y:
        c.move(ball, 1, -1)
        root.after(10, motion, x, y)
    if c.coords(ball)[2] < x and c.coords(ball)[3] == y:
        c.move(ball, 1, 0)
        root.after(10, motion, x, y)
    if c.coords(ball)[2] > x and c.coords(ball)[3] == y:
        c.move(ball, -1, 0)
        root.after(10, motion, x, y)
 
    if c.coords(ball)[2] == x and c.coords(ball)[3] > y:
        c.move(ball, 0, -1)
        root.after(10, motion, x, y)
    if c.coords(ball)[2] == x and c.coords(ball)[3] < y:
        c.move(ball, 0, 1)
        root.after(10, motion, x, y)
 
c.bind('<Button-1>', press)
 
root.mainloop()

Было немного сложно, хотелось плавной анимации и чтоб круг перемещался по кратчайшему пути правильно. Это выполнить удалось, хотя пришлось немного поломать голову. Так же сделал в процессе вывод служебной информации чтоб можно было ориентироваться (кол-во кадров и точность смещения круга). Оставил в итоговом коде.
#подключаем необходимые модули
import tkinter
import math
#константа скорости перемещения шара, пиксель/сек
speed = 100
#определяем функции
#
#функция движения круга
def move(event):
    #дистанция по оси х(отнимаем 10 пикселей чтоб было по центру фигуры)
    distance_x = event.x-screen.coords(ball)[0]-10
    #дистанция по оси y(отнимаем 10 пикселей чтоб было по центру фигуры)
    distance_y = event.y-screen.coords(ball)[1]-10
    #определяем расстояние до точки назначения
    distance = math.hypot(distance_x, distance_y)
    #определяем время достижения этой точки, сек
    time = distance/speed
    #кол-во кадров (25 кадр/сек) округляем до целого
    count_frame = round(time*25)
    try:
        #смещение по оси х за кадр (округляем до 3 знаков)
        move_x = round(distance_x/count_frame, 3)
        #смещение по оси y за кадр (округляем до 3 знаков)
        move_y = round(distance_y/count_frame, 3)
    except ZeroDivisionError:
        return
    #вызов функции анимации
    animation(move_x, move_y, count_frame, event.x, event.y, corr_x=0, corr_y=0)
#функция анимации
def animation(move_x, move_y, count_frame, eventx, eventy, corr_x, corr_y):
    #смещение по х с корректировкой
    x = move_x+corr_x
    #смещение по у с корректировкой
    y = move_y+corr_y
    #поскольку смещать можно только на целое число пикселей,
    #а смещение обычно очень маленькое, часто меньше 1
    #то необходимо переносить смещение на след. кадр, чтоб не терять его.
    #берем только целую часть смещения (x, y)
    screen.move(ball, math.trunc(x), math.trunc(y))
    #переносим дробную часть в корректировку
    corr_x = math.modf(x)[0]
    corr_y = math.modf(y)[0]
    #счетчик кадров
    count_frame = count_frame-1
    #обновление служебной информации на метке
    label["text"] = "текущие координаты:{}:{}\nосталось кадров:{}\
    \nкоординаты клика:{}:{}".format(screen.coords(ball)[0]+10,
    screen.coords(ball)[1]+10, count_frame, eventx, eventy)
    #выполняем пока есть кадры анимации
    if count_frame > 0:
        root.after(40, animation,
                   move_x, move_y, count_frame, eventx, eventy, corr_x, corr_y)
#создаем главное окно
root = tkinter.Tk()
#создаем холст 300*300
screen = tkinter.Canvas(root, width=300, height=300, bg="white")
screen.pack()
#метка для вывода служебной информации
label = tkinter.Label(root, width=30, height=3, bg="yellow")
label.pack()
#создаем круг по центру холста
ball = screen.create_oval(140, 140, 160, 160, fill="green")
#события
#
#нажатие лкм на холсте
screen.bind("<Button-1>", move)
#главный цикл
root.mainloop()