Tkinter and UnboundLocalError - python

I am trying to get the coordinates for each square on the board but the error UnboundLocalError: local variable 'x' referenced before assignment keeps showing up when I click on a square.
import tkinter
class RA:
def __init__(self):
self._columns = 8
self._rows = 8
self._root = tkinter.Tk()
self._canvas = tkinter.Canvas(master = self._root,
height = 500, width = 500,
background = 'green')
self._canvas.pack(fill = tkinter.BOTH, expand = True)
self._canvas.bind('<Configure>',self.draw_handler)
def run(self):
self._root.mainloop()
def draw(self):
self._canvas.create_rectangle(0,0,250,250,fill = 'blue',outline = 'white')
self._canvas.create_rectangle(250,250,499,499,fill = 'red',outline = 'white')
self._canvas.create_rectangle(499,499,250,250,fill = 'black',outline = 'white')
#
for c in range(self._columns):
for r in range(self._rows):
x1 = c * (column_width)#the width of the column
y1 = r * (row_height)
x2 = x1 + (column_width)
y2 = y1 + (row_height)
#3-5
def clicked(self,event: tkinter.Event):
x = event * x
y = event * y
rect = self._canvas.find_closest(x,y)[0]
coordinates = self._canvas.gettags(rect)
print(coordinates[0],coordinates[1])
def draw(self):
self._canvas.delete(tkinter.ALL)
column_width = self._canvas.winfo_width()/self._columns
row_height = self._canvas.winfo_height()/self._rows
for x in range(self._columns):
for y in range(self._rows):
x1 = x * column_width
y1 = y * row_height
x2 = x1 + column_width
y2 = y1 + row_height
r = self._canvas.create_rectangle(x1,y1,x2,y2,fill = 'blue')#,tag = (x,y))# added for the second time,
self._canvas.tag_bind(r,'<ButtonPress-1>',self.clicked)
self._canvas.create_rectangle(x1,y1,x2,y2)
self._canvas.bind('<Configure>',self.draw_handler)
def draw_handler(self,event):
self.draw()
r = RA()
r.run()

The problem is in these two lines:
def clicked(self,event: tkinter.Event):
x = event * x
You're using x on the right hand side of the expression, but x hasn't been defined anywhere. Also, you're going to have a problem because event is an object. Multiplying the object times some other number isn't likely going to do what you think it is going to do. Did you maybe intend to use event.x in the expression instead of event * x?
Getting the coordinates of the clicked item
Even though you specifically asked about the unbound local variable error, it appears you're attempting to get the coordinates of the item that was clicked on. Tkinter automatically gives the item that was clicked on the tag "current", which you can use to get the coordinates:
coordinates = self._canvas.coords("current")
From the official tk documentation:
The tag current is managed automatically by Tk; it applies to the
current item, which is the topmost item whose drawn area covers the
position of the mouse cursor (different item types interpret this in
varying ways; see the individual item type documentation for details).
If the mouse is not in the canvas widget or is not over an item, then
no item has the current tag.

This will happen with y as well. If you want x to be stored with the button, you'll need to create a Class for it, and store them as self.x and self.y. The clicked() event would be on the class itself, and then it will have access to it.

Looks like you used
x = event * x
y = event * y
when you probably wanted
x = event.x
y = event.y
The latter accesses the event object's x and y attributes instead of attempting to multiply it by an unbound variable.

Related

How to show this message when mouse stop

