Canvas.Move doesn't work - python

def moveR(amount):
global x
global y
x = x+amount
can.itemconfig(player, image = playerImageL)
can.move("player", x, y)
root.update()
##SETTING##
can = Canvas(width = 850, height = 550, bg = "black")
can.pack(expand = YES, fill = BOTH)
player = can.create_image(x, y, image = playerImageL, anchor = NW)
root.update()
Hey, i am trying to create a mini game using tkinter and canvas however the move command doesn't work. As you see the SETTING is the setup later in the code i am calling
moveR(100)
however it doesn't work and seems to completly destroy my sprite/image.

Text "player" and variable player are two different things.
Use variable player in move()
can.move(player, x, y)
BTW: you don't need itemconfig()

Please read https://stackoverflow.com/help/mcve. To make the code easily verifiable, use something like a rectangle instead of an image. (Your code fails the same for any item.)
Canvas.move(item, delta_x, delta_y) moves the item a given x and y amount. It does the x + dy calculation itself, so you should not. If you want to move to a given position, use Canvas.coords(item, x0, y0, x1, y1). If the size of the new bounding box is different from what it was, it will also change the shape. The following example uses both methods. As a bonus, it also shows how to use root.after to make repeated changes.
import tkinter as tk
root = tk.Tk()
can = tk.Canvas(root, width=800, height=500)
can.pack()
rec = can.create_rectangle(0, 0, 100, 100, fill='red')
def rmove():
box = can.bbox(rec)
if box[0] < 700:
can.move(rec, 100, 30)
root.after(1000, rmove)
else:
can.coords(rec, 0, 400, 50, 500) # position with new shape
root.after(1000, rmove)
root.mainloop()

Related

Why is canvas.create_rectangle not working when used in tkinter.after()

I am trying to display an overlay using the overlay package which is built on top of tkinter. When I run this process, the output is correct and I see the overlay displays the correct fill colors (i.e. 3 rectangles are green, red, blue)
_overlay = Overlay(
window.data["kCGWindowBounds"]["X"],
window.data["kCGWindowBounds"]["Y"]
)
screen = Screen(
window.data["kCGWindowBounds"]["X"],
window.data["kCGWindowBounds"]["Y"],
window.data["kCGWindowBounds"]["Height"],
window.data["kCGWindowBounds"]["Width"]
)
screen.capture()
pattern = Pattern()
pattern.convertImageToRGB()
pixelOne = pattern.getColorOnePixel()
pixelTwo = pattern.getColorTwoPixel()
_overlay.displayPattern(pixelOne, pixelTwo)
overlay.Window.launch()
...
def displayPattern(self, pixelOne, pixelTwo):
colors = COLOR_ONE_PIXEL_MAP.get(pixelOne, "Undefined") + COLOR_TWO_PIXEL_MAP.get(pixelTwo, "Undefined")
pattern = COLOR_PATTERN_MAP.get(colors, "Undefined")
canvas = tk.Canvas(
self.window.root,
width=162,
height=12,
bd=0,
highlightthickness=0
)
canvas.pack(
padx=0,
pady=0
)
if pattern != "Undefined":
print(pattern)
canvas.create_rectangle(-1, -1, 54, 12, fill=COLOR_FILL_MAP.get(pattern[:2]), outline=COLOR_FILL_MAP.get(pattern[:2]))
canvas.create_rectangle(54, -1, 108, 12, fill=COLOR_FILL_MAP.get(pattern[3] + pattern[4]), outline=COLOR_FILL_MAP.get(pattern[3] + pattern[4]))
canvas.create_rectangle(108, -1, 162, 12, fill=COLOR_FILL_MAP.get(pattern[6] + pattern[7]), outline=COLOR_FILL_MAP.get(pattern[6] + pattern[7]))
self.window.root.update()
Now I need to continue to run this process so that when information on the window that the program is using updates, this function gets run again. So I update this logic to this:
overlay.Window.after(
500,
proceed,
window.data["kCGWindowBounds"]["X"],
window.data["kCGWindowBounds"]["Y"],
window.data["kCGWindowBounds"]["Height"],
window.data["kCGWindowBounds"]["Width"]
)
overlay.Window.launch()
...
def proceed(x, y, height, width):
while True:
screen = Screen(
x,
y,
height,
width
)
screen.capture()
pattern = Pattern()
pattern.convertImageToRGB()
pixelOne = pattern.getColorOnePixel()
pixelTwo = pattern.getColorTwoPixel()
_overlay.displayPattern(pixelOne, pixelTwo)
time.sleep(.5)
I'm seeing the correct output in the print() (i.e. GG RR BB green red blue), but those fill colors are no longer being applied to the canvas.create_rectangle method. Is there something I'm missing with the after() process and packing tkinter canvas elements?
tkinter (like other GUIs) doesn't draw/update elements at once but it waits for end of your function to redraw/update all elements in one moment - this way it has less work and window doesn't blink.
Problem is that your function runs while True so it never ends.
You may use root.update() inside loop to force tkinter to redraw elements
def proceed(x, y, height, width):
while True:
screen = Screen(
x,
y,
height,
width
)
screen.capture()
pattern = Pattern()
pattern.convertImageToRGB()
pixelOne = pattern.getColorOnePixel()
pixelTwo = pattern.getColorTwoPixel()
_overlay.displayPattern(pixelOne, pixelTwo)
overlay.Window.update() # <--- force tkinter to redraw/update window
time.sleep(.5)
OR you can use after(500, process, ...) instead of while True and time.sleep()
def proceed(x, y, height, width):
screen = Screen(
x,
y,
height,
width
)
screen.capture()
pattern = Pattern()
pattern.convertImageToRGB()
pixelOne = pattern.getColorOnePixel()
pixelTwo = pattern.getColorTwoPixel()
_overlay.displayPattern(pixelOne, pixelTwo)
overlay.Window.after(500, process, x, y, height, width) # <--- run again after 500ms

