How can I make an object move diagonally in a Tkinter canvas every time someone presses two arrow keys at the same time?
I'm creating a simple animation but it only moves up, down, left or right.
Here's the code I have:
from tkinter import *
import time
root = Tk()
canvas = Canvas(root, width=800, height=800)
square = canvas.create_rectangle(0,0,50,50,outline='red')
def right(event):
for i in range(5):
canvas.move(ball,1,0)
canvas.update()
def left(event):
for i in range(5):
canvas.move(ball,-1,0)
canvas.update()
def down(event):
for i in range(5):
canvas.move(ball,0,1)
canvas.update()
def up(event):
for i in range(5):
canvas.move(ball,0,-1)
canvas.update()
root.bind('<Right>', right)
root.bind('<Left>', left)
root.bind('<Down>', down)
root.bind('<Up>', up)
canvas.pack()
root.mainloop()
Keypresses in tkinter are individual events; to the exception of key modifiers (shift, control, alt), you cannot bind an action to the "simultaneous" pressing of two keys.
What you can do, is assign NE, SE, NW, SW moves to different keys.
I assigned the control of the movements to the following keys:
Q W E
A D
Z X C
other changes:
It is best practice to avoid star imports.
It was not necessary to update the canvas each time; move already redraws the changes on the canvas.
I assigned a 'speed' to the object, removed the repeated calling of movein a loop, and use the speed to determine the distance to move.
I renamed square to ball, so ball is defined.
the code:
import tkinter as tk
root = tk.Tk()
canvas = tk.Canvas(root, width=800, height=800)
ball = canvas.create_rectangle(0, 0, 50, 50, outline='red')
speed = 5
def w(event):
canvas.move(ball, speed, 0)
def e(event):
canvas.move(ball, -speed, 0)
def s(event):
canvas.move(ball, 0, speed)
def n(event):
canvas.move(ball, 0, -speed)
def nw(e):
canvas.move(ball, speed, -speed)
def sw(e):
canvas.move(ball, speed, speed)
def ne(e):
canvas.move(ball, -speed, -speed)
def se(e):
canvas.move(ball, -speed, speed)
root.bind('<KeyPress-w>', n)
root.bind('<KeyPress-e>', nw)
root.bind('<KeyPress-d>', w)
root.bind('<KeyPress-c>', sw)
root.bind('<KeyPress-x>', s)
root.bind('<KeyPress-z>', se)
root.bind('<KeyPress-a>', e)
root.bind('<KeyPress-q>', ne)
canvas.pack()
root.mainloop()
Related
I've been working on learning more about the Canvas() widget in tkinter, so I decided to build a simple paint app just for practice.
To achieve this, I created a canvas and binded it to "<B1-Motion>", but it becomes unresponsive when I drag the mouse too fast.
Here's a code sample:
from tkinter import *
class Paint:
def __init__(self, root):
self.root = root
self.current_x = None
self.current_y = None
self.brush_size = 10
self.brush_color = "black"
def create_widgets(self):
# Creating the canvas
self.canvas = Canvas(self.root, width=1000, height=1000)
self.canvas.grid(row=0, column=0, sticky="nsew")
# Setting up bindings for the canvas.
self.canvas.bind("<Button-1>", self.setup_coords)
self.canvas.bind("<B1-Motion>", self.drag)
def setup_coords(self, e):
# Reset the starting points to the current mouse position
self.current_x = e.x
self.current_y = e.y
def drag(self, e):
# Create an oval that's size is the same as brush_size
oval = self.canvas.create_oval(self.current_x, self.current_y, self.current_x+self.brush_size, self.current_y+self.brush_size, fill=self.brush_color)
# Set the variables values to the current position of the mouse, so that the oval gets drawn correctly on the next call.
self.current_x = e.x
self.current_y = e.y
def main():
root = Tk()
root.geometry("1000x1000")
p = Paint(root)
p.create_widgets()
mainloop()
if __name__ == '__main__':
main()
Here, when I drag the mouse slowly, everything works just fine:
But as soon as I start dragging fast, the bindings don't get called on time and only a few circles get drawn:
Am I doing something inefficiently here? Is there any way to fix this problem?
It would be great if anyone could help me out. Thanks in advance.
UPDATE:
I tried acw1668's suggestion which is drawing lines instead of circles and setting it's width to the brush size:
from tkinter import *
class Paint:
def __init__(self, root):
self.root = root
self.current_x = None
self.current_y = None
self.brush_size = 50
self.brush_color = "black"
def create_widgets(self):
# Creating the canvas
self.canvas = Canvas(self.root, width=1000, height=1000)
self.canvas.grid(row=0, column=0, sticky="nsew")
# Setting up bindings for the canvas.
self.canvas.bind("<Button-1>", self.setup_coords)
self.canvas.bind("<B1-Motion>", self.drag)
def setup_coords(self, e):
# Reset the starting points to the current mouse position
self.current_x = e.x
self.current_y = e.y
def drag(self, e):
# Create an oval that's size is the same as brush_size
oval = self.canvas.create_line(self.current_x, self.current_y, e.x, e.y, width=self.brush_size, fill=self.brush_color)
# Set the variables values to the current position of the mouse, so that the oval gets drawn correctly on the next call.
self.current_x = e.x
self.current_y = e.y
def main():
root = Tk()
root.geometry("1000x1000")
p = Paint(root)
p.create_widgets()
mainloop()
if __name__ == '__main__':
main()
But still, there is some unwanted gap when I increase the brush size:
Any fixes?
For well-connected lines, set the set capstyle to round when creating lines.
Check out this canvas tutorial for more details.
Almost all drawing libraries that I'm familiar with support different line end ("caps") styles, as well as line connection ("joins") styles.
Also note that you can use create_line to draw either quadratic Bézier splines or cubic splines.
I am trying to make a program that lets me draw on a tkinter window using turtle. For some reason I cannot get the absolute mouse coordinates.
I have done root.winfo_pointerx() - root.winfo_rootx() (and vrootx).
I have also tried:
def mousePos(event):
x,y = event.x , event.y
return x,y
My code:
import turtle
import tkinter as tk
root = tk.Tk()
root.title("Draw!")
cv = tk.Canvas(root, width=500,height=500)
cv.focus_set()
cv.pack(side = tk.LEFT)
pen = turtle.RawTurtle(cv)
window = pen.getscreen()
def main():
window.setworldcoordinates(-500,-500,500,500)
window.bgcolor("white")
frame = tk.Frame(root)
frame.pack(side = tk.RIGHT,fill=tk.BOTH)
pointLabel = tk.Label(frame,text="Width")
pointLabel.pack()
def getPosition(event):
x = root.winfo_pointerx()-root.winfo_vrootx()
y = root.winfo_pointery()-root.winfo_vrooty()
pen.goto(x,y)
cv.bind("<Motion>", getPosition)
cv.pack
tk.mainloop()
pass
I want the cursor to be on top of the arrow, but instead it is always to the right and down. Also, when I move the mouse up, the arrow moves down, and vice versa.
Think hard about how you set the setworldcoordinate(). -500 - 500 means your world has 1,000 in size and window size is 500. Also, the mouse pointer offset from the window root - both absolute coordinates should be used. You mixed up the absolute coordinates - mouse pointer and vrootx which is in different scale so the distance of two makes no sense. Following code is probably closer to what you intended. Note that, I set the world coordinate to match the absolute coordinates of mouse pointer offset from the top/left corner of window.
import turtle
import tkinter as tk
root = tk.Tk()
root.title("Draw!")
cv = tk.Canvas(root, width=500,height=500)
cv.focus_set()
cv.pack(side = tk.LEFT)
pen = turtle.RawTurtle(cv)
window = pen.getscreen()
def main():
window.setworldcoordinates(0,500,500,0)
window.bgcolor("white")
frame = tk.Frame(root)
frame.pack(side = tk.RIGHT,fill=tk.BOTH)
pointLabel = tk.Label(frame,text="Width")
pointLabel.pack()
print(dir(root))
def getPosition(event):
x = root.winfo_pointerx()-root.winfo_rootx()
y = root.winfo_pointery()-root.winfo_rooty()
print(x, y)
pen.goto(x,y)
pass
cv.bind("<Motion>", getPosition)
cv.pack
tk.mainloop()
pass
if __name__ == "__main__":
main()
pass
You've got an issue working against you that isn't of your own making. The general rule is when in a turtle canvas, use turtle methods. But turtle doesn't have an inherent 'Motion' event type, so you were trying to use the raw Canvas one as a substitute. Thus the conflict.
An issue of your own making is that when you're inside a fast moving event handler, you need to disable the event hander as the first thing you do, reenabling on exit. Otherwise, events overlap and bad things happen. (Inadvertant recursions and other wierdness.)
I've rewritten your program below to work as I believe you intended. The fix is adding the missing turtle method so we can stay within the turtle domain:
import tkinter as tk
from turtle import RawTurtle, TurtleScreen
from functools import partial
def onscreenmove(self, fun, add=None): # method missing from turtle.py
if fun is None:
self.cv.unbind('<Motion>')
else:
def eventfun(event):
fun(self.cv.canvasx(event.x) / self.xscale, -self.cv.canvasy(event.y) / self.yscale)
self.cv.bind('<Motion>', eventfun, add)
def getPosition(x, y):
screen.onscreenmove(None) # disable events inside handler
pen.setheading(pen.towards(x, y))
pen.goto(x, y)
screen.onscreenmove(getPosition) # reenable handler on exit
root = tk.Tk()
root.title("Draw!")
cv = tk.Canvas(root, width=500, height=500)
cv.focus_set()
cv.pack(side=tk.LEFT)
screen = TurtleScreen(cv)
screen.onscreenmove = partial(onscreenmove, screen) # install missing method
pen = RawTurtle(screen)
frame = tk.Frame(root)
frame.pack(side=tk.RIGHT, fill=tk.BOTH)
tk.Label(frame, text="Width").pack()
screen.onscreenmove(getPosition)
screen.mainloop()
Mouse position for Tkinter:
import Tkinter as tk
root = tk.Tk()
def motion(event):
x, y = event.x, event.y
print('{}, {}'.format(x, y))
root.bind('<Motion>', motion)
root.mainloop()
Mouse position for turtle:
canvas = turtle.getcanvas()
x, y = canvas.winfo_pointerx(), canvas.winfo_pointery()
Hope this helps.
I am attempting to create a quick turtle display using Tkinter, but some odd things are happening.
First two turtle windows are being created, (one blank, one with the turtles), secondly, any attempt of turning the tracer off is not working.
This might be a simple fix but at the moment I cannot find it.
Any help would be appreciated,
Below is the code:
import tkinter as tk
import turtle
window = tk.Tk()
window.title('Top 10\'s')
def loadingscreen():
canvas = tk.Canvas(master = window, width = 500, height = 500)
canvas.pack()
arc1 = turtle.RawTurtle(canvas)
arc2 = turtle.RawTurtle(canvas)
#clean up the turtles and release the window
def cleanup():
turtle.tracer(True)
arc1.ht()
arc2.ht()
turtle.done()
#animate the turtles
def moveTurtles(rangevar,radius,extent,decrease):
for distance in range(rangevar):
arc1.circle(-radius,extent = extent)
arc2.circle(-radius,extent = extent)
radius -= decrease
#Set the turtle
def setTurtle(turt,x,y,heading,pensize,color):
turt.pu()
turt.goto(x,y)
turt.pd()
turt.seth(heading)
turt.pensize(pensize)
turt.pencolor(color)
#draw on the canvas
def draw():
#set variables
rangevar = 200
radius = 200
decrease = 1
extent = 2
#setup and draw the outline
turtle.tracer(False)
setTurtle(arc1,0,200,0,40,'grey')
setTurtle(arc2,14,-165,180,40,'grey')
moveTurtles(rangevar,radius,extent,decrease)
#setup and animate the logo
turtle.tracer(True)
setTurtle(arc1,0,200,0,20,'black')
setTurtle(arc2,14,-165,180,20,'black')
moveTurtles(rangevar,radius,extent,decrease)
#main program
def main():
turtle.tracer(False)
arc1.speed(0)
arc2.speed(0)
draw()
cleanup()
if __name__ == "__main__":
try:
main()
except:
print("An error occurred!!")
loadingscreen()
Essentially I am creating a Tk window, then a canvas, then two turtles, and then animating these turtles
My guess is you're trying to call turtle screen methods without actually having a turtle screen. When turtle is embedded in tkinter like this, you can overlay a Canvas with a TurtleScreen instance which will provide some, but not all, of the screen features of the standalone turtle:
import tkinter as tk
from turtle import RawTurtle, TurtleScreen
def cleanup():
""" hide the turtles """
arc1.hideturtle()
arc2.hideturtle()
def moveTurtles(rangevar, radius, extent, decrease):
""" animate the turtles """
for _ in range(rangevar):
arc1.circle(-radius, extent=extent)
arc2.circle(-radius, extent=extent)
radius -= decrease
def setTurtle(turtle, x, y, heading, pensize, color):
turtle.penup()
turtle.goto(x, y)
turtle.pendown()
turtle.setheading(heading)
turtle.pensize(pensize)
turtle.pencolor(color)
def draw():
# set variables
rangevar = 200
radius = 200
decrease = 1
extent = 2
screen.tracer(False) # turn off animation while drawing outline
# setup and draw the outline
setTurtle(arc1, 0, 200, 0, 40, 'grey')
setTurtle(arc2, 14, -165, 180, 40, 'grey')
moveTurtles(rangevar, radius, extent, decrease)
screen.tracer(True) # turn animation back on for the following
# setup and animate the logo
setTurtle(arc1, 0, 200, 0, 20, 'black')
setTurtle(arc2, 14, -165, 180, 20, 'black')
moveTurtles(rangevar, radius, extent, decrease)
# main program
window = tk.Tk()
window.title("Top 10's")
canvas = tk.Canvas(master=window, width=500, height=500)
canvas.pack()
screen = TurtleScreen(canvas)
arc1 = RawTurtle(screen)
arc1.speed('fastest')
arc2 = RawTurtle(screen)
arc2.speed('fastest')
draw()
cleanup()
Another suggestion: don't mess with tracer() until after everything else is working and then (re)read it's documentation carefully.
I just have the answer for the two windows that are being created: the one with the turtles is obvious, and the blank one is for the main root window that you define (window = tk.Tk()). If you want it not to appear at all, you can add the following line right after its definition:
window.withdraw()
I found this solution here and here.
I am making a game in which you dodge falling object by using the tkinter library. In my code, I am trying to make an object fall by binding a canvas.move() function with pressing the down arrow key, then using pyautogui to hold down the key. Here is my code:
from tkinter import *
from random import randint
import pyautogui
class Window(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.master = master
self.initWindow()
def initWindow(self):
self.master.title('Dodger')
self.pack(fill=BOTH, expand=1)
self.master.geometry('600x800')
self.master.config(bg='black')
menu = Menu(self.master)
self.master.config(menu=menu)
def clientExit():
exit()
file = Menu(menu)
file.add_command(label='Exit', command=clientExit)
file.add_command(label='Start', command=self.game)
menu.add_cascade(label='File', menu=file)
def game(self):
canvas = Canvas(self.master, width='600', height='800', borderwidth='0', highlightthickness='0')
canvas.pack()
canvas.create_rectangle(0, 0, 600, 800, fill='black', outline='black')
character = canvas.create_rectangle(270, 730, 330, 760, fill='magenta', outline='cyan', width='2')
def left(event):
cord = canvas.coords(character)
if not cord[0] <= 5:
canvas.move(character, -10, 0)
def right(event):
cord = canvas.coords(character)
if not cord[2] >= 595:
canvas.move(character, 10, 0)
self.master.bind('<Left>', left)
self.master.bind('<Right>', right)
class variables:
sizeMin = 10
sizeMax = 80
y = 10
minX = 5
maxX = 545
def createShape():
size = randint(variables.sizeMin, variables.sizeMax)
x = randint(variables.minX, variables.maxX)
topLeft = [x, variables.y]
bottomRight = [x + size, variables.y + size]
shape = canvas.create_rectangle(topLeft[0], topLeft[1], bottomRight[0], bottomRight[1],
fill='red', outline='red')
return shape
def moveShape(event):
cord = canvas.coords(x)
if cord[1] != 800:
canvas.move(x, 0, 10)
x = createShape()
self.master.bind('<Down>', moveShape)
pyautogui.keyDown('down')
root = Tk()
app = Window(root)
app.mainloop()
As you can see, at the bottom of the class, I binded the down arrow key and moving a shape down. However, the pyautogui does not work; the object does not move down unless I manually press the down arrow key. Am I forgetting something or is pyautogui not compatible with bind()? I know there are more efficient ways to move the object down, however with all the methods I have tried, none show the actual movement of the object heading down the screen; they just show the object being re-created in another position. Please let me know how I can fix this.
I wouldn't bother with it. Maybe it misses the _all argument. Try to simply bind canvas.move() function to a canvas.bind_all()
I'm working on a simple drawing program that combines Tkinter and Turtle modules.
I would like to add an option that the user can draw anything by just using mouse similar to pen widget on Paint. I tried many things, I could not figure out how l can do it.How can l make the turtle draw anything (like pen widget on Paint ) on canvas by using mouse
from tkinter import *
import turtle
sc=Tk()
sc.geometry("1000x1000+100+100")
fr4=Frame(sc,height=500,width=600,bd=4,bg="light green",takefocus="",relief=SUNKEN)
fr4.grid(row=2,column=2,sticky=(N,E,W,S))
#Canvas
canvas = Canvas(fr4,width=750, height=750)
canvas.pack()
#Turtle
turtle1=turtle.RawTurtle(canvas)
turtle1.color("blue")
turtle1.shape("turtle")
points=[]
spline=0
tag1="theline"
def point(event):
canvas.create_oval(event.x, event.y, event.x+1, event.y+1, fill="red")
points.append(event.x)
points.append(event.y)
return points
def canxy(event):
print (event.x, event.y)
def graph(event):
global theline
canvas.create_line(points, tags="theline")
def toggle(event):
global spline
if spline == 0:
canvas.itemconfigure(tag1, smooth=1)
spline = 1
elif spline == 1:
canvas.itemconfigure(tag1, smooth=0)
spline = 0
return spline
canvas.bind("<Button-1>", point)
canvas.bind("<Button-3>", graph)
canvas.bind("<Button-2>", toggle)
sc.mainloop()
The following code will let you freehand draw with the turtle. You'll need to integrate with the rest of your code:
import tkinter
import turtle
sc = tkinter.Tk()
sc.geometry("1000x1000+100+100")
fr4 = tkinter.Frame(sc, height=500, width=600, bd=4, bg="light green", takefocus="", relief=tkinter.SUNKEN)
fr4.grid(row=2, column=2, sticky=(tkinter.N, tkinter.E, tkinter.W, tkinter.S))
# Canvas
canvas = tkinter.Canvas(fr4, width=750, height=750)
canvas.pack()
# Turtle
turtle1 = turtle.RawTurtle(canvas)
turtle1.color("blue")
turtle1.shape("turtle")
def drag_handler(x, y):
turtle1.ondrag(None) # disable event inside event handler
turtle1.goto(x, y)
turtle1.ondrag(drag_handler) # reenable event on event handler exit
turtle1.ondrag(drag_handler)
sc.mainloop()