I want use python3 (tkinter) to finish this function:
When the mouse stop at some place, one message will be shown.
When the mouse move away, the message will disappear.
Is there any information for this function?
This effect can be achieved by combining bind and after.
The motion of cursor is tracked with bind which removes label
after checks every 50 milliseconds for non-movement of cursor and uses place manager to display label.
I've updated answer since bind event.x and y caused an occasional bug in the positioning of the message. (displaying it at top left corner)
Tracked it down to event.x event.y, so replaced it with
x = root.winfo_pointerx() - root.winfo_rootx()
y = root.winfo_pointery() - root.winfo_rooty()
This update solves the problem, now it functions as intended.
import tkinter as tk
root = tk.Tk()
label = tk.Label(root, text = "Message")
x = y = xx = yy = 0
def display(event):
global x, y
x = root.winfo_pointerx() - root.winfo_rootx()
y = root.winfo_pointery() - root.winfo_rooty()
label.place_forget()
def check(xx, yy):
global x, y
if x == xx and y == yy:
label.place(x = x, y = y, anchor = "s")
else:
xx, yy = x, y
root.after(50, check, xx, yy)
root.bind("<Motion>", display)
root.after(10, check, xx, yy)
root.mainloop()

How can I repeat a def function in Canvas?

I want this def function to repeat itself as soon as the ball hits the ground (border of the window)
I tried using if, for loop and while:, but I wasn't able to make it work. I'm a beginner so maybe I'm just making stupid mistakes. Thanks for the help.
import tkinter
canvas = tkinter.Canvas(width=600, height=600)
canvas.pack()
def ball():
canvas.delete('all')
global y
canvas.create_oval(x - 5,y - 5,x + 5,y + 5, fill = 'red')
y = y + 2
if y < 600:
canvas.after(2, ball)
y = 0
x=300
ball()
TL;DR: I want to repeat a def function in Python after a certain event.
It sounds like you just want to reset the y value:
import tkinter
canvas = tkinter.Canvas(width=600, height=600)
canvas.pack()
def ball():
canvas.delete('all')
global y
canvas.create_oval(x - 5,y - 5,x + 5,y + 5, fill = 'red')
y = y + 2
if y >= 600:
y = 0 # Restart y from 0 again
canvas.after(2, ball)
y = 0
x=300
ball()
I implement a situation with python to show you how you can use the while loop and also using return for your functions to exit the loop after some event.
It's just an example for better understanding the answer. And do not use global variables in your code. It's just for the example.
a = 100
def count_down():
global a
a -= 1
if a > 0:
return True
return False
in_the_loop = True
while(in_the_loop):
in_the_loop = count_down()
print(a)
Maybe this snippet will give some idea about your case to how to exit from the base function by simply adding return to that.

AttributeError: 'Event' object has no attribute 'wider'

I have this program in which I draw a rectangle on a canvas and when I press either the < arrow key or the > key the rectangle should get wider or narrower. But when I run this program and press either of those keys the python shell prints out AttributeError: 'Event' object has no attribute 'wider' (or 'narrower')... A. How can I fix this? and B. Why does it do that?
from tkinter import *
root = Tk()
canvas = Canvas(root, width=400, height=300, bg="#000000")
canvas.pack()
x1 = 150
y1 = 100
x2 = 250
y2 = 200
class ResizeRect:
def __init__(self, x1, y1, x2, y2):
self.x1 = x1
self.y1 = y1
self.x2 = x2
self.y2 = y2
self.rect = canvas.create_rectangle(0,0,1,1)
def draw(self):
canvas.delete(self.rect)
self.rect = canvas.create_rectangle(x1, y1, x2, y2,outline="#00B000", width=2)
def narrower(self):
self.x1 = self.x1 + 5
self.x2 = self.x2 - 5
def wider(self):
self.x1 = self.x1 - 5
self.x2 = self.x2 + 5
r = ResizeRect(150, 100, 250, 200)
r.draw()
def left(r):
r.narrower()
def right(r):
r.wider()
canvas.bind_all('<KeyPress-Left>', left)
canvas.bind_all('<KeyPress-Right>', right)
I also don't know if/when I fix this, there will still be a ton of errors. So it would be great if you help me with the specific problem. But it would be even cooler if you could tell me if/how to fix the other errors that come after this one.
Thanks
You need to provide an argument for the event that tkinter sends when using bind:
def left(event):
r.narrower()
Those methods will also need to call canvas.coords; simply updating the numbers won't cause the display to change.
Your left() and right() routines are receiving an event, which you are not currently accepting. You can change your routines to be like this:
def left(e):
r.narrower()
def right(e):
r.wider()
This gets rid of your error messages. The routines for narrowing and widening will now be called, but they won't work. To resize the rectangle, you'll need to work with the coords() method. By changing the coordinates of the rectangle, you can effectively move or resize it.
current_coords = canvas.coords(rectangleTagId)
# update the coords to new_coords
canvas.coords(rectangleTagId, *new_coords)

