Move objects automatically - python

The objective of my exercise is to create a game in which a falling ball needs to be caught by a bar at the bottom of the screen. Below code does not make the ball to fall automatically. I referred to below posts, but could not find a solution:
Tkinter bind to arc, Automatically Moving Shape? Python 3.5 Tkinter
import tkinter as tk
class Game(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.can = tk.Canvas(self, width=400, height=400)
self.can.pack(fill="both", expand=True)
self.ball = self.can.create_oval(40, 40, 60, 60, fill="red", tag="ball")
self.player = self.can.create_rectangle(300,345,350,360, fill="red")
self.bind("<Key>", self.move_player)
self.can.tag_bind("ball",self.move_b)
self.mainloop()
def move_b(self,event=None):
self.can.move(self.ball, 1, 0)
print(self.ball)
# move again after 25ms (0.025s)
self.can.after(25, self.move_b)
def move_player(self, event):
key = event.keysym
if key == "Left":
self.can.move(self.player, -20, 0)
elif key == "Right":
self.can.move(self.player, 20, 0)
if __name__ == '__main__':
Game()

2nd positional argument to tag_bind is an event, whereas in your code it's passed as the actual callback, self.move_b. First, add an event:
self.can.tag_bind("ball", "<ButtonRelease-1>", self.move_b)
If you don't want it to have event, simply pass None:
self.can.tag_bind("ball", None, self.move_b)
or don't use tag_bind at all, and simply call:
self.move_b()
Whenever you want the animation to start.

Related

TypeError: Missing one required positional argument 'event'

Im making a paint program and I keep running into this error
however if I change where event.x /event.y are defined the same error comes up but with a different subroutine
here is the code:
class paint:
def __init__(self,root,canvas):
self.root = root
self.mouse_press = False #mouse isnt being pressed
self.canvas = canvas
self.canvas.bind('<ButtonPress-1>',self.MouseDown) # when left click is pressed / down the subroutine MouseDown is opened
self.canvas.bind('<ButtonRelease-1>',self.MouseUp)#when left click is relased open the MouseUp subroutine
def MouseDown(self,event):
self.x = event.x
self.y = event.y
self.mouse_press = True # mouse is pressed
self.poll() #calls the coodinates subroutine
def MouseUp(self,event):
self.poll()
self.mouse_press = False
self.root.after_cancel(self.after_id)
def poll(self):
if self.mouse_press:
canvas.create_oval(self.x,self.y, self.x+10, self.y+10 , fill = '#f6f65a',outline = '#f6f65a')
self.after_id = self.root.after(10,self.MouseDown)
def colour_pick():
colour_choose = colorchooser.askcolor()[1] #opens a colour picker and picks the colour
print(colour_choose)
return(colour_choose)
how I am calling the class
p = paint(root,canvas)
root.mainloop()
the error I keep getting:
Traceback (most recent call last):
File "C:\Users\user\AppData\Local\Programs\Python\Python310\lib\tkinter\__init__.py", line 1921, in __call__
return self.func(*args)
File "C:\Users\user\AppData\Local\Programs\Python\Python310\lib\tkinter\__init__.py", line 839, in callit
func(*args)
TypeError: paint.MouseDown missing 1 required positional argument: 'event'
Your function MouseDown requires an event parameter since you defined it like this:
def MouseDown(self,event):
However, when you call the function from after, you're not passing this argument:
self.after_id = self.root.after(10,self.MouseDown)
Since MouseDown uses the data in the event object, you will need to synthesize an object with the attributes used by the function (ie: .x and .y) or you need to rewrite MouseDown so that it doesn't require an event parameter.
The cause of the error has been explained by Bryan's answer.
For your case, binding on events <Button-1> and <B1-Motion> is enough and don't need to use .after():
class paint:
def __init__(self, root, canvas):
self.root = root
self.canvas = canvas
self.canvas.bind('<Button-1>', self.mouse_down)
self.canvas.bind('<B1-Motion>', self.poll)
def mouse_down(self, event):
# save initial drag point
self.x, self.y = event.x, event.y
def poll(self, event):
# draw line from last saved point to current point instead of drawing circle
self.canvas.create_line(self.x, self.y, event.x, event.y, fill='#f6f65a', width=10, capstyle='round')
# save current point
self.x, self.y = event.x, event.y
You are working too hard in trying to fake <Motion>. The below illustrates what I believe you are actually trying to do.
import tkinter as tk
class Canvas(tk.Canvas):
def __init__(self, master, **kwargs):
tk.Canvas.__init__(self, master, **{'background':'#000000', 'highlightthickness':0, **kwargs})
self.bind('<Motion>', self.motion)
#instead of attempting to programmatically toggle up and down states
def motion(self, e):
if (e.state & 256) == 256: #if mouse 1 is down
self.create_oval(e.x, e.y, e.x+10, e.y+10, fill='#f6f65a', outline='#f6f65a') #fill and outline should actually come from your ColorChooser
class Root(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.paint = Canvas(self)
self.paint.pack(expand=True, fill=tk.BOTH)
if __name__ == '__main__':
Root().mainloop()

Monopoly python game using canvas player movement error

I am creating an online monopoly game using canvas and tkinter and having trouble trying to make the "player1.png" .ie my character to move across the board. Please help!
class Example(Frame):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.master.title("Monopoly Physics Edition")
self.pack(fill=BOTH, expand=1)
canvas = Canvas(self)
global player1
load= Image.open("player1.png")
player1 = ImageTk.PhotoImage(load)
img = Label(image=player1)
img.place(x=834, y=60)
def main():
canvas = Tk()
ex = Example()
canvas.geometry("1150x820+800+700")
if __name__ == '__main__':
main()
Ok so I programmed in the basics here:
import tkinter as tk
from PIL import Image, ImageTk
class Player:
def __init__(self, canvas, sprite, pos):
self.posx, self.posy = pos
self.canvas = canvas
self.sprite = sprite
self.tk_image = None
self.canvas_id = None
def display(self):
# This will display the train card on the board
self.load_sprite()
# If we already displayed the object hide it first
if self.canvas_id is not None:
self.hide_from_canvas()
# We need to create a new canvas object and get its id
# so that later we can move/remove it from the canvas
self.canvas_id = self.canvas.create_image(self.posx, self.posy, image=self.tk_image)
def hide_from_canvas(self):
# This will remove the sprite from the canvas
self.canvas.delete(self.canvas_id)
def load_sprite(self):
# If we already loaded the sprite just don't do anything
if self.tk_image is not None:
return None
# Later you can determine the filename from `self.name`
filename = "img_small.png"
pillow_image = Image.open(filename)
# You must keep a reference to this `tk_image`
# Otherwise the image would show up on the board
self.tk_image = ImageTk.PhotoImage(pillow_image)
def move(self, change_x, change_y):
# Move the object by change_x, change_y
self.canvas.move(self.canvas_id, change_x, change_y)
class Property:
def __init__(self, canvas, name, cost, pos):
self.posx, self.posy = pos
self.canvas = canvas
self.name = name
self.cost = cost
self.tk_image = None
self.canvas_id = None
def display(self):
# This will display the train card on the board
self.load_sprite()
# If we already displayed the object hide it first
if self.canvas_id is not None:
self.hide_from_canvas()
# We need to create a new canvas object and get its id
# so that later we can move/remove it from the canvas
self.canvas_id = self.canvas.create_image(self.posx, self.posy, image=self.tk_image)
def hide_from_canvas(self):
# This will remove the sprite from the canvas
self.canvas.delete(self.canvas_id)
def load_sprite(self):
# If we already loaded the sprite just don't do anything
if self.tk_image is not None:
return None
# Later you can determine the filename from `self.name`
filename = "img_small.png"
pillow_image = Image.open(filename)
# You must keep a reference to this `tk_image`
# Otherwise the image would show up on the board
self.tk_image = ImageTk.PhotoImage(pillow_image)
def move(self, change_x, change_y):
# Move the object by change_x, change_y
self.canvas.move(self.canvas_id, change_x, change_y)
class App(tk.Canvas):
def __init__(self, master, **kwargs):
# Create the canvas
super().__init__(master, **kwargs)
# lets show all of the monopoly properties on the screen
self.init()
# I will also bind to key presses (You might want to use them later)
master.bind("<Key>", self.on_key_press)
# I will also bind to mouse presses
super().bind("<Button-1>", self.on_mouse_press)
def on_key_press(self, event):
print("You pressed this character:", repr(event.char))
print("For non character keys (like Escape):", repr(event.keysym))
# For now I will move the player sprite by 10 pixels to the right
# when "d" or the right key is pressed
if (event.char == "d") or (event.keysym == "Right"):
# Move the first player of list
self.players[0].move(10, 0)
def on_mouse_press(self, event):
print("You clicked with the mouse at this position:", (event.x, event.y))
def init(self):
# A list that will hold all of the properties:
self.properties = []
# A list that will hold all of the players playing
self.players = []
# I will only create 1 property (You can add the rest)
# I will create 1 of the trains where the cost is 200 and
# its position is (50, 50). Note (0, 0) is the top left
# corner of the canvas
train1 = Property(self, "Train", 200, (50, 50))
train1.display()
self.properties.append(train1)
# I will also create a player at position (100, 100)
player1 = Player(self, "player_sprite1", (100, 100))
player1.display()
self.players.append(player1)
# Create the window
root = tk.Tk()
# Make it so that the user can't resize the window
# Otherwise you will have a lot of problems with your images not being in
# the correct places
root.resizable(False, False)
# Create the App, I also passed in `width=200, height=200` those are in pixels
main_app = App(root, width=200, height=200)
# Show the App on the screen:
main_app.pack()
# Enter tkinter's mainloop
root.mainloop()
It allows us to move/hide/display any of the sprites we create. I also added a way for use to detect key/mouse presses (might come in handy later). Using this it shouldn't be hard to create the full program. Note that most of the methods in the Player and Property class are the same so if you know how to use inheritance, you should be able to simplify my code by a lot.

Delete a created rectangle in canvas by right-clicking in tkinter python

I'm creating two rectangles. I want to delete rectangles from the canvas by right-clicking. The code is able to delete only 1 rectangle but not the other one. I used tag_bind("Button1") function but only bottom one is getting deleted.
Tag_bind function should be able to get the id and delete any of the selected rectangles but it is not happening.
#import sys, os, string, time
import tkinter
tk = tkinter
root =tk.Tk()
root.title ("Drag-N-Drop Demo")
# A Python example of drag and drop functionality within a single Tk widget.
# The trick is in the bindings and event handler functions.
# Tom Vrankar twv at ici.net
canvas =tk.Canvas ( width =256, height =256,
relief =tk.RIDGE, background ="white", borderwidth =1)
class CanvasDnD (tk.Frame):
def __init__ (self, master):
self.master =master
self.loc =self.dragged =0
tk.Frame.__init__ (self, master)
id=canvas.create_rectangle(75,75,100,100,tags="DnD")
canvas.tag_bind(id,"<ButtonPress-1>")
id=canvas.create_rectangle(100,100,125,125,tags="DnD")
canvas.tag_bind(id,"<ButtonPress-1>")
canvas.pack (expand =1, fill =tk.BOTH)
canvas.tag_bind ("DnD", "<ButtonPress-1>", self.down)
canvas.tag_bind ("DnD", "<ButtonRelease-1>", self.chkup)
canvas.tag_bind ("DnD", "<Enter>", self.enter)
canvas.tag_bind ("DnD", "<Leave>", self.leave)
self.popup = tk.Menu(root, tearoff=0)
self.popup.add_command(label="delete",command=lambda: self.dele(id))
root.bind("<Button-3>", self.do_popup)
def do_popup(self,event):
# display the popup menu
try:
self.popup.tk_popup(event.x_root, event.y_root, 0)
finally:
# make sure to release the grab (Tk 8.0a1 only)
self.popup.grab_release()
# empirical events between dropee and target, as determined from Tk 8.0
# down.
# leave.
# up, leave, enter.
def down (self, event):
#print ("Click on %s" %event.widget.itemcget (tk.CURRENT, "text"))
self.loc =1
self.dragged =0
event.widget.bind ("<Motion>", self.motion)
def motion (self, event):
root.config (cursor ="exchange")
cnv = event.widget
cnv.itemconfigure (tk.CURRENT, fill ="blue")
x,y = cnv.canvasx(event.x), cnv.canvasy(event.y)
a,b = cnv.canvasx(event.x + 25), cnv.canvasy(event.y+25)
got = event.widget.coords (tk.CURRENT, x, y, a, b)
def leave (self, event):
self.loc =0
def enter (self, event):
self.loc =1
if self.dragged ==event.time:
self.up (event)
def chkup (self, event):
event.widget.unbind ("<Motion>")
root.config (cursor ="")
self.target =event.widget.find_withtag (tk.CURRENT)
#event.widget.itemconfigure (tk.CURRENT, fill =self.defaultcolor)
if self.loc: # is button released in same widget as pressed?
self.up (event)
else:
self.dragged =event.time
def up (self, event):
event.widget.unbind ("<Motion>")
if (self.target ==event.widget.find_withtag (tk.CURRENT)):
print("1")
# print ("Select %s" %event.widget.itemcget (tk.CURRENT, "text"))
else:
event.widget.itemconfigure (tk.CURRENT, fill ="blue")
self.master.update()
time.sleep (.1)
print ("%s Drag-N-Dropped onto %s" \
%(event.widget.itemcget (self.target, "text")),
event.widget.itemcget (tk.CURRENT, "text"))
event.widget.itemconfigure (tk.CURRENT, fill =self.defaultcolor)
def dele(self,id):
canvas.delete(id)
CanvasDnD (root).pack()
root.mainloop()
Question: first I will select that rectangle with "Button 1" and then I will right-click and delete
Create the rectangles ...
canvas.create_rectangle(75, 75, 100, 100, tags="DnD")
canvas.create_rectangle(100, 100, 125, 125, tags="DnD")
Bind event "<ButtonPress-1>" to the Canvas
canvas.bind("<ButtonPress-1>", self.on_button_1)
Prepare the popup, to delete items with tag='DELETE'
self.popup.add_command(label="delete",
command=lambda: canvas.delete(canvas.find_withtag('DELETE')))
Define the event "<ButtonPress-1>" callback.
Here, the matching item get added tags='DELETE' and outlined 'red'.
def on_button_1(self, event):
iid = canvas.find_enclosed(event.x - 26, event.y - 26, event.x + 26, event.y + 26)
canvas.itemconfigure(iid, tags='DELETE', outline='red')

Bind a tkinter event generated in a class outside of class

I am working on a program that would move a ball around depending on joystick inputs. The GUI I am using is Tkinter.
Recently, I found a demo of a method that used Tkinter and was compatible with Pygame (Prior to this, I thought that Pygame was incompatible with Tkinter. I was psyched when I could found this.)
I defined the virtual event "LT" in the class Find_Joystick. ("LT" refers to the "LT" button on a gamepad.) When pressed, it's supposed to move the ball to the left. However, when I try to bind the event to the actual movement function, the function doesn't appear to receive the input.
Here is my code (somewhat simplified):
from tkinter import *
import pygame
class Find_Joystick:
def __init__(self, root):
self.root = root
## initialize pygame and joystick
pygame.init()
if(pygame.joystick.get_count() < 1):
# no joysticks found
print("Please connect a joystick.\n")
self.quit()
else:
# create a new joystick object from
# ---the first joystick in the list of joysticks
joystick = pygame.joystick.Joystick(0)
# tell pygame to record joystick events
joystick.init()
## start looking for events
self.root.after(0, self.find_events)
def find_events(self):
## check everything in the queue of pygame events
events = pygame.event.get()
joystick = pygame.joystick.Joystick(0)
LT = joystick.get_button(6)
for event in events:
# event type for pressing any of the joystick buttons down
if event.type == pygame.JOYBUTTONDOWN:
self.root.event_generate('<<JoyFoo>>')
if LT == 1:
self.root.event_generate('<<LT>>')
if event.type == pygame.JOYAXISMOTION:
self.root.event_generate('<<JoyMove>>')
#Move left
if axisX < 0:
self.root.event_generate('<<Left>>')
#Move right
if axisX > 0:
self.root.event_generate('<<Right>>')
#Move upwards
if axisY < -0.008:
self.root.event_generate('<<Up>>')
#Move downwards
if axisY > -0.008:
self.root.event_generate('<<Down>>')
## return to check for more events in a moment
self.root.after(20, self.find_events)
def main():
## Tkinter initialization
root = Tk()
app = Find_Joystick(root)
frame = Canvas(root, width=500, height = 250)
frame.pack()
ball = frame.create_oval([245, 120], [255, 130], outline = 'red', fill = 'red')
def callback(event):
frame.focus_set()
def moveLeftFunc(event):
frame.move(ball, -3, 0)
print('Move left')
frame.bind('<Button-1>', callback)
frame.bind('<<LT>>', moveLeftFunc)
root.mainloop()
if __name__ == "__main__":
main()
Here is the demo I was referring to earlier (the class is the main part I am using).
The solution might be in the event binding.
You bind the events to the frame Canvas, and then events are generated by the root widget (self.root.event_generate('<<LT>>'))
It could work in the other way, the event are propagated from the widget to the parent, but not from the root to the children widgets.
Can you try with :
root.bind('<Button-1>', callback)
root.bind('<<LT>>', moveLeftFunc)
(you should also remove the line self.root.event_generate('<<JoyFoo>>'))
EDIT :
Here is a reproducible case without the joystick stuff:
(the child button propagates the event to close the window, but the event triggered by the root timer doesn't hit the frame binding to close)
from tkinter import *
import sys
def quit(*arg):
sys.exit()
class ChildFrame(Frame):
def __init__(self, root):
Frame.__init__(self, root)
self.grid()
button = Button(self, text="Propagate Quit", command=self.child_event)
button.pack()
def child_event(self):
self.event_generate('<<Propagated>>') # triggers the root binding
root = Tk()
frame = ChildFrame(root)
def root_event(*arg):
global root
root.event_generate('<<NotPropagated>>') # doesn't trigger child binding
#root.event_generate('<<Propagated>>') # triggers the root binding
root.bind('<<Propagated>>', quit)
frame.bind('<<NotPropagated>>', quit)
root.after(2000, root_event) # timer to create event from root to child
root.mainloop()

Tkinter — executing functions over time

I'm trying to figure out how the tkinter control flow works.
I want to display a rectangle and to make it blink three times. I wrote this code, but it doesn't work. I guess it's because blink is executed before mainloop, and it doesn't actually draw anything. If so, how can I swap the control flow between blink and mainloop to make it work?
My code:
from tkinter import *
from time import *
def blink(rectangle, canvas):
for i in range(3):
canvas.itemconfigure(rectangle, fill = "red")
sleep(1)
canvas.itemconfigure(rectangle, fill = "white")
sleep(1)
root = Tk()
fr = Frame(root)
fr.pack()
canv = Canvas(fr, height = 100, width = 100)
canv.pack()
rect = canv.create_rectangle(25, 25, 75, 75, fill = "white")
blink(rect, canv)
root.mainloop()
Event-driven programming requires a different mindset from procedural code. Your application is running in an infinite loop, pulling events off of a queue and processing them. To do animation, all you need to do is place items on that queue at an appropriate time.
Tkinter widgets have a method named after which lets you schedule functions to run after a certain period of time. The first step is to write a function that does one "frame" of your animation. In your case, you're defining animation as switching between two colors. A function that checks the current color, then switches to the other color is all you need:
def blink(rect, canvas):
current_color = canvas.itemcget(rect, "fill")
new_color = "red" if current_color == "white" else "white"
canvas.itemconfigure(rect, fill=new_color)
Now, we just need to have that function run three times at one second intervals:
root.after(1000, blink, rect, canv)
root.after(2000, blink, rect, canv)
root.after(3000, blink, rect, canv)
When you start your main loop, after one second the color will change, after another second it will change again, and after a third second it will change again.
That works for your very specific need, but that's not a very good general solution. A more general solution is to call blink once, and then have blink call itself again after some time period. blink then must be responsible to know when to stop blinking. You can set a flag or counter of some sort to keep track of how many times you've blinked. For example:
def blink(rect, canvas):
...
# call this function again in a second to
# blink forever. If you don't want to blink
# forever, use some sort of flag or computation
# to decide whether to call blink again
canvas.after(1000, blink, rect, canvas)
As a final bit of advice, I recommend that you define your program as a class, then create an instance of that class. This makes it so that you don't need global functions, and you don't need to pass around so many arguments. It doesn't really matter for a 20 line program, but it starts to matter when you want to write something substantial.
For example:
from tkinter import *
class MyApp(Tk):
def __init__(self):
Tk.__init__(self)
fr = Frame(self)
fr.pack()
self.canvas = Canvas(fr, height = 100, width = 100)
self.canvas.pack()
self.rect = self.canvas.create_rectangle(25, 25, 75, 75, fill = "white")
self.do_blink = False
start_button = Button(self, text="start blinking",
command=self.start_blinking)
stop_button = Button(self, text="stop blinking",
command=self.stop_blinking)
start_button.pack()
stop_button.pack()
def start_blinking(self):
self.do_blink = True
self.blink()
def stop_blinking(self):
self.do_blink = False
def blink(self):
if self.do_blink:
current_color = self.canvas.itemcget(self.rect, "fill")
new_color = "red" if current_color == "white" else "white"
self.canvas.itemconfigure(self.rect, fill=new_color)
self.after(1000, self.blink)
if __name__ == "__main__":
root = MyApp()
root.mainloop()
Each widget has an 'after' function - that is to say it can call a another function after a specified time period - So, what you would want to do is call:
root.after( 1000, blink )
If you want it to be a repeating call, just call 'after' again inside your blink function. The only problem you will have is passing arguments to blink - maybe look at using lamda inside of 'after' for that.

Categories