странный прыжок курсора - PullRequest
0 голосов
/ 30 апреля 2018

Я пытаюсь использовать рисование черепах с мышью, демонстрационный код работает ниже, но иногда при перемещении мыши прыгает курсор:

#!/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 для фактического поведения:

enter image description here

Не уверен, что какой-либо вызов API неверен!

Ответы [ 3 ]

0 голосов
/ 01 мая 2018

Я вижу несколько проблем с вашим кодом:

  • Вы смешиваете объектно-ориентированный интерфейс с черепахой функциональный интерфейс к этому модулю. Я рекомендую один или другой, но не оба. См. Мое import изменение, чтобы принудительно только OOP.

  • Вы используете низкоуровневые события мыши и клавиш 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 доступным для черепахи.

... ограничение "переместить сюда", а затем "начать перетаскивание" очень не удобно для пользователя? Как мы можем улучшить это?

Комбинируя мой приведенный выше код с моим альтернативным ответом, на который я ссылался, мы получаем решение, аналогичное тому, с которого вы начали, но без сбоев и в более черепахоподобном стиле:

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
0 голосов
/ 01 мая 2018

Я согласен с 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: должно выполняться только для нерекурсивных вызовов; все рекурсивные вызовы будут видеть очередь больше этой. Цикл while pending: работает со всеми встроенными событиями, обрабатывая их в порядке поступления.

Результат: черепаха, которая покорно следует по пути курсора, хотя и в своем собственном темпе:

enter image description here

0 голосов
/ 30 апреля 2018

Добавление 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 все еще присутствует.

...