'Move object' function bound to a key in Tkinter can only make one object move at a time, how to make more object move at the same time? - python

I've bound a key to a function which makes an oval (included in a list of other identical ovals) moves a certain distance. I want it to make a new oval moves each time I press the key, without stopping the previous moving oval if its course is not over.
With my code, by pressing 'c', I create a new oval randomly placed on the canvas, and saved in a dictionary. Each new oval is saved with key = 'compteur', 'compteur' increments for every new oval created to make sure every oval is not created over a previous existing one.
By pressing 'm', I want to make a new oval move each time I press the key, without the previous one stopping.
from tkinter import *
import time
from random import *
import time
compteur = 0
dic = {}
w = Tk()
w.geometry('400x400')
c = Canvas(w, width = 400, height = 400)
c.pack()
dic[compteur] = c.create_oval(200,150,250,200,fill = 'pink')
compteur += 1
def create(event):
global compteur
b = randrange(300)
dic[compteur] = c.create_oval(200,b,250,(b+50),fill = 'pink')
compteur += 1
def move(event):
rond = dic[randrange(len(dico))]
if c.coords(rond)[0] == 200:
for x in range (15):
c.move(rond,-10,0)
w.update()
time.sleep(0.15)
w.bind('<m>', move)
w.bind('<c>',create)
w.mainloop()
I'm obviously missing something but as I'm a beginner, I have no idea why only one oval can move at a time. And weirdly, once the second oval finish it's course, the first one starts again to finish its course too.
Thanks for your help :)

I use list to keep all circles.
In move() I move last circle from list only when I press <m>
In move_other() I move all circles except last one and use after() to run move_other() after 100ms (0.1s) so it will move all time.
from tkinter import *
from random import *
# --- fucntions ---
def create(event):
b = randrange(300)
circles.append(c.create_oval(200, b, 250, (b+50), fill='pink'))
def move(event):
item = circles[-1]
c.move(item, -10, 0)
def move_other():
for item in circles[:-1]:
c.move(item, -10, 0)
w.after(100, move_other)
# --- main ---
circles = []
w = Tk()
w.geometry('400x400')
c = Canvas(w, width=400, height=400)
c.pack()
circles.append(c.create_oval(200, 150, 250, 200, fill='pink'))
move_other() # start moving other circles
w.bind('<m>', move)
w.bind('<c>', create)
w.mainloop()

Related

I'm trying to make a simple line drawing program with tkinter but it won't work

I'm trying to make this really simple program, all it does is store the current x/y pos of the mouse on the canvas and then use them to draw a line when you click for the second time. I've already bound it and I'm not getting any errors, it seems like it's not even being activated. Any help is greatly appreciated
from tkinter import *
main = Tk()
c = Canvas(main, width=600, height=600)
c.pack()
#For colored lines
presses = 0
def click(event):
if presses == 0:
initX = int(c.canvasx(event.x))
initY = int(c.canvasy(event.y))
presses == 1
elif presses == 1:
c.create_line(initX, initY,
int(c.canvasx(event.x)),
int(c.canvasy(event.y)))
presses == 0
c.bind("<Button-1>", click)
mainloop()
How does something like this work for you?
from tkinter import *
main = Tk()
c = Canvas(main, width=600, height=600)
c.pack()
line = []
def click(event):
global line
X = int(c.canvasx(event.x))
Y = int(c.canvasy(event.y))
line.append((X,Y))
if len(line) > 1:
startX,startY = line[-2]
c.create_line(startX, startY, X, Y)
c.bind("<Button-1>", click)
mainloop()
I've changed around your code a bit to store a list of the X,Y coordinates that have been clicked on. If more than 1 point on the screen has been clicked, it will draw a line between the current point clicked on and the last point clicked on.
Reason your code wasn't working was that initX and initY are forgotten in between calls on the the click function. Adding them to a list solves this.

Making text shrink and expand enlessly in tkinter canvas