Window shifts with Python Turtle setworldcoordinates()

I've noticed a strange blip with Python Turtle Graphics when using Screen.setworldcoordinates(). For some reason when I click on the window title bar after running the code below, there is a small but perceptible shift of the contents of the window. Can anyone please explain this phenomenon, and let me know if there is way to avoid it? I'm on Windows 10 with Python 3.8.
import turtle
screen = turtle.Screen()
screen.setup(500, 500) # Set the dimensions of the Turtle Graphics window.
screen.setworldcoordinates(0, screen.window_height(), screen.window_width(), 0)
my_turtle = turtle.Turtle(shape="circle")
my_turtle.color("red")
my_turtle.forward(10)
turtle.done()
turtle comes with it's own default config, that uses 50% of your monitor width, and 75% of it's height.
_CFG = {"width" : 0.5, # Screen
"height" : 0.75,
"canvwidth" : 400,
"canvheight": 300,
...
}
There's some interplay between various elements during construction.
self._canvas = TK.Canvas(master, width=width, height=height,
bg=self.bg, relief=TK.SUNKEN, borderwidth=2)
Setting borderwidth to any value above 3 alleviates it; believe that's because it calls for a complete screen redraw.
/usr/lib/python3.9/turtle.py is read-only, though.
Made a local copy with write privs, then modded it for debugging purposes. Named turtel.py instead, just to keep changes seperate. Put in a few print() statements to figure out when functions were running.
Method 1:
You can fix it by forcing new values, before screen construction.
Either by supplying your own turtle.cfg in the same directory as your script, or
Method 2:
overriding those values:
import turtle
Width, Height = 500, 500
turtle._CFG['canvwidth'], turtle._CFG['canvheight'] = Width, Height
screen = turtle.Screen()
Method 3:
_setscrollregion(self, srx1, sry1, srx2, sry2) calls _rescale(self, xscalefactor, yscalefactor)
but doesn't call adjustScrolls(self) until onResize(self, event) is called, after you drag the titlebar.
So you can force a redraw after screen.setworldcoordinates(0, Height, Width, 0)
cv = screen.getcanvas()
cv.adjustScrolls()
Uncomment ## to enable lines, and try.
#! /usr/bin/python3
import turtle
## print( turtle._CFG['width'], turtle._CFG['height'] ) ## 0.5 0.75
## print( turtle._CFG['canvwidth'], turtle._CFG['canvheight'] ) ## 400 300
Width, Height = 500, 500
## turtle._CFG['width'], turtle._CFG['height'] = Width, Height
## turtle._CFG['canvwidth'], turtle._CFG['canvheight'] = Width, Height
screen = turtle.Screen()
screen.setworldcoordinates(0, Height, Width, 0)
## cv = screen.getcanvas()
## cv.adjustScrolls()
my_turtle = turtle.Turtle(shape='circle')
my_turtle.color('red')
my_turtle.forward(10)
def click_callback( x, y ):
cv = screen.getcanvas()
print( cv.width, cv.height, screen.screensize(), cv.winfo_width(), cv.winfo_height() )
print('<< initialized >>')
screen.onclick( click_callback )
screen.mainloop()

