Jonathan
Jonathan

Reputation: 284

callback function not defined

I'm trying to learn how to make GUI with tkinter in Python and I'm getting an error that my callback function is undefined. Basically I want a grid of white squares and when you click on the square it will become red.

from tkinter import *

root = Tk()

class Application(Frame):
    def __init__(self, master, *args, **kwargs):
        Frame.__init__(self, master, *args, ** kwargs)
        self.createWidgets()

    def mouse_click(self):
        self.squares.bg = 'red'

    def createWidgets(self):
        for i in range(10):
            for j in range(10):
                self.squares = Button(self, height = 3, width = 7, bg = 'white',
                                      command = lambda: mouse_click())
                self.squares.grid(row = i, column = j)

Application(root).grid()
root.mainloop()

Upvotes: 0

Views: 4074

Answers (2)

R Sahu
R Sahu

Reputation: 206707

You need to use ommand = lambda: self.mouse_click().

However, that does not solve all the problems. The other problem is that you are creating a 2D grid of buttons but are storing only the last of them in your class.

The line

                self.squares = Button(...)

stores only one button. You end up storing a handle to the last Button. The other Buttons that you created in previous steps are lost to your class.

Here's my suggestion (not verified):

class Application(Frame):
   def __init__(self, master, *args, **kwargs):
      Frame.__init__(self, master, *args, ** kwargs)
      self.createWidgets()

   def mouse_click(self, i, j):
      self.squares[i][j].bg = 'red'

   def createWidgets(self):

      # Creeate a 2D array that contains None in all the elements.
      self.squares = [[None for x in range(10)] for y in range(10)]

      # Fill up the array.
      for i in range(10):
         for j in range(10):
            # Create a button and display it in the i-th row and j-th
            # column in a grid.
            button = Button(self,
                            height = 3,
                            width = 7,
                            bg = 'white',
                            command = lambda: self.mouse_click(i, j))
            button.grid(row = i, column = j)

            # Store the button for the callback function
            self.squares[i][j] = button

Upvotes: 1

Aran-Fey
Aran-Fey

Reputation: 43276

There are multiple problems in your code.

  1. mouse_click is an instance method, so it must be referenced as self.mouse_click.
  2. You're overwriting self.squares in each iteration of your loop, so in the end it references the last button you created. This isn't useful and can be removed.

The clean way to solve this is to rewrite the mouse_click method to take a button as an argument:

def mouse_click(self, square):
    square['bg'] = 'red'

And then give each button's command function a reference to the button itself. This can be done with functools.partial:

def createWidgets(self):
    for i in range(10):
        for j in range(10):
            square = Button(self, height = 3, width = 7, bg = 'white')
            square['command'] = functools.partial(self.mouse_click, square)
            square.grid(row = i, column = j)

This makes it so that the button's click event handler receives the button as an argument. With these two changes, everything works as intended.

Upvotes: 2

Related Questions