Yes, you can put a binding on any widget. A common thing to do is use a label widget, though you can also use a canvas and draw any shape you want. If you store all of the widgets in a dictionary using the row and column as a key, it's very easy for the callback to know what you clicked on (though, of course, event.widget
will also tell you).
Here's a quick example that uses labels, and illustrates how to pass the row and column into a callback. It just creates a single tic-tac-toe board for brevity:
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent, background="bisque")
self.canvas = tk.Canvas(self, width=400, height=400)
board = TicTacToe(self)
board.pack(padx=20, pady=20)
class TicTacToe(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent, background='black')
self.player = "X"
self.cell = {}
for row in range(3):
for col in range(3):
cell = tk.Label(self, background="darkgray", foreground="white", width=2)
cell.bind("<1>", lambda event, col=col, row=row: self.on_click(row, col))
cell.grid(row=row, column=col, sticky="nsew", padx=1, pady=1)
self.cell[(row,col)] = cell
def on_click(self, row, col):
current = self.cell[(row, col)].cget("text")
if current == "X" or current == "O":
self.bell()
return
self.cell[(row, col)].configure(text=self.player)
self.player = "X" if self.player == "O" else "O"
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()