Tkinter canvas not updating in an infinite loop

I created some code to show 2 balls moving, but when I run it, it doesn't show the movement of the balls.Furthermore it stops running and ignores the infinite loop This is my code up to now:
import tkinter as tk
class ObjectHolder:
def __init__(self, pos, velocity, radius, id):
self.id = id # the id of the canvas shape
self.pos = pos # the position of the object
self.r = radius # the radius of incluence of the object
self.velocity = velocity # the velocity of the object
def moveobject(object):
x = object.pos[0] + object.velocity[0] # moves the object where
y = object.pos[1] + object.velocity[1] # 0=x and 1=y
object.pos = (x, y)
canvas.move(object, x, y)
class App():
def __init__(self, canvas):
self.canvas = canvas
self.objects = []
for i in range(0, 2):
position = ((i+1)*100, (i+1)*100)
velocity = (-(i+1)*10, -(i+1)*10)
radius = (i + 1) * 20
x1 = position[0]-radius
y1 = position[1]-radius
x2 = position[0]+radius
y2 = position[1]+radius
id = canvas.create_oval(x1, y1, x2, y2)
self.objects.append(ObjectHolder(position, velocity, radius, id))
self.symulation(self.objects)
def symulation(self, objects):
for object in objects: # this moves each object
ObjectHolder.moveobject(object)
# This part doesn't work. It is supposed to update the canvas
# and repeat forever.
self.canvas.update()
root.update()
self.canvas.after(50, self.symulation, objects)
root = tk.Tk()
canvas = tk.Canvas(root, width=800, height=600, bg="light blue")
canvas.pack()
App(canvas)
There are a number of issues with your code. One big one was the way you were updating the position of the existing canvas objects. The move() method needs to know the amount of movement (change in x and y value), not the new absolute position.
When I fixed that it turned out that the velocities were too big, so I reduced them to only be 10% of the values you had.
Another problem was with the way the ObjectHolder class was implemented. For one thing, the moveobject() method had no self argument, which it should have been using instead of having an object argument. You should probably also rename the method simply move().
The code below runs and does animate the movement.
import tkinter as tk
class ObjectHolder:
def __init__(self, pos, velocity, radius, id):
self.id = id # the id of the canvas shape
self.pos = pos # the position of the object
self.r = radius # the radius of incluence of the object
self.velocity = velocity # the velocity of the object
def moveobject(self):
x, y = self.pos
dx, dy = self.velocity
self.pos = (x + dx, y + dy)
canvas.move(self.id, dx, dy) # Amount of movement, not new position.
class App():
def __init__(self, canvas):
self.canvas = canvas
self.objects = []
for i in range(0, 2):
position = ((i+1)*100, (i+1)*100)
# velocity = (-(i+1)*10, -(i+1)*10)
velocity = (-(i+1), -(i+1)) # Much slower speed...
radius = (i + 1) * 20
x1 = position[0]-radius
y1 = position[1]-radius
x2 = position[0]+radius
y2 = position[1]+radius
id = canvas.create_oval(x1, y1, x2, y2)
self.objects.append(ObjectHolder(position, velocity, radius, id))
self.simulation(self.objects)
def simulation(self, objects):
for object in objects: # this moves each object
object.moveobject()
# This part doesn't work. It is supposed to update the canvas
# and repeat forever.
# self.canvas.update() # Not needed.
# root.update() # Not needed.
self.canvas.after(50, self.simulation, objects)
root = tk.Tk()
canvas = tk.Canvas(root, width=800, height=600, bg="light blue")
canvas.pack()
app = App(canvas)
root.mainloop() # Added.

