питон черепаха странный курсор прыгать
Я пытаюсь использовать turtle draw с мышью, я получил ниже демо-код работает, но курсор прыгать иногда во время движения мыши:
#!/usr/bin/env python
import turtle
import sys
width = 600
height = 300
def gothere(event):
turtle.penup()
x = event.x
y = event.y
print "gothere (%d,%d)"%(x,y)
turtle.goto(x,y)
turtle.pendown()
def movearound(event):
x = event.x
y = event.y
print "movearound (%d,%d)"%(x,y)
turtle.goto(x,y)
def release(event):
print "release"
turtle.penup()
def circle(x,y,r):
turtle.pendown()
turtle.goto(x,y)
turtle.circle(r)
turtle.penup()
return
def reset(event):
print "reset"
turtle.clear()
#------------------------------------------------#
sys.setrecursionlimit(90000)
turtle.screensize(canvwidth=width, canvheight=height, bg=None)
turtle.reset()
turtle.speed(0)
turtle.setup(width, height)
canvas = turtle.getcanvas()
canvas.bind("<Button-1>", gothere)
canvas.bind("<B1-Motion>", movearound)
canvas.bind("<ButtonRelease-1>", release)
canvas.bind("<Escape>",reset)
screen = turtle.Screen()
screen.setworldcoordinates(0,height,width,0)
screen.listen()
turtle.mainloop()
#------------------------------------------------#
см. ниже gif для фактического поведения:
Не уверен, что какой-либо вызов API ошибочен!
3 ответов
Я вижу несколько проблем с вашим кодом:
вы смешиваете объектно-ориентированный интерфейс с turtle с функциональный интерфейс к этому модулю. Я рекомендую один или другие, но не оба сразу. Смотрите мой
import
изменить на force OOP-only.вы используете низкоуровневую мышь tkinter и ключевые события вместо собственные события черепахи. Я рекомендую вам попробовать работать на уровне черепахи (хотя это вводит Глюк по сравнению с вашим реализации см. под.)
вы вводите непреднамеренную рекурсию, не выключая события внутри обработчиков событий. Отключение событий внутри этих обработчиков это займет значительное время, чтобы очистить вашу графику.
вот моя переделка вашего кода по вышеуказанным строкам. Один глюк заключается в том, что в отличие от вашего оригинала, "переместить черепаху здесь" и "начать перетаскивание" займет два клика, один щелчок экрана, чтобы переместить черепаху в текущее местоположение и один черепаха нажмите, чтобы начать перетаскивание. Это связано с различиями в интерфейсе, который turtle предоставляет событиям tkinter. (Python 3 немного лучше в этом отношении, но не для этой ситуации.)
чтобы облегчить это, я использовал больший курсор черепахи. Я также добавил заголовок логика:
from turtle import Turtle, Screen, mainloop
WIDTH = 600
HEIGHT = 300
def gothere(x, y):
screen.onscreenclick(gothere) # disable events inside handler
turtle.penup()
print("gothere (%d,%d)" % (x, y))
turtle.goto(x, y)
turtle.pendown()
screen.onscreenclick(gothere)
def movearound(x, y):
turtle.ondrag(None) # disable events inside handler
turtle.setheading(turtle.towards(x, y))
print("movearound (%d,%d)" % (x, y))
turtle.goto(x, y)
turtle.ondrag(movearound)
def release(x, y):
print("release (%d,%d)" % (x, y))
turtle.penup()
def reset():
print("reset")
turtle.clear()
screen = Screen()
screen.setup(WIDTH, HEIGHT)
# screen.setworldcoordinates(0, HEIGHT, WIDTH, 0) # should work fine either way
turtle = Turtle('turtle')
turtle.speed('fastest')
turtle.ondrag(movearound)
turtle.onrelease(release)
screen.onscreenclick(gothere)
screen.onkey(reset, "Escape")
screen.listen()
mainloop() # normally screen.mainloop() but not in Python 2
, но и ответ где я показываю, как сделать tkinter это onmove
событие доступно для turtle.
... "переехать сюда" тогда ограничение "начать перетаскивание" очень не удобный для потребителя? Как мы можем это улучшить?
объединение моего кода выше с моим альтернативным ответом, с которым я связан, мы получаем это решение, похожее на то, где вы начали, но без сбоев и в более черепашьем стиле:
from turtle import Turtle, Screen, mainloop
from functools import partial
WIDTH = 600
HEIGHT = 300
VERBOSE = False
def onscreenmove(self, fun, btn=1, add=None): # method missing from turtle.py
if fun is None:
self.cv.unbind('<Button%s-Motion>' % btn)
else:
def eventfun(event):
fun(self.cv.canvasx(event.x) / self.xscale, -self.cv.canvasy(event.y) / self.yscale)
self.cv.bind('<Button%s-Motion>' % btn, eventfun, add)
def onscreenrelease(self, fun, btn=1, add=None): # method missing from turtle.py
if fun is None:
self.cv.unbind("<Button%s-ButtonRelease>" % btn)
else:
def eventfun(event):
fun(self.cv.canvasx(event.x) / self.xscale, -self.cv.canvasy(event.y) / self.yscale)
self.cv.bind("<Button%s-ButtonRelease>" % btn, eventfun, add)
def gothere(x, y):
if VERBOSE:
print("gothere (%d,%d)" % (x, y))
turtle.penup()
turtle.goto(x, y)
turtle.pendown()
def movearound(x, y):
screen.onscreenmove(None) # disable events inside handler
if VERBOSE:
print("movearound (%d,%d)" % (x, y))
turtle.setheading(turtle.towards(x, y))
turtle.goto(x, y)
screen.onscreenmove(movearound) # reenable events
def release(x, y):
if VERBOSE:
print("release (%d,%d)" % (x, y))
turtle.penup()
def reset():
if VERBOSE:
print("reset")
turtle.clear()
screen = Screen()
screen.setup(WIDTH, HEIGHT)
screen.onscreenrelease = partial(onscreenrelease, screen) # install missing methods
screen.onscreenmove = partial(onscreenmove, screen)
turtle = Turtle('turtle')
turtle.speed('fastest')
screen.onscreenclick(gothere)
screen.onscreenrelease(release)
screen.onscreenmove(movearound)
screen.onkey(reset, "Escape")
screen.listen()
mainloop() # normally screen.mainloop() but not in Python 2
Я согласен с cdlane, что было бы лучше избегать привязки событий уровня Tkinter, где это возможно, но я подумал, что было бы также интересно подойти к проблеме, не сильно изменяя общий дизайн. Мое решение требует только дополнительного импорта:
from collections import deque
новая версия movearound
:
pending = deque()
def movearound(event):
x = event.x
y = event.y
pending.append((x,y))
if len(pending) == 1:
while pending:
x,y = pending[0]
turtle.goto(x,y)
pending.popleft()
как я указал в своем комментарии goto
можно назвать movearound
если резервная копия очереди событий окна, и это может вызвать переполнение стека или гонки условия, которые заставляют черепаху двигаться необычным образом. Этот подход направлен на предотвращение произвольно глубокой рекурсии, позволяя только самый верхний экземпляр movearound
вызов goto
. if len(pending) == 1:
должно выполняться только для нерекурсивных вызовов; все рекурсивные вызовы будут видеть очередь больше, чем это. The while pending:
loop работает через все встроенные события, обрабатывая их в порядке, в котором они прибыли.
результат: черепаха, которая послушно следует по пути курсора, хотя и в своей собственной turtley Пейс:
добавлять turtle.tracer(2,0)
Кажется, что проблема исчезает. Это может быть временное решение, так как я не знаю, просто ли оно скрывает проблему за другой.
...
screen = turtle.Screen()
screen.setworldcoordinates(0,height,width,0)
screen.listen()
turtle.tracer(2, 0) # This line
turtle.mainloop()
...
в документации мы можем прочитать:
включает/выключает анимацию черепахи и устанавливает задержку для обновления чертежей.
как указывает @Keven, проблема, похоже, неявная update
в обратного вызова. Это уменьшает количество update
звонок 2. Я думаю, что он также может удалить рекурсивный вызов.
примечание: Я не знаю, почему, но убрать screen.setworldcoordinates(0,height,width,0)
удаляет поведение Глюка, но рекурсивный вызов update
по-прежнему присутствует.