Efficiently create a grid in tkinter - python

I am currently using the following code to create a grid the size of the window within python using the tkinter module
import tkinter as tk
class Frame():
def __init__(self, *args, **kwargs):
# Setup Canvas
self.c = tk.Canvas(root, height=500, width=500, bg='white')
self.c.pack(fill=tk.BOTH, expand=True)
self.c.bind('<Configure>', self.createGrid)
self.pixel_width = 20
self.pixel_height = 20
# Setup binds
self.c.bind("<ButtonPress-1>", self.leftClick)
def leftClick(self, event):
items = self.c.find_closest(event.x, event.y)
if items:
rect_id = items[0]
self.c.itemconfigure(rect_id, fill="red")
def createGrid(self, event=None):
for x in range(0, self.c.winfo_width()):
for y in range(0, self.c.winfo_height()):
x1 = (x * self.pixel_width)
x2 = (x1 + self.pixel_width)
y1 = (y * self.pixel_height)
y2 = (y1 + self.pixel_height)
self.c.create_rectangle(x1,y1,x2,y2)
self.c.update()
root = tk.Tk()
gui = Frame(root)
root.mainloop()
If I set the canvas height and width to something like 50 this loads quite quickly, although when the size is increased to 500 x 500 like is set here it takes about 5 seconds to create the grid. I have tried creating the grid with lines but the problem with that is I need squares as I am then planning to change the colour of a square that is selected. Is there any way I can make this more efficient?

I think you created way more rectangles than you need. The below two lines:
for x in range(0, self.c.winfo_width()):
for y in range(0, self.c.winfo_height()):
Will create 504x504 rectangles = 254016. It will work fine if you reduce it to just fill your current screen:
def createGrid(self, event=None):
for x in range(0, int(self.c.winfo_width()/20+1)):
for y in range(0, int(self.c.winfo_height()/20+1)):
x1 = (x * self.pixel_width)
x2 = (x1 + self.pixel_width)
y1 = (y * self.pixel_height)
y2 = (y1 + self.pixel_height)
self.c.create_rectangle(x1,y1,x2,y2)
self.c.update()

Related

How to make a line that follows the mouse(It follows it only by direction and don't change it's size)in tkinter

https://www.youtube.com/watch?v=TOEi6T2mtHo&ab_channel=TheCodingTrain
18:09
How to do the same thing in Tkinter
what i tried
from multiprocessing.connection import wait
from pickle import PicklingError
from tkinter import *
import win32api
from sklearn import preprocessing
root = Tk()
canvas = Canvas(root,bg = "pink",height = "500" ,width="1000")
def UpdateLine():
position = win32api.GetCursorPos()
x = position[0]
y = position[1]
wx = root.winfo_x()
wy = root.winfo_y()
if x < wx +1000 and x > wx and y < wy + 500 and y> wy:
canvas.coords(Line1, 100, 200,x-100 + 3 ,y-200)
root.after(1,UpdateLine)
def Create_Line(x, y, r, canvasName): #center coordinates, radius
position = win32api.GetCursorPos()
x = position[0]
y = position[1]
return canvasName.create_line(100,200,x,y)
Line1 = Create_Line(1,2,3,canvas)
canvas.pack()
root.after(1,UpdateLine)
root.mainloop()
I have tried to calculate the distance between the lines than doing some bad maths but still, it's not working(not working as I wanted)
to make things more clear I want to rotate the line to the mouse(just rotating nothing else cause i already can make it rotate but when it rotates the size of the line will change with it)
to be more more specific
how to make lookAt function(It makes and object literally look at in another obejct)
Solution
Find the x, y coordinates of mouse
Calculate unit vector in the direction of mouse
Give the vector a constant magnitude
Example
import tkinter as tk
root = tk.Tk()
cv = tk.Canvas(root, width=500, height=500)
cv.pack()
length = 100
def redraw(event):
cv.delete("all")
msx = event.x - 250
msy = event.y - 250
mag = (msx*msx + msy*msy) ** 0.5
print(250, 250, (msx/mag*length)+250, (msy/mag*length)+250)
cv.create_line(250, 250, (msx/mag*length)+250, (msy/mag*length)+250, fill="red")
cv.bind("<Motion>", redraw)
root.mainloop()

Is there a way to update Tkinter canvas live with text?