How to make an object move constantly in tkinter [duplicate]

This is a very basic program with which I want to make two moving balls, but only one of them actually moves.
I have tried some variations as well but can't get the second ball moving; another related question - some people use the move(object) method to achieve this, while others do a delete(object) and then redraw it. Which one should I use and why?
This is my code that is only animating/moving one ball:
from Tkinter import *
class Ball:
def __init__(self, canvas, x1, y1, x2, y2):
self.x1 = x1
self.y1 = y1
self.x2 = x2
self.y2 = y2
self.canvas = canvas
self.ball = canvas.create_oval(self.x1, self.y1, self.x2, self.y2, fill="red")
def move_ball(self):
while True:
self.canvas.move(self.ball, 2, 1)
self.canvas.after(20)
self.canvas.update()
# initialize root Window and canvas
root = Tk()
root.title("Balls")
root.resizable(False,False)
canvas = Canvas(root, width = 300, height = 300)
canvas.pack()
# create two ball objects and animate them
ball1 = Ball(canvas, 10, 10, 30, 30)
ball2 = Ball(canvas, 60, 60, 80, 80)
ball1.move_ball()
ball2.move_ball()
root.mainloop()
You should never put an infinite loop inside a GUI program -- there's already an infinite loop running. If you want your balls to move independently, simply take out the loop and have the move_ball method put a new call to itself on the event loop. With that, your balls will continue to move forever (which means you should put some sort of check in there to prevent that from happening)
I've modified your program slightly by removing the infinite loop, slowing down the animation a bit, and also using random values for the direction they move. All of that changes are inside the move_ball method.
from Tkinter import *
from random import randint
class Ball:
def __init__(self, canvas, x1, y1, x2, y2):
self.x1 = x1
self.y1 = y1
self.x2 = x2
self.y2 = y2
self.canvas = canvas
self.ball = canvas.create_oval(self.x1, self.y1, self.x2, self.y2, fill="red")
def move_ball(self):
deltax = randint(0,5)
deltay = randint(0,5)
self.canvas.move(self.ball, deltax, deltay)
self.canvas.after(50, self.move_ball)
# initialize root Window and canvas
root = Tk()
root.title("Balls")
root.resizable(False,False)
canvas = Canvas(root, width = 300, height = 300)
canvas.pack()
# create two ball objects and animate them
ball1 = Ball(canvas, 10, 10, 30, 30)
ball2 = Ball(canvas, 60, 60, 80, 80)
ball1.move_ball()
ball2.move_ball()
root.mainloop()
This function seems to be the culprit
def move_ball(self):
while True:
self.canvas.move(self.ball, 2, 1)
self.canvas.after(20)
self.canvas.update()
You deliberately put yourself in an infinite loop when you call it.
ball1.move_ball() # gets called, enters infinite loop
ball2.move_ball() # never gets called, because code is stuck one line above
Its only moving one because the program reads only one variable at a time. If you set the program to read when the ball gets to a certain spot, say the end of the canvas, you could then code the program to read the next line and trigger the second ball to move. But, this will only move one at a time.
Your program is literally stuck on the line:
ball1.move_ball()
And it will never get to line:
ball2.move_ball()
Because there isn't a limit to where the loop should end.
Otherwise, the answer by "sundar nataraj" will do it.
try
instead of self.canvas.move(self.ball, 2, 1) use
self.canvas.move(ALL, 2, 1)
All this used to move all the objects in canvas

