Question

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
Was it helpful?

Solution

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()

OTHER TIPS

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()
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top