问题
Using the Python 3 "turtle" module, I am trying to handle two different click conditions separately:
- If a turtle is clicked on, it should call a function. (In the example below, it should switch from black to red, or vice versa.)
- If a click is NOT on a (visible) turtle, a different function should be called (to create a turtle at that point).
Right now I can make this work using two different mouse buttons, like so:
#!/usr/local/bin/python3
import turtle
sc = turtle.Screen()
def new_turtle(x, y):
t = turtle.RawTurtle(sc, shape='circle', visible=False)
t.penup()
t.speed(0)
t.goto(x, y)
t.color('black')
t.showturtle()
t.onclick(selector(t), 2)
def deselector(t):
def deselect(x, y):
t.color('black')
t.onclick(selector(t), 2)
return deselect
def selector(t):
def select(x, y):
t.color('red')
t.onclick(deselector(t), 2)
return select
sc.onscreenclick(new_turtle, 1)
turtle.mainloop()
However, I want to use the second mouse button for other things.
If the above code is changed to use mouse button 1 for everything, the turtles do change color when clicked like they're supposed to, but the onscreenclick
handler is also called so that a new turtle is created almost right above the turtle that changes color.
Is there any way to only call the onscreenclick
handler if the click is not on a turtle?
回答1:
I believe the following will do what you describe. As you noted, if there is both a screen button event handler and a turtle button event handler active on the same button, both get triggered! This seems wrong for a beginner friendly programming environment sitting atop tkinter. But there you have it.
My solution below is to have the screen button event handler test if the click was likely over a turtle, and, if so, ignore it. This lets just the turtle button event handler deal with the click:
from turtle import Screen, Turtle
from functools import partial
CURSOR_SIZE = 20
def new_turtle(x, y):
screen.onscreenclick(None) # disable event handler inside handler
# don't respond if the click was on a turtle
if not any(t.distance(x, y) <= CURSOR_SIZE/2 for t in screen.turtles()):
t = Turtle(shape='circle', visible=False)
t.color('black')
t.penup()
t.goto(x, y)
t.showturtle()
t.onclick(partial(select, t))
screen.onscreenclick(new_turtle) # reenable event handler
def select(t, x, y):
t.color('black' if t.pencolor() == 'red' else 'red')
screen = Screen()
screen.onscreenclick(new_turtle)
screen.mainloop()
One side effect is that clicking on the screen away from turtles gets slightly sluggish as lots of turtles are added to the screen and need to be tested. To get around this, I noticed, on my system at least, that turtle button event handlers are invoked before screen button event handlers. So the trick is to have the turtle button event handler disable the screen button event handler, but eventually reenable it:
from turtle import Screen, Turtle
from functools import partial, update_wrapper
def new_turtle(x, y):
screen.onscreenclick(None) # disable this event handler inside handler
t = Turtle(shape='circle', visible=False)
t.color('black')
t.penup()
t.goto(x, y)
t.showturtle()
t.onclick(partial(select, t))
screen.onscreenclick(new_turtle) # reenable event handler
def select(t, x, y):
screen.onscreenclick(None) # disable screen event handler inside handler
t.onclick(None) # disable this event handler inside handler
t.color('black' if t.pencolor() == 'red' else 'red')
t.onclick(partial(select, t)) # reenable this event handler
screen.ontimer(wrapper) # reenable screen event handler, eventually
screen = Screen()
wrapper = partial(screen.onscreenclick, new_turtle) # prep wrapper for later use
update_wrapper(wrapper, screen.onscreenclick)
screen.onscreenclick(new_turtle)
screen.mainloop()
This does not require examing the turtles so won't slow down. (Nor be as finicky as you click very close to the turtle.) However if the timing doesn't match up the same, you might have to use the other version.
来源:https://stackoverflow.com/questions/62336296/how-to-handle-clicks-on-a-turtle-and-clicks-off-of-a-turtle-separately