python 3 tkinter: how to add .after() to graphics

i am trying to make a circle that slowly gets bigger and to do so i need to add a delay. I tried using time.sleep() but found out that won't work and that i need to use .after(). i have tried making a function that makes the circle but that didn't work. i have tried adding a lambda before the oval is created but that didn't work either. is there anyway i can add a delay before the new circle is made?
Thanks.
My code:-
from tkinter import *
root = Tk()
c = Canvas(root, width = 500, height = 500)
c.pack()
oval = c.create_oval(0, 0, 1, 1)
for x in range(2, 200, 5):
c.delete(oval)
root.after(100, oval = c.create_oval(0, 0, x, x))
root.after() takes a ms delay and a function to run after that delay. What you are trying to do is after that delay run a function with a parameter which is x (the radius of the circle). The problem with this is that you cannot ask it to run a function with an input.
To get around this, you could use an update function and then global variables for the radius of the circle etc.
This worked for me:
from tkinter import *
root = Tk()
c = Canvas(root, width = 500, height = 500, highlightthickness = 0)
c.pack()
oval = c.create_oval(0, 0, 1, 1)
r = 2
def update():
global r, oval
if x < 200:
x += 5
c.delete(oval)
oval = c.create_oval(0, 0, r, r)
root.after(100, update)
update()
root.mainloop()
I also added the option highlightthickness = 0 to your canvas because otherwise the coordinate system does not match up and your circles will overflow out of the canvas.

Remove canvas widgets in Tkinter

from Tkinter import *
import random
root = Tk()
width = 700
height = 600
canvas = Canvas(root, width = width, height = height, bg = "light blue")
canvas.pack()
pipes = []
class NewPipe:
def __init__(self, pipe_pos, pipe_hole):
self.pipe_pos = list(pipe_pos)
def update(self):
self.pipe_pos[0] -= 3
self.pipe_pos[2] -= 3
def draw(self):
canvas.create_rectangle(self.pipe_pos, fill = "green")
def get_pos(self):
return self.pipe_pos
def generate_pipe():
pipe_hole = random.randrange(0, height)
pipe_pos = [width - 100, 0, width, pipe_hole]
pipes.append(NewPipe(pipe_pos, pipe_hole))
draw_items()
canvas.after(2000, generate_pipe)
def draw_items():
for pipe in pipes:
if pipe.get_pos()[2] <= 0 - 5:
pipes.remove(pipe)
else:
pipe.draw()
pipe.update()
canvas.after(100, draw_items)
def jump(press):
pass
canvas.bind("<Button-1>", jump)
canvas.after(2000, generate_pipe)
draw_items()
mainloop()
Right now I am trying to make a game where you have to dodge rectangles, which are pipes. It is basically Flappy Bird, but on Tkinter. In this code I am trying to generate pipes and move them, but the pipes I have drawn before do not leave and they just stay there. This means that when the pipe moves, the position it was just in doesnt change and that shape stays there. Is there any way to delete past shapes, or another way to move them?
canvas.create_rectangle(self.pipe_pos, fill = "green") returns an ID.
You can use this ID to put it into methods like
canvas.coords
canvas.delete
canvas.itemconfigure
canvas.scale
canvas.type
...
Have a look at help(canvas).
The canvas is not a framebuffer on which you paint stuff for one frame. The painted stuff does not go away and you can move it and change all the parameters you can use when creating.

Categories