I am trying to visualise the backtracking algorithm to solve sudoku puzzles using Tkinter (Example video: https://www.geeksforgeeks.org/building-and-visualizing-sudoku-game-using-pygame/)
def play_puzzle(self):
self.play_frame.pack_forget()
self.home_frame.pack_forget()
self.play_frame.pack(fill=BOTH, expand=1)
self.canvas = Canvas(self.play_frame, width=WIDTH, height=HEIGHT)
self.canvas.grid(row=0, column=0, columnspan=9, rowspan=9)
self.canvas.bind("<Button-1>", self.cell_clicked)
self.canvas.bind("<Key>", self.key_pressed)
solution_btn = ttk.Button(self.play_frame, text='Solution', command=self.solve_puzzle)
home_btn = ttk.Button(self.play_frame, text='Home', command=lambda: self.return_home('play'))
clear = ttk.Button(self.play_frame, text='clear', command = lambda: self.canvas.delete('numbers'))
view_solution_btn = ttk.Button(self.play_frame, text='View Solution', command=self.view_solution)
solution_btn.grid(row= 1, column = 11)
home_btn.grid(row = 3, column = 11)
clear.grid(row=5, column = 11)
view_solution_btn.grid(row=7, column = 11)
self.draw_grid()
self.draw_puzzle()
def view_solution(self):
find = self.game.find_empty()
if not find:
print('Solution found')
return True
else:
e_row, e_col = find
for i in range(1,10):
if self.game.is_valid(i, e_row, e_col):
self.game.puzzle[e_row][e_col] = i
self.play_puzzle()
time.sleep(1)
if self.view_solution():
return True
self.game.puzzle[e_row][e_col] = 0
return False
def draw_puzzle(self):
self.canvas.delete("numbers")
for i in range(9):
for j in range(9):
answer = self.game.puzzle[i][j]
if answer != 0:
x = MARGIN + j * SIDE + SIDE / 2
y = MARGIN + i * SIDE + SIDE / 2
original = self.game.start_puzzle[i][j]
color = "black" if answer == original else "sea green"
self.canvas.create_text(x, y, text=answer, tags="numbers", fill=color)
def draw_grid(self):
for i in range(10):
color = 'blue' if i%3==0 else 'gray'
x0 = MARGIN + i*SIDE
y0 = MARGIN
x1 = MARGIN + i*SIDE
y1 = HEIGHT - MARGIN
self.canvas.create_line(x0,y0,x1,y1, fill=color)
x0 = MARGIN
y0 = MARGIN + i*SIDE
x1 = WIDTH-MARGIN
y1 = MARGIN + i*SIDE
self.canvas.create_line(x0,y0,x1,y1, fill=color)
When I call the view_solution function in the above snippet(by clicking the view solution button), it doesn't update the canvas every time it runs but outputs the answer/fills the puzzle with solution after it completes the entire loop. Is there a way that I could make this work like the one in the video shown?
I have tried using .after() function in Tkinter but I am not sure how to implement it perfectly.
Entire code here - https://github.com/ssram4298/sudoku_gui_tkinter
There are a few ways to update the canvas while processing it.
root.update() #self.parent.update() in your code
root.update_idletasks() #self.parent.update_idletasks() in your code
You can also update individual widgets by calling update() on them (myButton.update()).
If you need to process a change on a widget it needs to be updated before it will be rendered.

How to animate the creation of this arc in Tkinter? [duplicate]

I am trying to model a simple solar system in Tkinter using circles and moving them around in canvas. However, I am stuck trying to find a way to animate them. I looked around and found the movefunction coupled with after to create an animation loop. I tried fidgeting with the parameters to vary the y offset and create movement in a curved path, but I failed while trying to do this recursively or with a while loop. Here is the code I have so far:
import tkinter
class celestial:
def __init__(self, x0, y0, x1, y1):
self.x0 = x0
self.y0 = y0
self.x1 = x1
self.y1 = y1
sol_obj = celestial(200, 250, 250, 200)
sx0 = getattr(sol_obj, 'x0')
sy0 = getattr(sol_obj, 'y0')
sx1 = getattr(sol_obj, 'x1')
sy1 = getattr(sol_obj, 'y1')
coord_sol = sx0, sy0, sx1, sy1
top = tkinter.Tk()
c = tkinter.Canvas(top, bg='black', height=500, width=500)
c.pack()
sol = c.create_oval(coord_sol, fill='black', outline='white')
top.mainloop()
Here's something that shows one way to do what you want using the tkinter after method to update both the position of the object and the associated canvas oval object. It uses a generator function to compute coordinates along a circular path representing the orbit of one of the Celestial instances (named planet_obj1).
import math
try:
import tkinter as tk
except ImportError:
import Tkinter as tk # Python 2
DELAY = 100
CIRCULAR_PATH_INCR = 10
sin = lambda degs: math.sin(math.radians(degs))
cos = lambda degs: math.cos(math.radians(degs))
class Celestial(object):
# Constants
COS_0, COS_180 = cos(0), cos(180)
SIN_90, SIN_270 = sin(90), sin(270)
def __init__(self, x, y, radius):
self.x, self.y = x, y
self.radius = radius
def bounds(self):
""" Return coords of rectangle surrounding circlular object. """
return (self.x + self.radius*self.COS_0, self.y + self.radius*self.SIN_270,
self.x + self.radius*self.COS_180, self.y + self.radius*self.SIN_90)
def circular_path(x, y, radius, delta_ang, start_ang=0):
""" Endlessly generate coords of a circular path every delta angle degrees. """
ang = start_ang % 360
while True:
yield x + radius*cos(ang), y + radius*sin(ang)
ang = (ang+delta_ang) % 360
def update_position(canvas, id, celestial_obj, path_iter):
celestial_obj.x, celestial_obj.y = next(path_iter) # iterate path and set new position
# update the position of the corresponding canvas obj
x0, y0, x1, y1 = canvas.coords(id) # coordinates of canvas oval object
oldx, oldy = (x0+x1) // 2, (y0+y1) // 2 # current center point
dx, dy = celestial_obj.x - oldx, celestial_obj.y - oldy # amount of movement
canvas.move(id, dx, dy) # move canvas oval object that much
# repeat after delay
canvas.after(DELAY, update_position, canvas, id, celestial_obj, path_iter)
top = tk.Tk()
top.title('Circular Path')
canvas = tk.Canvas(top, bg='black', height=500, width=500)
canvas.pack()
sol_obj = Celestial(250, 250, 25)
planet_obj1 = Celestial(250+100, 250, 15)
sol = canvas.create_oval(sol_obj.bounds(), fill='yellow', width=0)
planet1 = canvas.create_oval(planet_obj1.bounds(), fill='blue', width=0)
orbital_radius = math.hypot(sol_obj.x - planet_obj1.x, sol_obj.y - planet_obj1.y)
path_iter = circular_path(sol_obj.x, sol_obj.y, orbital_radius, CIRCULAR_PATH_INCR)
next(path_iter) # prime generator
top.after(DELAY, update_position, canvas, planet1, planet_obj1, path_iter)
top.mainloop()
Here's what it looks like running:

Is there a way to centre a canvas widget in the window?

I am building a chess program using Tkinker/Python. I am trying to align the board in the centre of the window so that I can have the taken pieces placed alongside the board. Any ideas?
from tkinter import *
root=Tk()
class Board():
def drawboard():
dark="#643c22"
light="#faeac6"
canvas=Canvas(root, width=920, height=720,)
canvas.pack( fill=BOTH)
colour=light
for row in range(8):
if colour==dark:
colour=light
else:
colour=dark
for column in range(8):
x1 = (column * 90)
y1 = ((7-row)* 90)
x2 = x1 + 90
y2 = y1 + 90
canvas.create_rectangle(x1, y1, x2, y2, fill=colour)
if colour==dark:
colour=light
else:
colour=dark
Board.drawboard()
root.mainloop()
I expect it to lined up in the centre but it is aligned to the left.
The class you created is simply a container for some functions... You probably need to read a little bit about object oriented python, and get acquainted with its specifics. I rewrote your class Board as an example; it inherits from tk.Tk and therefore is a tkinter root.
As far as the placement of the various widgets, I added a right and left frame in order to center the canvas representing the checkers board.
it comes like this:
import tkinter as tk
class Board(tk.Tk):
colours = ["#643c22", "#faeac6"]
def __init__(self, n=8):
super().__init__()
self.n = n
self.left_frame = tk.Frame(self)
self.left_frame.grid(row=0, column=0, rowspan=8, padx=100)
self.right_frame = tk.Frame(self)
self.right_frame.grid(row=0, column=10, rowspan=8)
self.canvas = tk.Canvas(self, width=920, height=720, )
self.canvas.grid(row=0, column=1, columnspan=8, rowspan=8)
self.board = [[None for row in range(n)] for col in range(n)]
self.current_colour_ndx = 0
def _swap_colours(self):
self.current_colour_ndx = (self.current_colour_ndx + 1) % 2
def drawboard(self):
for col in range(self.n):
self._swap_colours()
for row in range(self.n):
x1 = col * 90
y1 = (7-row) * 90
x2 = x1 + 90
y2 = y1 + 90
colour = self.colours[self.current_colour_ndx]
self.board[row][col] = self.canvas.create_rectangle(x1, y1, x2, y2, fill=colour)
self._swap_colours()
if __name__ == '__main__':
board = Board()
board.drawboard()
board.mainloop()

Animating an object to move in a circular path in Tkinter

I am trying to model a simple solar system in Tkinter using circles and moving them around in canvas. However, I am stuck trying to find a way to animate them. I looked around and found the movefunction coupled with after to create an animation loop. I tried fidgeting with the parameters to vary the y offset and create movement in a curved path, but I failed while trying to do this recursively or with a while loop. Here is the code I have so far:
import tkinter
class celestial:
def __init__(self, x0, y0, x1, y1):
self.x0 = x0
self.y0 = y0
self.x1 = x1
self.y1 = y1
sol_obj = celestial(200, 250, 250, 200)
sx0 = getattr(sol_obj, 'x0')
sy0 = getattr(sol_obj, 'y0')
sx1 = getattr(sol_obj, 'x1')
sy1 = getattr(sol_obj, 'y1')
coord_sol = sx0, sy0, sx1, sy1
top = tkinter.Tk()
c = tkinter.Canvas(top, bg='black', height=500, width=500)
c.pack()
sol = c.create_oval(coord_sol, fill='black', outline='white')
top.mainloop()
Here's something that shows one way to do what you want using the tkinter after method to update both the position of the object and the associated canvas oval object. It uses a generator function to compute coordinates along a circular path representing the orbit of one of the Celestial instances (named planet_obj1).
import math
try:
import tkinter as tk
except ImportError:
import Tkinter as tk # Python 2
DELAY = 100
CIRCULAR_PATH_INCR = 10
sin = lambda degs: math.sin(math.radians(degs))
cos = lambda degs: math.cos(math.radians(degs))
class Celestial(object):
# Constants
COS_0, COS_180 = cos(0), cos(180)
SIN_90, SIN_270 = sin(90), sin(270)
def __init__(self, x, y, radius):
self.x, self.y = x, y
self.radius = radius
def bounds(self):
""" Return coords of rectangle surrounding circlular object. """
return (self.x + self.radius*self.COS_0, self.y + self.radius*self.SIN_270,
self.x + self.radius*self.COS_180, self.y + self.radius*self.SIN_90)
def circular_path(x, y, radius, delta_ang, start_ang=0):
""" Endlessly generate coords of a circular path every delta angle degrees. """
ang = start_ang % 360
while True:
yield x + radius*cos(ang), y + radius*sin(ang)
ang = (ang+delta_ang) % 360
def update_position(canvas, id, celestial_obj, path_iter):
celestial_obj.x, celestial_obj.y = next(path_iter) # iterate path and set new position
# update the position of the corresponding canvas obj
x0, y0, x1, y1 = canvas.coords(id) # coordinates of canvas oval object
oldx, oldy = (x0+x1) // 2, (y0+y1) // 2 # current center point
dx, dy = celestial_obj.x - oldx, celestial_obj.y - oldy # amount of movement
canvas.move(id, dx, dy) # move canvas oval object that much
# repeat after delay
canvas.after(DELAY, update_position, canvas, id, celestial_obj, path_iter)
top = tk.Tk()
top.title('Circular Path')
canvas = tk.Canvas(top, bg='black', height=500, width=500)
canvas.pack()
sol_obj = Celestial(250, 250, 25)
planet_obj1 = Celestial(250+100, 250, 15)
sol = canvas.create_oval(sol_obj.bounds(), fill='yellow', width=0)
planet1 = canvas.create_oval(planet_obj1.bounds(), fill='blue', width=0)
orbital_radius = math.hypot(sol_obj.x - planet_obj1.x, sol_obj.y - planet_obj1.y)
path_iter = circular_path(sol_obj.x, sol_obj.y, orbital_radius, CIRCULAR_PATH_INCR)
next(path_iter) # prime generator
top.after(DELAY, update_position, canvas, planet1, planet_obj1, path_iter)
top.mainloop()
Here's what it looks like running:

Categories