This question already has answers here:
Moving balls in Tkinter Canvas
(4 answers)
Closed 5 years ago.
I am trying to animate a circle that moves over time but i do not know how to use .after() on Canvas to add a delay before the circle changes position each time. Does anyone know how to do this?
Thanks.
my code (i have put a .sleep() where i would like the delay to be):
from tkinter import *
import time
root = Tk()
c = Canvas(root, width = 500, height = 500)
c.pack()
oval = c.create_oval(0, 0, 0, 0)
for x in range(2, 50, 5):
time.sleep(0.1)
c.delete(oval)
oval = c.create_oval(x+50, x+50, x+50, x+50)
Here's a simple example of using .after, derived from your code. As you can see, the .after method needs to be supplied a function to call after the specified delay, so you need to wrap your changes in a function.
import tkinter as tk
root = tk.Tk()
canvas = tk.Canvas(root, width = 500, height = 500)
canvas.pack()
radius = 10
bbox = (-radius, -radius, radius, radius)
oval = canvas.create_oval(*bbox)
def move_oval():
canvas.move(oval, 1, 1)
canvas.after(20, move_oval)
# Start moving!
move_oval()
root.mainloop()
If you'd like to see a more complex example, take a look at tk_orbit.py.
Related
Why does the time.sleep() work before the window of tkinter opens?
Code:
import tkinter
import time
window = tkinter.Tk()
window.geometry("500x500")
window.title("Holst")
holst = tkinter.Canvas(window, width = 450, height = 450, bg = "white")
holst.place(x = 25, y = 25)
x = 30
y = 50
d = 30
circle = holst.create_oval(x, y, x+d, y+d, fill = "red")
time.sleep(2)
holst.move(circle, 50, 40)
You asked that why is time.sleep() is called before the windows loads
because you hopefully called the window.mainloop() at the last of the code which loads the window and keep maintain the tkinter window
The time.sleep() function code executes before the window.mainloop() function so it was stop the execution and sleeps before the window could load
A nice approach will be to call the time.sleep() in a if statement.
The Tk instance requires you to run it's mainloop function in order to take control of the main process thread.
Your code is calling time.sleep() on the main process thread, which blocks the GUI from doing anything. If you want to be able to do things with the GUI while waiting (such as drawing the window, moving it around, or drawing other things to it) then you would need to extend Tk to have the UI handle the callback using self.after()
Here's a simple example of how you would extend the Tk class to achieve what you want.
import tkinter
class TkInstance(tkinter.Tk):
def __init__(self):
tkinter.Tk.__init__(self)
#Set up the UI here
self.canvas = tkinter.Canvas(self, width = 450, height = 450, bg = "white")
self.canvas.place(x = 25, y = 25) #Draw the canvas widget
#Tell the UI to call the MoveCircle function after 2 seconds
self.after(2000, self.MoveCircle) #in ms
def MoveCircle(self):
x = 30
y = 50
d = 30
circle = self.canvas.create_oval(x, y, x+d, y+d, fill = "red")
self.canvas.move(circle, 50, 40) #Draw the circle
#Main entrance point
if __name__ == "__main__": #good practice with tkinter to check this
instance = TkInstance()
instance.mainloop()
I have a program where I need to move an image object every time the mainloop() loops. I haven't tried doing much, mostly because I don't know where to start. I made a dummy version of my project that simulates the issue I'm having.
from tkinter import *
window = tk.Tk()
window.geometry('%ix%i+400+0' % (500, 600))
canvas = Canvas(window, width=500, height=600, bg='white')
canvas.pack()
w, x, y, z = 300, 300, 200, 200
x = canvas.create_rectangle(w, x, y, z)
def moveRectangle():
canvas.move(x, 10, 0)
# Run the moveRectangle function everytime the mainloop loops
window.mainloop()
To sum up my issue, I need to run mainloop as if it isn't a blocking function. Rather, either run it asynchronous, or maybe pause it and then run the function, though I don't think that's possible.
Anything helps
Thanks
Mainloop in tkinter doesn't loop through your code. It's looping through list of events. You can create an event by clicking buttons etc. Another way is that you can call commands like update() or update_idletasks(). Combinig that with after() can give you results you are looking for. So look up these in documentation, it will be helpful. Also you can read: understanding mainloop.
def moveRectangle():
canvas.move(x, 10, 0)
for i in range(20): # 20 moves 10px every 50ms
window.after(50, canvas.move(x, 10, 0))
window.update()
moveRectangle()
This little code above demonstrate how you could use mentioned commands to get your object move on screen.
I think there are ways to set up a custom mainloop() which could update your UI every time the loop runs but depending on what you want to do the more usual method is to use after. By arranging for the function called by after to call itself with after an effective loop can be created.
I've amended you code to bounce the rectangle as it reaches the sides of the canvas so it can show something as it runs indefinitely.
import tkinter as tk
WAIT = 10 # in milliseconds, can be zero
window = tk.Tk()
window.geometry('%ix%i+400+0' % (500, 600))
canvas = tk.Canvas(window, width=500, height=600, bg='white')
canvas.pack()
w, x, y, z = 300, 300, 200, 200
x = canvas.create_rectangle(w, x, y, z)
amount = 10
def direction( current ):
x0, y0, x1, y1 = canvas.bbox( x )
if x0 <= 0:
return 10 # Move rect right
elif x1 >= 500:
return -10 # Move rect left
else:
return current
def moveRectangle():
global amount
canvas.move(x, amount, 0)
window.update()
# Change Direction as the rectangle hits the edge of the canvas.
amount = direction( amount )
window.after( WAIT, moveRectangle )
# ms , function
# Loop implemented by moveRectangle calling itself.
window.after( 1000, moveRectangle )
# Wait 1 second before starting to move the rectangle.
window.mainloop()
I want to make a shape move left if I leftclick my mouse , and go right if I click right. The following code is not working. What have I done wrong ?
from tkinter import *
import time
tk =Tk()
canvas = Canvas(tk, width=500, height=500,)
tk.title("Drawing")
xbat = 0
def clickL(event):
xbat =- 1
print(xbat)
def clickR(event):
xbat =+ 1
print(xbat)
canvas.bind("<Button-1>", clickL)
canvas.bind("<Button-3>", clickR)
canvas.pack()
bat = canvas.create_rectangle(150, 500, 300, 480, fill="black")
while True:
canvas.move(bat,xbat,0)
tk.update()
time.sleep(0.01)
canvas.mainloop()
Try this
from tkinter import *
tk =Tk()
canvas = Canvas(tk, width=500, height=500,)
tk.title("Drawing")
def clickL(event):
xbat = -1
print(xbat)
xmove(bat,xbat)
def clickR(event):
xbat = 1
print(xbat)
xmove(bat,xbat)
def xmove(object,x):
canvas.move(object,x,0)
canvas.bind("<Button-1>", clickL)
canvas.bind("<Button-3>", clickR)
canvas.pack()
bat = canvas.create_rectangle(150, 500, 300, 480, fill="black")
canvas.mainloop()
It is typically bad practice to have a while True loop in tkinter code, it doesn't give the GUI time to update. Instead I've changed you code so that each click event calls a separate xmove function.
An alternative approach could be to change the coordinate of the object when the button is clicked then have a periodic update function which re-draws/moves the objects to their update positions. This periodic update could be achieved with the tkinter .after method.
I am attempting to create a simple window that maintains a square shape when resized, using python 3.6.4 and tkinter 8.6. Here is my code that produces a window, but does not maintain its aspect ratio when resized.
import tkinter as tk
w = tk.Tk()
w.aspect(1,1,1,1)
w.mainloop()
maybe you can use a canvas to make that, he detect event (image size changed) and .place relative x and relative y. I tryed make a script to help you, but you need make somes changes
from tkinter import *
# create a canvas with no internal border
canvas = Canvas(bd=0, highlightthickness=0)
canvas.pack(fill=BOTH, expand=1)
lastw, lasth = canvas.winfo_width(), canvas.winfo_height()
# track changes to the canvas size and draw
# a rectangle which fills the visible part of
# the canvas
def configure(event):
global lastw, lasth
canvas.delete("all")
w, h = event.width, event.height
try:
label.config(font = ('Arial ', int(12 * ((w - lastw) / (h - lasth))))) # -- this formula need change :3
except ZeroDivisionError: pass
lastw, lasth = canvas.winfo_width(), canvas.winfo_height()
canvas.bind("<Configure>", configure)
label = Label(canvas, text = "YOLO")
label.place(relx = 0.5, rely = 0.5) # - this make the widget automatic change her pos
mainloop()
you can see how this work here http://effbot.org/zone/tkinter-window-size.htm
I am trying to make a ball go to one side of the screen turn around, then come back. Whenever I try running this program the tkinter window doesn't show up, but when i get rid of the sleep(0.5) part it shows up when the ball is already off the screen. Can someone tell my what I did wrong?
from tkinter import *
from time import sleep
window = Tk()
cWidth = 800
cHeight = 500
c = Canvas(window, width = cWidth, height = cHeight, bg='black')
c.pack()
x = 400
y = 250
ball = c.create_polygon(x, y, x, y+25, x+25, y+25, x+25,y, fill='yellow')
Ball_move = 10
for i in range(200):
c.move(ball, Ball_move, 0)
window.update
x += Ball_move
if x == cWidth:
Ball_move = -Ball_move
sleep(0.5)
window.mainloop()
In windows, Tkinter frame shows up only after you call mainloop(). In your case, the for loop might be blocking it. Keep the for loop in a function and then call that function using threads so that it won't block the main loop.