I picked up this code and wanted to mess with it, the main problem i'm having is being unable to add an image to the actual gui at a set location on the 2 dimensional array. I get no actual error but also get no output of the image on the gui. Please help! Thank you.
import tkinter as tk
class GameBoard(tk.Frame):
def __init__(self, parent, rows=9, columns=9, size=60, color1="light grey", color2="light grey"):
'''size is the size of a square, in pixels'''
self.rows = rows
self.columns = columns
self.size = size
self.color1 = color1
self.color2 = color2
self.pieces = {}
canvas_width = columns * size
canvas_height = rows * size
tk.Frame.__init__(self, parent)
self.canvas = tk.Canvas(self, borderwidth=0, highlightthickness=0,
width=canvas_width, height=canvas_height, background="white")
self.canvas.pack(side="top", fill="both", expand=True, padx=2, pady=2)
self.canvas.bind("<Configure>", self.refresh)
def addpiece(self, name, image, row=1, column=1):
bishop = tk.PhotoImage(file='C:\\Users\\Sharjeel Jan\\Desktop\\final shit man\\Pieces\\Bishop.gif')
self.canvas.create_image(1,1, image=bishop, tags=(name, "Bishop"), anchor= "c")
self.placepiece(name, row, column)
def placepiece(self, name, row, column):
'''Place a piece at the given row/column'''
self.pieces[name] = (row, column)
x0 = (column * self.size) + int(self.size/2)
y0 = (row * self.size) + int(self.size/2)
self.canvas.coords(name, x0, y0)
def placepiece(self, name, row, column):
'''Place a piece at the given row/column'''
self.pieces[name] = (row, column)
x0 = (column * self.size) + int(self.size/2)
y0 = (row * self.size) + int(self.size/2)
self.canvas.coords(name, x0, y0)
def refresh(self, event):
'''Redraw the board, possibly in response to window being resized'''
xsize = int((event.width-1) / self.columns)
ysize = int((event.height-1) / self.rows)
self.size = min(xsize, ysize)
self.canvas.delete("square")
color = self.color2
for row in range(self.rows):
color = self.color1 if color == self.color2 else self.color2
for col in range(self.columns):
x1 = (col * self.size)
y1 = (row * self.size)
x2 = x1 + self.size
y2 = y1 + self.size
self.canvas.create_rectangle(x1, y1, x2, y2, outline="black", fill=color, tags="square")
color = self.color1 if color == self.color2 else self.color2
for name in self.pieces:
self.placepiece(name, self.pieces[name][0], self.pieces[name][1])
self.canvas.tag_raise("piece")
self.canvas.tag_lower("square")
if __name__ == "__main__":
root = tk.Tk()
board = GameBoard(root)
board.pack(side="top", fill="both", expand="true", padx=4, pady=4)
# player1 = tk.PhotoImage(data=imagedata)
# board.addpiece("player1", player1, 0,0)
root.mainloop()
You have a single line that tries to put an image to GUI:
self.canvas.create_image(1,1, image=bishop, tags=(name, "Bishop"), anchor= "c")
which is under addpiece.
The line below saves the image in a reference:
bishop = tk.PhotoImage(file='C:\\Users\\Sharjeel Jan\\Desktop\\final shit man\\Pieces\\Bishop.gif')
which is defined exclusively for the scope of addpiece, when the method is finished, the image disappears if at all being displayed.
Which effectively makes this question a duplicate to Why does Tkinter image not show up if created in a function?
In order to prevent that, make the image reference available in the scope where mainloop is called.
Either attach the reference bishop to an object, for example self.canvas, whose reference is available in the scope of mainloop:
self.canvas.bishop = tk.PhotoImage(file='C:\\Users\\Sharjeel Jan\\Desktop\\final shit man\\Pieces\\Bishop.gif')
self.canvas.create_image(1,1, image=self.canvas.bishop, tags=(name, "Bishop"), anchor= "c")
or simply pass the image reference from the mainloop scope is in, and use that object reference:
self.canvas.create_image(1,1, image=image, tags=(name, "Bishop"), anchor= "c")
Related
When I am trying to save an image from a tkinter canvas onto an external file, it doesn't keep the 16x16 resolution I would like which makes it blurry.
I was expecting no blur and it to keep the 16x16 resolution. I tried two methods of saving the file
The first method I tried is this:
self.sprite = tk.Canvas(self.root, width=16, height=16)
def get_clicked_pos(self, event):
y, x = event.y, event.x
gap = 288//16
row = y//gap # row and column are the wrong way but i found it easier to keep it that way instead of changing it all
col = x//gap
self.spriteGrid[row][col].tile = self.colour
self.spriteDraw(gap) # draws on the large canvas
self.sprite.create_rectangle(col, row, col+1, row+1, fill=self.colour, outline="")
def save_as_png1(self):
ps = self.sprite.postscript(width=16, height=16)
image = PIL.Image.open(io.BytesIO(ps.encode('utf-8')))
image.save('sprite.png')
In this method I made two canvases. One that is large and visible but a 16x16 grid so you can draw and one that is the correct size and not visible. When you draw on the large canvas it mimics it on the small canvas and when you save it turns the correct size canvas into a postscript and makes it a sprite. However, it doesn't work and creates this:
enter image description here method 1
The second method I tried is:
self.image1 = PIL.Image.new("RGB", (16, 16), "white")
self.draw = PIL.ImageDraw.Draw(self.image1)
def get_clicked_pos(self, event):
y, x = event.y, event.x
gap = 288//16
row = y//gap # row and column are the wrong way but i found it easier to keep it that way instead of changing it all
col = x//gap
self.spriteGrid[row][col].tile = self.colour
self.spriteDraw(gap)
self.draw.rectangle([(col, row), (col+1, row+1)], fill=self.colour)
self.sprite.create_rectangle(col, row, col+1, row+1, fill=self.colour, outline="")
def save_as_png2(self):
self.image1.save("alternate.png")
In this method, it copies what you draw onto a PIL image which then gets saved when you call save_as_image2. This provides similar results but in a 16x16 file
enter image description here method 2
The whole code is:
import tkinter as tk
from tkinter.colorchooser import askcolor
import PIL.Image
import PIL.ImageDraw
import io
class DrawWindow():
def __init__(self):
self.root = tk.Tk()
self.colour = "black"
self.menubar = tk.Menu(self.root)
self.optionsmenu = tk.Menu(self.menubar, tearoff=0)
self.optionsmenu.add_command(label="Save v1", command=self.save_as_png1)
self.optionsmenu.add_command(label="Save v2", command=self.save_as_png2)
self.menubar.add_cascade(menu=self.optionsmenu, label="Options")
self.root.config(menu=self.menubar)
self.sprFrame = tk.Frame(self.root)
self.sprFrame.pack()
self.spriteCanvas = tk.Canvas(self.sprFrame, height=288, width=288, bg="white")
self.spriteCanvas.pack(pady=5, padx=5)
self.pickColour = tk.Button(self.sprFrame, text="Pick colour", command=self.changeColour)
self.pickColour.pack(pady=5)
self.spriteGrid = []
for i in range(16):
self.spriteGrid.append([])
for j in range(16):
spot = Spot(i, j, 1, 16)
self.spriteGrid[i].append(spot)
self.sprite = tk.Canvas(self.root, width=16, height=16)
self.image1 = PIL.Image.new("RGB", (16, 16), "white")
self.draw = PIL.ImageDraw.Draw(self.image1)
self.spriteCanvas.bind("<Button-1>", self.get_clicked_pos)
self.root.mainloop()
def get_clicked_pos(self, event):
y, x = event.y, event.x
gap = 288//16
row = y//gap # row and column are the wrong way but i found it easier to keep it that way instead of changing it all
col = x//gap
self.spriteGrid[row][col].tile = self.colour
self.spriteDraw(gap)
self.draw.rectangle([(col, row), (col+1, row+1)], fill=self.colour)
self.sprite.create_rectangle(col, row, col+1, row+1, fill=self.colour, outline="")
def changeColour(self):
self.colour = askcolor(title="Sprite Colour")[1]
def save_as_png1(self):
ps = self.sprite.postscript(width=16, height=16)
image = PIL.Image.open(io.BytesIO(ps.encode('utf-8')))
image.save('sprite.png')
def save_as_png2(self):
self.image1.save("alternate.png")
def spriteDraw(self, gap):
for i in self.spriteGrid:
for j in i:
if not(j.tile==None):
self.spriteCanvas.create_rectangle(j.y*gap, j.x*gap, j.y*gap+gap, j.x*gap+gap, fill=j.tile)
class Spot:
def __init__(self, row, col, width, total_rows):
self.row = row
self.col = col
self.x = row * width
self.y = col * width
self.tile = None
self.width = width
self.total_rows = total_rows
DrawWindow()
I figured it out, method two works but I was using self.draw.rectangle() when I should've been using self.image1.putpixel().
So for anyone wanting the finished code it is:
import tkinter as tk
from tkinter.colorchooser import askcolor
import PIL.Image
import PIL.ImageDraw
class DrawWindow():
def __init__(self):
self.root = tk.Tk()
self.colour = ((0, 0, 0), "black")
self.menubar = tk.Menu(self.root)
self.optionsmenu = tk.Menu(self.menubar, tearoff=0)
self.optionsmenu.add_command(label="Save image", command=self.save_as_png)
self.menubar.add_cascade(menu=self.optionsmenu, label="Options")
self.root.config(menu=self.menubar)
self.sprFrame = tk.Frame(self.root)
self.sprFrame.pack()
self.spriteCanvas = tk.Canvas(self.sprFrame, height=288, width=288, bg="white")
self.spriteCanvas.pack(pady=5, padx=5)
self.pickColour = tk.Button(self.sprFrame, text="Pick colour", command=self.changeColour)
self.pickColour.pack(pady=5)
self.spriteGrid = []
for i in range(16):
self.spriteGrid.append([])
for j in range(16):
spot = Spot(i, j, 1, 16)
self.spriteGrid[i].append(spot)
self.image1 = PIL.Image.new("RGB", (16, 16), "white")
self.spriteCanvas.bind("<Button-1>", self.get_clicked_pos)
self.root.mainloop()
def get_clicked_pos(self, event):
y, x = event.y, event.x
gap = 288//16
row = y//gap # row and column are the wrong way but i found it easier to keep it that way instead of changing it all
col = x//gap
self.spriteGrid[row][col].tile = self.colour[1]
self.spriteDraw(gap)
self.image1.putpixel((col, row), self.colour[0])
def changeColour(self):
self.colour = askcolor(title="Sprite Colour")
print(self.colour)
def save_as_png(self):
self.image1.save("sprite.png")
def spriteDraw(self, gap):
for i in self.spriteGrid:
for j in i:
if not(j.tile==None):
self.spriteCanvas.create_rectangle(j.y*gap, j.x*gap, j.y*gap+gap, j.x*gap+gap, fill=j.tile)
class Spot:
def __init__(self, row, col, width, total_rows):
self.row = row
self.col = col
self.x = row * width
self.y = col * width
self.tile = None
self.width = width
self.total_rows = total_rows
DrawWindow()
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()
I am trying to write a pathfinding algorithm in python. The user is supposed to select a starting point by hovering the mouse of a field and pressing s. The field should now change the color.
However, I can't figure out what is wrong with my code. I am only able to color to color the fields from top left corner to the bottom right corner. In the code, Im printing out the objectID in console, which shows that there is maybe something wrong with the way of how I created the rectangles.
I'm creating the rectangles in the draw_grid method in the Window class and coloring the fields in the select_start_node method.
import tkinter as tk
class Window:
def __init__(self):
self.height = 600
self.width = 600
self.grid_list = {x for x in range(0, 600)}
self.grid = []
self.grid_dict = {}
self.root = tk.Tk()
self.root.geometry("600x600")
self.root.resizable(False, False)
self.canvas = tk.Canvas(self.root, width=self.width,
height=self.height, background="white")
self.canvas.bind("s", self.select_start_node)
self.canvas.bind("<1>", lambda event:
self.canvas.focus_set())
def draw_grid(self):
print(self.grid)
for x in self.grid_list:
if x % 30 == 0:
self.grid.append(x)
else:
pass
print(self.grid)
for x in self.grid:
for y in self.grid:
print(x, y+30)
rec = self.canvas.create_rectangle(x, x, y+30, y+30)
self.canvas.pack()
def select_start_node(self, event):
print(event.x, event.y)
x = self.canvas.canvasx(event.x)
y = self.canvas.canvasy(event.y)
item = self.canvas.find_closest(x, y)
p = self.canvas.coords(item)
print(item)
print(p)
self.canvas.create_rectangle(p[0], p[0], p[0]+30, p[0]+30, fill="red")
def main():
node_list = []
cord_list = []
window = Window()
window.draw_grid()
window.root.mainloop()
if __name__ == "__main__":
main()
I don't know the entire design of you game, but suggest that you do things differently with respect to the grid of rectangles. In the code below self.grid is a 2-dimensional list-of-lists and each entry is a Canvas rectangle object. This make selecting and changing one of them relatively each because canvas.find_closest(x, y) will give you the object id of the associated rectangle object directly, which makes changing its fill color trivial.
Because of that, I also changed it so you can just click on one of the rectangles to change it instead of moving the mouse cursor and then pressing a key.
Also note that I also got rid of most those hardcoded numerical constants you were using all over the place, which makes the code more flexible in case you decide to change one of them at a later time.
import tkinter as tk
class Window:
def __init__(self):
self.cell_size = 30
self.height = 600
self.width = 600
self.hz_cells = self.width // self.cell_size # Number of horizontal cells.
self.vt_cells = self.height // self.cell_size # Number of vertical cells.
# Preallocate 2D grid (list-of-lists).
self.grid = [[None for _ in range(self.hz_cells)]
for _ in range(self.vt_cells)]
self.root = tk.Tk()
self.root.geometry("%sx%s" % (self.width, self.height))
self.root.resizable(False, False)
self.canvas = tk.Canvas(self.root, width=self.width,
height=self.height, background="white")
self.canvas.pack()
self.canvas.bind("<1>", self.select_start_node)
# You can still do it this way if you want.
# self.canvas.bind("s", self.select_start_node)
# self.canvas.bind("<1>", lambda event: self.canvas.focus_set())
def draw_grid(self):
""" Fill Canvas with a grid of white rectangles. """
for i in range(self.hz_cells):
x = i * self.cell_size
for j in range(self.vt_cells):
y = j * self.cell_size
self.grid[i][j] = self.canvas.create_rectangle(
x, y, x+self.cell_size, y+self.cell_size, fill="white")
def select_start_node(self, event):
""" Change the color of the rectangle closest to x, y of event. """
x = self.canvas.canvasx(event.x)
y = self.canvas.canvasy(event.y)
selected_rect = self.canvas.find_closest(x, y)
if selected_rect:
self.canvas.itemconfigure(selected_rect, fill="red") # Change color.
def main():
node_list = []
cord_list = []
window = Window()
window.draw_grid()
window.root.mainloop()
if __name__ == "__main__":
main()
I'm creating in tkinter a Crop Tool that is similar in Photoshop. This code has a function that is supposed to crop a moveable image within the cropping box (2 in x 2 in, passport size, and so on). The problem is, the code often crops portions of the image outside the box.
For example, if I have a portrait and aimed the face at the rectangle, the code would crop the hat instead, or anywhere but the face.
I tried to use bbox, event objects, etc. but the measurements end up wrong. Please help me. Thanks.
Here is a partial code. Sorry if it's a quite lengthy.
from tkinter import *
from tkinter import ttk
import tkinter as tk
from tkinter import messagebox
from tkinter.filedialog import askopenfilename, asksaveasfilename
from PIL import Image, ImageTk
class PictureEditor:
# Quits when called
#staticmethod
# Opens an image
def open_app(self, event=None):
self.canvas.delete(ALL)
# Opens a window to choose a file=
self.openfile = askopenfilename(initialdir = # "Filename here")
if self.openfile:
with open(self.openfile) as _file:
# if file is selected by user, I'm going to delete
# the contents inside the canvas widget
self.canvas.delete(1.0, END)
self.im = Image.open(self.openfile)
self.image = ImageTk.PhotoImage(self.im)
self.a1 = self.canvas.create_image(0, 0, anchor=NW,
image=self.image, tags="image")
self.image_dim = self.canvas.bbox(self.a1)
self.imx = self.image_dim[0]
self.imy = self.image_dim[1]
# updating text widget
window.update_idletasks()
def on_drag(self, event):
# record the item and its location
self._drag_data["item"] = self.canvas.find_closest(event.x, event.y)[0]
self._drag_data["x"] = event.x
self._drag_data["y"] = event.y
self.origx = event.x
self.origy = event.y
def on_release(self, event):
# when I release the mouse, this happens
# reset the drag information
self._drag_data["item"] = None
self._drag_data["x"] = 0
self._drag_data["y"] = 0
self.newx = event.x
self.newy = event.y
# Measures mouse movement from one point to another
self.movex = self.origx - self.newx
self.movey = self.origy - self.newy
def on_motion(self, event):
# handles the dragging of an object
# compute how much the mouse has moved
delta_x = event.x - self._drag_data["x"]
delta_y = event.y - self._drag_data["y"]
# move the object the appropriate amount
self.canvas.move(self._drag_data["item"], delta_x, delta_y)
# record the new position
self._drag_data["x"] = event.x
self._drag_data["y"] = event.y
def draw(self, event, x1=None, y1=None,x2=None,y2=None):
# deleting contents of border, if any.
try:
self.canvas.delete(self.border)
except:
pass
# if an item is selected
selection = self.combo.get()
if selection == 'No Crop':
x1, y1, x2, y2 = None, None, None, None
if selection == '2 in x 2 in':
x1, y1, x2, y2 = self.imx, self.imy, self.imx + 200, self.imy + 200
if selection == '1 in x 1 in':
x1, y1, x2, y2 = self.imx, self.imy, self.imx + 100, self.imy + 100
if selection == 'Passport Size':
x1, y1, x2, y2 = self.imx, self.imy, self.imx + 132.28, self.imy
+170.079
if x1 != None or y1 != None or x2 != None or y2 != None:
self.dimensions = {"x1":x1, "y1":y1, "x2":x2, "y2":y2}
width = 5
self.border = self.canvas.create_rectangle(x1+ width, y1 +
width, x2 + width, y2 + width, width=width, outline="#ffffff", fill ="",
tags = "rectangle")
else:
pass
def crop(self, event=None):
# cropping the image
try:
self.crop_image = self.im.crop((self.dimensions["x1"] +
self.movex,
self.dimensions["y1"] + self.movey,
self.dimensions["x2"] + self.movex,
self.dimensions["y2"] + self.movey))
except:
print("cropping failed")
return 1
self.newly_cropped = ImageTk.PhotoImage(self.crop_image)
try:
new_image = self.canvas.create_image(120, 120,
image=self.newly_cropped)
print("Image is cropped")
except:
print("Cropping failed")
def __init__(self,window):
frame1 = Frame(bg='red')
frame1.pack(side=TOP, fill=X)
frame2height = 600
frame2width = 600
frame2 = Frame(window, bd=2, relief=SUNKEN)
frame2.pack(side=LEFT, fill=X)
frame3 = Frame(bg='green')
frame3.pack(side=LEFT, fill=X)
# Button that open pictures
open = Button(frame1, text='Open Pic', padx=20, command =
self.open_app)
open.pack(pady=5, padx=5, side=LEFT)
# Creating a canvas widget
self.canvas = tk.Canvas(frame2, height=frame2height,
width=frame2width,
bg='gray')
self.xsb = Scrollbar(frame2, orient="horizontal",
command=self.canvas.xview)
self.ysb = Scrollbar(frame2, orient="vertical",
command=self.canvas.yview)
self.canvas.configure(yscrollcommand=self.ysb.set,
xscrollcommand=self.xsb.set)
self.canvas.configure(scrollregion=(0, 0, 1000, 1000))
# keeps track of data being dragged
self._drag_data = {"x": 0, "y": 0, "item": None}
# creating image and crop border
self.canvas.tag_bind("image","<ButtonPress-1>", self.on_drag)
self.canvas.tag_bind("image","<ButtonRelease-1>", self.on_release)
self.canvas.tag_bind("image","<B1-Motion>", self.on_motion)
# widget positions in frame2
self.xsb.pack(side=BOTTOM, fill=X)
self.canvas.pack(side=LEFT)
self.ysb.pack(side=LEFT, fill=Y)
self.combo = ttk.Combobox(frame1)
# Combobox selections
self.combo['values'] = ('No Crop', '2 in x 2 in', '1 in x 1 in',
'Passport Size')
self.combo.current(0)
self.combo.pack(pady=5, padx=5, side=LEFT)
self.combo.bind("<Button-1>", self.draw)
# Button that crops picture
self.crop = Button(frame1, text='Crop Pic', padx=20,
command=self.crop)
self.crop.pack(pady=5, padx=5, side=LEFT)
# this window has all the properties of tkinter.
# .Tk() declares this variable as the frame
window = tk.Tk()
# .title() will input whatever title you want for the app
window.title("ID Picture Generator")
# .geometry() sets the size in pixels of what the window will be
window.geometry("800x600")
app = PictureEditor(window)
# runs everything inside the window
window.mainloop()
I tried to use the Tkinter library for my small project in python. I create a 500 by 500 square with 10000 small square in it.
And I want each small square turns black when user click on it. Can someone please tell me why, I would really appreciate it. Here is the graphics code:
from Tkinter import *
from button import *
class AppFrame(Frame):
def __init__(self):
self.root = Tk()
self.root.geometry = ("1000x1000")
self.f = Frame(self.root, relief = 'sunken', width = 600, height = 600)
self.w = Canvas(self.f,width = 505, height =505)
##get the x, y value whenever the user make a mouse click
self.w.bind("<Button-1>", self.xy)
self.bolist = []
for k in range(1,101):
for i in range(1, 101):
button = Buttons(self.w, i * 5, k * 5, i * 5 + 5, k * 5 + 5)
self.bolist.append(button)
self.f.grid(column =0, columnspan = 4)
self.w.grid(column = 0)
self.root.mainloop()
def xy (self, event):
self.x, self.y = event.x, event.y
print (self.x, self.y)
##check each button if it's clicked
for hb in self.bolist:
if hb.clicked(self.x, self.y):
print ("hurry")
hb.activate()
And
##button.py
from Tkinter import *
class Buttons:
def __init__(self,canvas,bx,by,tx,ty):
self.canvas = canvas
self.rec = canvas.create_rectangle((bx,by,tx,ty),fill = "lightgray",
activefill= 'black', outline = 'lightgray')
self.xmin = bx
self.xmax = tx
self.ymin = by
self.ymax = ty
##print (bx, by, tx, ty)
def clicked(self, px, py):
return (self.active and self.xmin <= px <= self.xmax and
self.ymin <= py <= self.ymax)
def activate(self):
self.canvas.itemconfigure(slef.rec, fill = 'black')
self.active = True
The problem is that you don't initialize the active attribute, so it doesn't exist until the cell becomes active. To fix that, add self.active = False inside the __init__ method of Buttons.
You also have a typo in this line (notice you use slef rather than self):
self.canvas.itemconfigure(slef.rec, fill = 'black')
Instead of a global binding on the canvas, it would be more efficient to set a binding on each individual rectangle. You can then use the binding to pass the instance of the Buttons class to the callback. This way you don't have to iterate over several thousand widgets looking for the one that was clicked on.
To do this, use the tag_bind method of the canvas. You can make it so that your main program passes in a reference to a function to call when the rectangle is clicked, then the binding can call that method and pass it a reference to itself.
For example:
class Buttons:
def __init__(self,canvas,bx,by,tx,ty, callback):
...
self.rec = canvas.create_rectangle(...)
self.canvas.tag_bind(self.rec, "<1>",
lambda event: callback(self))
...
class AppFrame(Frame):
def __init__(...):
...
button = Buttons(..., self.callback)
...
def callback(self, b):
b.activate()
Here, I looked at your code, debugged it, and made some adjustments. It works now.
Just keep both the scripts in one folder and run your AppFrame script (the second one in this answer)
##button.py
from Tkinter import *
class Buttons:
def __init__(self,canvas,bx,by,tx,ty):
self.canvas = canvas
self.rec = canvas.create_rectangle((bx,by,tx,ty),fill = "lightgray", activefill= 'black', outline = 'lightgray')
self.xmin = bx
self.xmax = tx
self.ymin = by
self.ymax = ty
##print (bx, by, tx, ty)
def clicked(self, px, py):
return (self.xmin <= px <= self.xmax and
self.ymin <= py <= self.ymax)
def activate(self):
self.canvas.itemconfigure(self.rec, fill = 'black')
AND
from Tkinter import *
from button import *
class AppFrame(Frame):
def __init__(self):
self.root = Tk()
self.root.geometry = ("1000x1000")
self.f = Frame(self.root, relief = 'sunken', width = 600, height = 600)
self.w = Canvas(self.f,width = 505, height =505)
##get the x, y value whenever the user make a mouse click
self.w.bind("<Button-1>", self.xy)
self.bolist = []
for k in range(1,101):
for i in range(1, 101):
button = Buttons(self.w, i * 5, k * 5, i * 5 + 5, k * 5 + 5)
self.bolist.append(button)
self.f.grid(column =0, columnspan = 4)
self.w.grid(column = 0)
self.root.mainloop()
def xy (self, event):
self.x, self.y = event.x, event.y
print (self.x, self.y)
##check each button if it's clicked
for hb in self.bolist:
if hb.clicked(self.x, self.y):
print ("hurry")
hb.activate()
newApp = AppFrame()