Swinging pendulum does not work

I am having a little trouble with this project. I have to create a pendulum using key handles and the code I have for the key's up and down don't seem to be working. "up" is suppose to make the pendulum go faster and "down" makes it go slower. This is the code that I have so far. can somebody please help.
from tkinter import * # Import tkinter
import math
width = 200
height = 200
pendulumRadius = 150
ballRadius = 10
leftAngle = 120
rightAngle = 60
class MainGUI:
def __init__(self):
self.window = Tk() # Create a window, we may call it root, parent, etc
self.window.title("Pendulum") # Set a title
self.canvas = Canvas(self.window, bg = "white",
width = width, height = height)
self.canvas.pack()
self.angle = leftAngle # Start from leftAngle
self.angleDelta = -1 # Swing interval
self.delay = 200
self.window.bind("<Key>",self.key)
self.displayPendulum()
self.done = False
while not self.done:
self.canvas.delete("pendulum") # we used delete(ALL) in previous lab
# here we only delete pendulum object
# in displayPendulum we give the tag
# to the ovals and line (pendulum)
self.displayPendulum() # redraw
self.canvas.after(self.delay) # Sleep for 100 milliseconds
self.canvas.update() # Update canvas
self.window.mainloop() # Create an event loop
def displayPendulum(self):
x1 = width // 2;
y1 = 20;
if self.angle < rightAngle:
self.angleDelta = 1 # Swing to the left
elif self.angle > leftAngle:
self.angleDelta = -1 # Swing to the right
self.angle += self.angleDelta
x = x1 + pendulumRadius * math.cos(math.radians(self.angle))
y = y1 + pendulumRadius * math.sin(math.radians(self.angle))
self.canvas.create_line(x1, y1, x, y, fill="blue", tags = "pendulum")
self.canvas.create_oval(x1 - 2, y1 - 2, x1 + 2, y1 + 2,
fill = "red", tags = "pendulum")
self.canvas.create_oval(x - ballRadius, y - ballRadius,
x + ballRadius, y + ballRadius,
fill = "green", tags = "pendulum")
def key(self,event):
print(event.keysym)
print(self.delay)
if event.keysym == 'up':
print("up arrow key pressed, delay is",self.delay)
if self.delay >10:
self.delay -= 1
if event.keysym == 'Down':
print("Down arrow key pressed,delay is",self.delay)
if self.delay < 200:
self.delay += 1
if event.keysym=='q':
print ("press q")
self.done = True
self.window.destroy()
MainGUI()
The root of the problem is that the event keysym is "Up" but you are comparing it to the all-lowercase "up". Naturally, the comparison fails every time. Change the if statement to if event.keysym == 'Up':
Improving the animation
In case you're interested, there is a better way to do animation in tkinter than to write your own infinite loop. Tkinter already has an infinite loop running (mainloop), so you can take advantage of that.
Create a function that draws one frame, then have that function arrange for itself to be called again at some point in the future. Specifically, remove your entire "while" loop with a single call to displayFrame(), and then define displayFrame like this:
def drawFrame(self):
if not self.done:
self.canvas.delete("pendulum")
self.displayPendulum()
self.canvas.after(self.delay, self.drawFrame)
Improving the bindings
Generally speaking, your code will be marginally easier to manage and test if you have specific bindings for specific keys. So instead of a single catch-all function, you can have specific functions for each key.
Since your app supports the up key, the down key, and the "q" key, I recommend three bindings:
self.window.bind('<Up>', self.onUp)
self.window.bind('<Down>', self.onDown)
self.window.bind('<q>', self.quit)
You can then define each function to do exactly one thing:
def onUp(self, event):
if self.delay > 10:
self.delay -= 1
def onDown(self, event):
if self.delay < 200:
self.delay += 1
def onQuit(self, event):
self.done = True
self.window.destroy()

Categories

Resources