Basically, I wanted to make a program that would create a text in the cavas with the size 1, rotate it by 180 degrees (continuosly) and while that expand it to its full size (let's say 50) than keep rotating it and by the time it has made a full it spin it would have shrunk down to 1 again and then repeat the process.
This is the only think I've come up with and keep in mind I've been only messing around with Python for a week or two so the code will probably need to be completly changed.
from tkinter import *
import time
import random
size=1
angl=0
i=0
canvas=Canvas(width=600, height=600)
canvas.pack()
while i<180:
canvas.delete("all")
canvas.create_text(150,150, text="kappa123",angle=angl,font=("helvetica",size))
angl+=1
size+=1
i+=1
canvas.update()
time.sleep(1/360)
while i>=180:
canvas.delete("all")
canvas.create_text(150,150, text="kappa123",angle=angl,font=("helvetica",size))
angl+=1
size-=1
i+=1
canvas.update()
time.sleep(1/360)
As you can see, it only works once adn then expands forever.
I believe it is because you aren't leaving the second loop. your i variable moves on into infinity. To see an example run this and look at the output:
from tkinter import *
import time
j = 0 # Time spent in the first while loop
k = 0 # Time spent in the second while loop
size = 1
angl = 0
i = 0
canvas = Canvas(width=600, height=600)
canvas.pack()
while i < 180:
canvas.delete("all")
canvas.create_text(150, 150, text="kappa123", angle=angl, font=("helvetica", size))
angl += 1
size += 1
i += 1
j += 1
print('First loop', j)
canvas.update()
time.sleep(1 / 360)
while i >= 180:
canvas.delete("all")
canvas.create_text(150, 150, text="kappa123", angle=angl, font=("helvetica", size))
angl += 1
size -= 1
i += 1
k += 1
print('Second loop', k)
canvas.update()
time.sleep(1 / 360)
A good solution to your problem, depending on your skill and comfort level, would be to create two functions, one to grow and one to shrink. Use the grow function to go from 1 to 180, then call the shrink function when you have iterated through and shrink from 180 to 1.
Edit
class Spinner(Canvas):
def __init__(self, parent):
self.size = 1
# Other data
self.pack()
def expand(self):
for i in range(0, 181):
self.angl += 1
# increment size, update, sleep
self.shrink()
def shrink(self):
for i in range(0, 181):
# increment angle, decrease size, update, sleep
self.expand()
root = Tk()
canvas = Spinner(root)
root.mainloop()

Why does my object move in the wrong direction

I have a made a simple program which is meant to move a ball left and right horizontally within a canvas. The user will use the left and right keys to move the ball accordingly by 5 pixels a time. If the x coordinate of the ball is less than 40 or more than 240 then it will do nothing.
try:
import tkinter as tk
except ImportError:
import Tkinter as Tk
window = tk.Tk()
game_area = tk.Canvas(width=270, height=400, bd=0, highlightthickness=0,
bg="white")
ball = game_area.create_oval(10, 10, 24, 24, fill="red")
game_area.move(ball, 120, 4)
coords = 120
def move_left(event):
global coords
if coords < 40:
pass
else:
coords = int(coords)- 5
game_area.move(ball, coords, 4)
game_area.update()
def move_right(event):
global coords
if coords > 240:
pass
else:
coords = int(coords)+5
game_area.move(ball, coords, 4)
game_area.update()
window.bind("<Left>", move_left)
window.bind("<Right>", move_right)
game_area.pack()
window.mainloop()
However, pressing either key moves the ball towards the right (more than 5 pixels across) and off the screen despite the if function which is meant to prevent this.
According to the Tkinter Canvas documentation, the second argument to the move method, dx, is an offset. Try calling it like
game_area.move(ball, -5, 4)
Then you don't need the following line, either.
coords = int(coords)- 5

How to change the coordinates of a shape (i.e. oval) in tkinter?

I am trying to make a program which when you press the S button it moves shape to the square below it on the grid. I have managed to get the shape to move the first time but after that it just keeps getting bigger.
Here is my code:
from tkinter import *
root = Tk()
global y
y = 0
x = 0
def down(event):
global y
global x
y = y+100
x = x+ 100
global pirate
canvas.delete(pirate)
pirate = canvas.create_oval((x,y), (100,100), fill = 'red')
print(y)
canvas = Canvas(root, width = 1000, height = 1000)
canvas.pack()
for a in range (10):
i = a*100
canvas.create_line((i,0), (i,1000))
for a in range (10):
i = a*100
canvas.create_line((0,i), (1000,i))
pirate = canvas.create_oval((x, y),(100, 100), fill = 'red')
root.bind('<Key - S>', down)
root.mainloop()
As ArtOfWarfare mentioned in comments, instead of creating new ovals everytime, create one and move that thing around.
def down(event):
canvas.move(pirate, 0, 100)
Above code is sufficient to move your oval one square down in your code.
Assuming you'll need to move oval other than just down, instead of binding only S to canvas, I think you should get all key events and do stuff depending on pressed char.
def keyPressed(event):
if event.char.lower() == 's': #move it down if it's S or s
canvas.move(pirate, 0, 100)
root.bind('<Key>', keyPressed) #get all key pressed events

Keyup handler in Tkinter?

The title says it all. Is there a something in Tkinter I can call which will let me monitor specific key releases and let me link it to a function? I want to use it to let me end a timer that I am using to move my item. Here is the code:
from Tkinter import *
master = Tk()
master.wm_title("Ball movement")
width = 1000
height = 600
circle = [width / 2, height / 2, width / 2 + 50, height / 2 + 50]
canvas = Canvas(master, width = width, height = height, bg = "White")
canvas.pack()
canvas.create_oval(circle, tag = "ball", fill = "Red")
while True:
canvas.update()
def move_left(key):
#This is where my timer will go for movement
canvas.move("ball", -10, 0)
canvas.update()
def move_right(key):
#This is where my other timer will go
canvas.move("ball", 10, 0)
canvas.update()
frame = Frame(master, width=100, height=100)
frame.bind("<Right>", move_right)
frame.bind("<Left>", move_left)
frame.focus_set()
frame.pack()
mainloop()
You can define events prefixed with KeyRelease, such as <KeyRelease-a>. For example:
canvas.bind("<KeyRelease-a>", do_something)
Note: you need to remove your while loop. You should never create an infinite loop inside a GUI program, and you definitely don't want to be creating a frame every iteration -- you'll end up with thousands of frames in only a second or two!
You already have an infinite loop running, mainloop. If you want to do animation, use after to run a function every few milliseconds. For example, the following will cause a ball to move 10 pixels every 10th of a second. Of course, you'll want to handle the case where it moves off screen or bounces or whatever. The point is, you write a function that draws one frame of animation, and then have that function be called periodically.
def animate():
canvas.move("ball", 10, 0)
canvas.after(100, animate)

Resources