I have a canvas with a little oval in it. It moves throughout the widget using the arrow keys but when it's on the edge of the canvas if I move it beyond that, the oval just disappears.
I want the oval stays on any edge of the canvas no matter if I continue pressing the arrow key corresponding to that edge without disappearing.
This is the code:
from tkinter import *
root = Tk()
root.title("Oval")
root.geometry("800x600")
w = 600
h = 400
x = w//2
y = h//2
my_canvas = Canvas(root, width=w, height=h, bg='black')
my_canvas.pack(pady=20)
my_circle = my_canvas.create_oval(x, y, x+20, y+20, fill='cyan')
def left(event):
x = -10
y = 0
my_canvas.move(my_circle, x, y)
def right(event):
x = 10
y = 0
my_canvas.move(my_circle, x, y)
def up(event):
x = 0
y = -10
my_canvas.move(my_circle, x, y)
def down(event):
x = 0
y = 10
my_canvas.move(my_circle, x, y)
root.bind('<Left>', left)
root.bind('<Right>', right)
root.bind('<Up>', up)
root.bind('<Down>', down)
root.mainloop()
This is what it looks like:
The oval on an edge
And if I continue pressing the key looks like this:
The oval disappearing
You could test the current coordinates and compare them to your canvas size.
I created a function to get the current x1, y1, x2, y2 from your oval. This way you have the coordiantes of the borders of your oval.
So all I do is testing if the oval is touching a border.
from tkinter import *
root = Tk()
root.title("Oval")
root.geometry("800x600")
w = 600
h = 400
x = w // 2
y = h // 2
my_canvas = Canvas(root, width=w, height=h, bg='black')
my_canvas.pack(pady=20)
my_circle = my_canvas.create_oval(x, y, x + 20, y + 20, fill='cyan')
def left(event):
x1, y1, x2, y2 = get_canvas_position()
if x1 > 0:
x = -10
y = 0
my_canvas.move(my_circle, x, y)
def right(event):
x1, y1, x2, y2 = get_canvas_position()
if x2 < w:
x = 10
y = 0
my_canvas.move(my_circle, x, y)
def up(event):
x1, y1, x2, y2 = get_canvas_position()
if y1 > 0:
x = 0
y = -10
my_canvas.move(my_circle, x, y)
def down(event):
x1, y1, x2, y2 = get_canvas_position()
if y2 < h:
x = 0
y = 10
my_canvas.move(my_circle, x, y)
def get_canvas_position():
return my_canvas.coords(my_circle)
root.bind('<Left>', left)
root.bind('<Right>', right)
root.bind('<Up>', up)
root.bind('<Down>', down)
root.mainloop()
The canvas object is stored via 2 sets of coordinates [x1, y1, x2, y2]. You should check against the objects current location by using the .coords() method. The dimensions of the canvas object will affect the coordinates.
def left(event):
x = -10
y = 0
if my_canvas.coords(my_circle)[0] > 0: # index 0 is X coord left side object.
my_canvas.move(my_circle, x, y)
def right(event):
x = 10
y = 0
# The border collision now happens at 600 as per var "w" as previously defined above.
if my_canvas.coords(my_circle)[2] < w: # index 2 is X coord right side object.
my_canvas.move(my_circle, x, y)
Now repeat a similar process for up and down.
Related
import time
from tkinter import *
class Template:
def __init__(self):
self.window = Tk()
self.window.title("2D Display")
self.canvas = self.canvas_display()
self.line1 = self.line_creation(650,350,500 * .3, 1000)
self.line3 = self.line_movement_creation(0, 350,2000, 350)
self.horizon = self.canvas.create_line(0,350,2000, 350, width = 2, fill ="white")
self.speedx = 0 # x movement of line3
self.speedy = 9 # y movement of line3
self.active = True
self.pos1 = []
self.move_active() #Code that creates the problem
self.canvas.update()
def canvas_display(self): #canvas
canvas = Canvas(self.window, width=500, height=400, background='black')
canvas.pack(expand=True, fill="both")
canvas.update()
return canvas
Upwards is Initialization
def line_creation(self,x,y,x1,y1): #creation of multple lines
spacing = 0
lines = [] # could list([])
for i in range(11):
id = self.canvas.create_line( x, y, x1 + spacing, y1, width=2, fill="white")
lines.append(id)
spacing += 100
pos1 = self.canvas.coords(id)
self.pos1 = pos1
print(self.pos1)
return lines
This is the creation method for the vertices lines
def line_movement_creation(self,x,y,x1,y1):
spacing1 = 0
lines = []
for i in range(4):
id = self.canvas.create_line(x , y+spacing1, x1, y1 + spacing1, width=2, fill="white")
lines.append(id)
spacing1 += 100
#line = [] equal all horizontal and vertical, 12 - 15 equal horizontal moving lines
return lines
The is the creation for the horizontal lines
def line_update(self): #line movement method
for line in self.line3:
self.canvas.move(line, self.speedx, self.speedy)
#Create variables for all x and y values for the lines
pos = self.canvas.coords(line)
print(pos)
if pos[3] >= 800:
self.canvas.move(line, self.speedx, self.speedy - 460)
def move_active(self):
if self.active:
self.line_update()
self.window.after(40, self.move_active)
This is what moves the lines creating an illusion of movement. I want to take the list of horizontal lines and set them between the outer most vertical lines. So it would stay between the vertical lines. Creating a road like image. I think I need to make a separate list for both but I am not sure. So to clarify I can someone help show me how to make the horizontal lines not attach to the ends of the screen but to inside the horizontal lines Code Demonstration
def run(self):
self.window.mainloop()
if __name__ == '__main__':
Temp = Template()
Temp.run()
So, first I would suggest that you use some game engine like pygame because it is faster and provides a bit more options and other stuff but here it is with tkinter (basically it is some simple trigonometry):
import tkinter as tk
import math
class Canvas(tk.Canvas):
def __init__(self, parent, width=700, height=500, **kwargs):
super().__init__(parent, width=width, height=height, **kwargs)
self.width = width
self.height = height
self.angle = 70
self.speedy = 10
self.change_speed_ms = 50
self.draw_angle(self.angle, 5)
self.lines, self.interval = self.init_lines(amount=5)
self.draw_lines()
#property
def radians(self):
return math.radians(self.angle)
def draw_angle(self, view_angle, lines):
orient = 0
adjacent = self.height // 2
step = view_angle // lines
half = view_angle // 2
for angle in range(orient - half, orient + half + step, step):
rad = math.radians(angle)
delta = math.tan(rad) * adjacent
x1, y1 = self.width // 2, self.height // 2
x2, y2 = x1 + delta, y1 + adjacent
self.create_line(x1, y1, x2, y2)
def init_lines(self, amount=5):
interval = round((self.height // 2) / amount)
coordinate_list = list()
offset = self.height // 2
for y in range(0, self.height // 2 + interval, interval):
delta = math.tan(self.radians / 2) * y
x1 = self.width // 2 - delta
x2 = self.width // 2 + delta
y1 = y2 = y + offset
line = self.create_line(x1, y1, x2, y2)
coordinate_list.append((line, (x1, y1, x2, y2)))
return coordinate_list, interval
def draw_lines(self):
tmp_lst = list()
for id_, (x1, y1, x2, y2) in self.lines:
y1 += self.speedy
if y1 > self.height + self.interval - self.speedy:
y1 = self.height // 2
y = y2 = y1
adjacent = y - self.height // 2
delta = math.tan(self.radians / 2) * adjacent
x1 = self.width // 2 - delta
x2 = self.width // 2 + delta
self.coords(id_, x1, y1, x2, y2)
tmp_lst.append((id_, (x1, y1, x2, y2)))
self.lines = tmp_lst
self.after(self.change_speed_ms, self.draw_lines)
root = tk.Tk()
Canvas(root, highlightthickness=0).pack()
root.mainloop()
So the main method here is Canvas().draw_lines(). First of you get a list of line IDs and their coordinates at set intervals based on the amount of total lines, then you iterate over them, change their y value and accordingly calculate the opposite side of a right-angle triangle using tan and the adjacent side which is known from the current y coordinate and the starting point (the middle).
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()
I have this program that I tried to make in python tkinter. A ball would appear on screen and every time I click I want the ball to glide to the point at which I clicked. The x and y locations of the ball changed but the ball only redraws after the ball is finished "moving." Can someone tell me what I am doing wrong.
from tkinter import *
import time
width = 1280
height = 700
ballRadius = 10
iterations = 100
mouseLocation = [width/2, height/2]
ballLocation = [width/2, height/2]
root = Tk()
def drawBall(x, y):
canvas.delete(ALL)
canvas.create_oval(x - ballRadius, y - ballRadius, x + ballRadius, y + ballRadius, fill="blue")
print(x, y)
def getBallLocation(event):
mouseLocation[0] = event.x
mouseLocation[1] = event.y
dx = (ballLocation[0] - mouseLocation[0]) / iterations
dy = (ballLocation[1] - mouseLocation[1]) / iterations
for i in range(iterations):
ballLocation[0] -= dx
ballLocation[1] -= dy
drawBall(round(ballLocation[0]), round(ballLocation[1]))
time.sleep(0.02)
ballLocation[0] = event.x
ballLocation[1] = event.y
canvas = Canvas(root, width=width, height=height, bg="black")
canvas.pack()
canvas.create_oval(width/2-ballRadius, height/2-ballRadius, width/2+ballRadius, height/2+ballRadius, fill="blue")
canvas.bind("<Button-1>", getBallLocation)
root.mainloop()
In your code time.sleep pauses the entire GUI, that's why you don't see the intermediate locations of the ball. Instead you can construct a function with widget.after method. Try the following:
print(x, y)
dx = 0
dy = 0
def getBallLocation(event):
canvas.unbind("<Button-1>")
global dx, dy
mouseLocation[0] = event.x
mouseLocation[1] = event.y
dx = (ballLocation[0] - mouseLocation[0]) / iterations
dy = (ballLocation[1] - mouseLocation[1]) / iterations
draw()
i = 0
def draw():
global i
ballLocation[0] -= dx
ballLocation[1] -= dy
drawBall(round(ballLocation[0]), round(ballLocation[1]))
if i < iterations-1:
canvas.after(20, draw)
i += 1
else:
canvas.bind("<Button-1>", getBallLocation)
i = 0
canvas = Canvas(root, width=width, height=height, bg="black")
so there are a lot of problems going on with this. I attempted to make a snake game in python but It was just a mess so now I'm trying to make it where the only direction they can go after the initial right movement is down and they have to get to the coordinates of the food otherwise it starts over. if they 'eat' the food/square then it will make another level with ought starting new game so I can keep score, however the grow definition doesn't seem to be working at all no matter what I put under the if's. Im using python tkinter.
import Tkinter
import random
import math
from Tkinter import *
import time
# Create root window
####
root = Tkinter.Tk()
#####
# Create Model
######
speed_intvar = Tkinter.IntVar()
speed_intvar.set(1) # Initialize y coordinate
# radius and x-coordinate of circle
new_dir = 0
leng = 40
var = 1
x1 = 10
y1 = 10
x2 = 50
y2 = 10
x3 = x2
y3 = 10
direction = 3
foodx1 = 1
foodx2 = 8
foody1 = 1
foody2 = 8
food_present = 0
length = 8
food = 0# radians of angle in standard position, ccw from positive x axis
######
# Create Controller
#######
# Instantiate and place slider
# Create and place directions for the user
#canvas = Tkinter.Canvas(root, width=900, height=900, background='#FFFFFF')
#canvas.grid(row=0, rowspan=2, column=2)
#text = Tkinter.Button(root, text='Use WASD to move', command = new_game)
#text.grid(row=0, column =2)
#text = Tkinter.Label(root, text='Use WASD to move')
#text.grid(row=0, column =2)
######
# Create View
#######
# Create and place a canvas
def new_game():
c.delete("all")
canvas = Tkinter.Canvas(root, width=900, height=900, background='#FFFFFF')
canvas.grid(row=0, rowspan=2, column=1)
# Create a circle on the canvas to match the initial model
circle_item = canvas.create_line(x1, y1, x2, y2, x2, y3, fill='black', width = "8")
#circle_item2 = canvas.create_rectangle(8, 1, 9, 8,
# outline='#000000', fill='black')
#def move(event):
# if event.char=='w'
def food():
global foodx1, foodx2, foody1, foody2, food_present, food, var
food_present = 0
if food_present==0:
foodx1 = random.randint(1, 900)
foody1 = random.randint(1,900)
foodx2 = foodx1 + 8
foody2 = foody1 + 8
food = canvas.create_rectangle(foodx1, foody1, foodx2, foody2, fill = 'black')
food_present += 1
food()
#def make_longer():
# global food_present, food_list, x1, y1, x2, y2, circle_item
# #x1 = canvas.coords(circle_item)
# #y1 = canvas.coords(circle_item)
# #x2 = canvas.coords(circle_item)
# #y2 = canvas.coords(circle_item)
# xx1, yy1, xx2, yy2 = canvas.coords(circle_item)
# xx1 -= 10
# xx2 -= 50
# for i in range(len(food_list)):
# circle_item2=canvas.create_rectangle(xx1, yy1, xx2, yy2, fill ="black")
#make_longer()
def animate():
global x1, y1, x2, y2, x3, y3, var
x1, y1, x2, y2, x3, y3 = canvas.coords(circle_item)
#for i in range(food_present):
if var == 1:
x1 += 1
x2 += 1
#x3+= 1
if var == 4:
y1+=1
y2+=1
y3+=1
if var == 5:
x1-=1
x2-=1
x3-=1
if var == 6:
y1 -= 1
y2 -= 1
y3 -= 1
canvas.coords(circle_item, x1, y1, x2, y2, x2, y3)
#canvas.itemconfig(circle_item, x1, y1, x2, y2, x3, y3, fill="blue")
canvas.update()
#
#while
# Get the slider data and create x- and y-components of velocity
#velocity_x = speed_intvar.get() * math.cos(direction) # adj = hyp*cos()
#velocity_y = speed_intvar.get() * math.sin(direction) # opp = hyp*sin()
## Change the canvas item's coordinates
#canvas.move(circle_item, velocity_x, velocity_y)
#canvas.move(circle_item2, velocity_x, velocity_y)
# Get the new coordinates and act accordingly if ball is at an edge
#global location
## If crossing left or right of canvas
#if x2>canvas.winfo_width():
#
#if x1<0:
# canvas.move(circle_item, x+400, y)
#global direction
canvas.after(1, animate)
# Call function directly to start the recursion
animate()
#
def move(event):
global direction, var, x1, x2, x3, y1, y2, y3, new_dir
direction = 3
if event.char=='s' and direction == 3:
if direction == 1:
return
else:
new_dir = 1
var = 3
for i in range(10):
x1, y1, x2, y2, x3, y3 = canvas.coords(circle_item)
#for i in range(food_present):
if var == 3:
x1 += 1
x2 += 0
x3 +=0
y1 +=0
y2= y1
#x2 += 1
#x3 += 1
y3 += 1
canvas.coords(circle_item, x1, y1, x2, y2, x3, y3)
canvas.update()
if x1 == x3:
var = 4
direction = 1
canvas.update()
# if event.char=='s' and direction == 2:
# if direction == 1:
# return
# else:
# new_dir = 1
# var = 3
# for i in range(10):
# x1, y1, x2, y2, x3, y3 = canvas.coords(circle_item)
##for i in range(food_present):
# if var == 3:
# x1 -= 0
# x2 -= 1
# x3 -=1
# y1 -=0
# y2 -=1
# #x2 += 1
# #x3 += 1
#
# y3 -= 1
# canvas.coords(circle_item, x1, y1, x2, y2, x3, y3)
# canvas.update()
# if x1 == x3:
# var = 4
# direction = 1
# canvas.update()
root.bind("<Key>", move)
def x():
i = Tkinter.Canvas(root, width=900, height=900, background='#FFFFFF')
x = Tkinter.Button(root, text='Use S to move down. Try and eat the food.', command = new_game)
x.grid(row=0, column =1)
def grow():
global x1, y1, x2, y2, x3, y3, var, food_present, foodx1, foody1, foodx2, foody2
x1, y1, x2, y2, x3, y3 = canvas.coords(circle_item)
if x2>canvas.winfo_width() or x1<0:
x()
if y2>canvas.winfo_height() or y3>canvas.winfo_height():
food_present = 0
x()
canvas.update()
grow()
c = Tkinter.Canvas(root, width=900, height=900, background='#FFFFFF')
c.grid(row=0, rowspan=2, column=1)
text = Tkinter.Button(root, text='Use S to move down. Try and eat the food.', command = new_game)
text.grid(row=0, column =1)
root.mainloop()
If you want to check if two objects are overlapping on a Tkinter canvas, you may get their positions by the method of object Canvas .coords(objectname). You use the .coords method to check on a drawn object.
Example:
circle = canvas.create_oval(0, 0, 50, 50)
canvas.move(circle, 5, 0) # moves the circle by 5 pixels to the right
coords = canvas.coords(circle) # Returns an array (5, 0, 55, 0).
What you don't do is specify a coordinate to return what object is on it(what I think you are doing).
In this test script I draw a square I may zoom in by using the mouse wheel.
If I right click inside a cell I get the right cell coordinates (not x and y, but column and row): this is exactly what I expect it to write to the console in the background.
If I, instead, move the canvas by pressing the mouse left button and dragging it somewhere else, the coordinates are not right any more.
Where do I get the delta x and delta y (or offsets) to give back the right info?
FYI:
1) get_pos() is the method that does the check and produces the result.
2) the following code has been tested on Ubuntu 16.10 (with the latest updates) running Python 3.5.2.
import tkinter as tk
import tkinter.ttk as ttk
class GriddedMazeCanvas(tk.Canvas):
def almost_centered(self, cols, rows):
width = int(self['width'])
height = int(self['height'])
cell_dim = self.settings['cell_dim']
rows = rows % height
cols = cols % width
w = cols * cell_dim
h = rows * cell_dim
if self.zoom < 0:
raise ValueError('zoom is negative:', self.zoom)
zoom = self.zoom
if self.drawn() and 1 != zoom:
w *= zoom
h *= zoom
h_shift = (width - w) // 2
v_shift = (height - h) // 2
return [h_shift, v_shift,
h_shift + w, v_shift + h]
def __init__(self, *args, **kwargs):
if 'settings' not in kwargs:
raise ValueError("'settings' not passed.")
settings = kwargs['settings']
del kwargs['settings']
super().__init__(*args, **kwargs)
self.config(highlightthickness=0)
self.settings = settings
self.bind_events()
def draw_maze(self, cols, rows):
self.cols = cols
self.rows = rows
if self.not_drawn():
self.cells = {}
self.cell_dim = self.settings['cell_dim']
self.border_thickness = self.settings['border_thickness']
self.zoom = 1
self.delete(tk.ALL)
maze, coords = self._draw_maze(cols, rows, fix=False)
lines = self._draw_grid(coords)
return maze, lines
def _draw_maze(self, cols, rows, fix=True):
data = self.settings
to_max = data['to_max']
border_thickness = data['border_thickness']
poligon_color = data['poligon_color']
poligon_border_color = data['poligon_border_color']
coords = self.almost_centered(cols, rows)
if fix:
# Fix for the disappearing NW borders
if to_max == cols:
coords[0] += 1
if to_max == rows:
coords[1] += 1
maze = self.create_rectangle(*coords,
fill=poligon_color,
outline=poligon_border_color,
width=border_thickness,
tag='maze')
return maze, coords
def _draw_grid(self, coords):
data = self.settings
poligon_border_color = data['poligon_border_color']
cell_dim = data['cell_dim']
if coords is None:
if self.not_drawn():
raise ValueError('The maze is still uninitialized.')
x1, y1, x2, y2 = self.almost_centered(self.cols, self.rows)
else:
x1, y1, x2, y2 = coords
zoom = self.zoom
if self.drawn() and 1 != zoom:
if self.zoom < 1:
self.zoom = zoom = 1
print('no zooming below 1.')
else:
cell_dim *= zoom
lines = []
for i, x in enumerate(range(x1, x2, cell_dim)):
line = self.create_line(x, y1, x, y2,
fill=poligon_border_color,
tags=('grid', 'grid_hl_{}'.format(i)))
lines.append(line)
for i, y in enumerate(range(y1, y2, cell_dim)):
line = self.create_line(x1, y, x2, y,
fill=poligon_border_color,
tags=('grid', 'grid_vl_{}'.format(i)))
lines.append(line)
return lines
def drawn(self):
return hasattr(self, 'cells')
def not_drawn(self):
return not self.drawn()
def bind_events(self):
self.bind('<Button-4>', self.onZoomIn)
self.bind('<Button-5>', self.onZoomOut)
self.bind('<ButtonPress-1>', self.onScrollStart)
self.bind('<B1-Motion>', self.onScrollMove)
self.tag_bind('maze', '<ButtonPress-3>', self.onMouseRight)
def onScrollStart(self, event):
print(event.x, event.y, self.canvasx(event.x), self.canvasy(event.y))
self.scan_mark(event.x, event.y)
def onMouseRight(self, event):
col, row = self.get_pos(event)
print('zoom:', self.zoom, ' col, row:', col, row)
def onScrollMove(self, event):
delta = event.x, event.y
self.scan_dragto(*delta, gain=1)
def onZoomIn(self, event):
if self.not_drawn():
return
max_zoom = 9
self.zoom += 1
if self.zoom > max_zoom:
print("Can't go beyond", max_zoom)
self.zoom = max_zoom
return
print('Zooming in.', event.num, event.x, event.y, self.zoom)
self.draw_maze(self.cols, self.rows)
def onZoomOut(self, event):
if self.not_drawn():
return
self.zoom -= 1
if self.zoom < 1:
print("Can't go below one.")
self.zoom = 1
return
print('Zooming out.', event.num, event.x, event.y, self.zoom)
self.draw_maze(self.cols, self.rows)
def get_pos(self, event):
x, y = event.x, event.y
cols, rows = self.cols, self.rows
cell_dim, zoom = self.cell_dim, self.zoom
x1, y1, x2, y2 = self.almost_centered(cols, rows)
print('x1, y1, x2, y2:', x1, y1, x2, y2,
' bbox:', self.bbox('maze'))
if not (x1 <= x <= x2 and y1 <= y <= y2):
print('Here we are out of bounds.')
return None, None
scale = zoom * cell_dim
col = (x - x1) // scale
row = (y - y1) // scale
return col, row
class CanvasButton(ttk.Button):
def freeze_origin(self):
if not hasattr(self, 'origin'):
canvas = self.canvas
self.origin = canvas.xview()[0], canvas.yview()[0]
def reset(self):
canvas = self.canvas
x, y = self.origin
canvas.yview_moveto(x)
canvas.xview_moveto(y)
def __init__(self, *args, **kwargs):
if 'canvas' not in kwargs:
raise ValueError("'canvas' not passed.")
canvas = kwargs['canvas']
del kwargs['canvas']
super().__init__(*args, **kwargs)
self.config(command=self.reset)
self.canvas = canvas
root = tk.Tk()
settings = {'cell_dim': 3,
'to_max': 200,
'border_thickness': 1,
'poligon_color': '#F7F37E',
'poligon_border_color': '#AC5D33'}
frame = ttk.Frame(root)
canvas = GriddedMazeCanvas(frame,
settings=settings,
width=640,
height=480)
button = CanvasButton(frame, text='Reset', canvas=canvas)
button.freeze_origin()
canvas.draw_maze(20, 10)
canvas.grid(row=0, column=0, sticky=tk.NSEW)
button.grid(row=1, column=0, sticky=tk.EW)
frame.rowconfigure(0, weight=1)
frame.grid()
root.mainloop()
Looking at a previously answered question, I learned how to find the delta x and delta y I was looking for.
So, the solution is:
def get_pos(self, event):
x, y = event.x, event.y
cols, rows = self.cols, self.rows
cell_dim, zoom = self.cell_dim, self.zoom
x1, y1, x2, y2 = self.almost_centered(cols, rows)
# the following line stores deltax and deltay into x0, y0
x0, y0 = int(self.canvasx(0)), int(self.canvasy(0))
# then it is trivial to compute the solution
xa, ya = x1 - x0, y1 - y0
xb, yb = x2 - x0, y2 - y0
if not (xa <= x <= xb and ya <= y <= yb):
print('Here we are out of bounds.')
return None, None
scale = zoom * cell_dim
col = (x - xa) // scale
row = (y - ya) // scale
return col, row