-1

I have a problem related to a TKinter GUI I am creating, but the problem is not necessarily specific to this library.

Background

I am currently in the advanced stage of a python self-learning course. The learning module I am on is covering TKinter for creating interactive GUI's. I am making a game whereby randomly generated numbered buttons must be clicked in succession in the quickest time possible.

Brief: https://edube.org/learn/pcpp1-4-gui-programming/lab-the-clicker

Problem

  • Under my class, game_grid, I have created an instance variable; 'self.holder', a 25 entry dictionary of {Key : TkinterButtonObject} form

  • When calling this instance variable for use in a class method, I get the following error:

AttributeError: 'game_grid' object has no attribute 'holder'

  • I have a print statement under class init which proves this attribute has been successfully created. I have made sure my spacing and tabs are all OK, and have tried every location for this variable, including using as a class variable, and a global variable to no avail - as it is an semi-complex object. I don't see what difference it should make, but any ideas would be much appreciated. I am also aware this could be done without classes, but I am trying to adopt DRY principles and orthogonality in all of my programs.

Thanks in advance.

Full Code:

import tkinter as tk
from tkinter import*
import random
from tkinter import messagebox
import time

win   = tk.Tk()

class game_grid:

    def __init__(self, win):
        self.last_number = 0
        self.number_buttons = {}
        self.row_count = 0
        self.column_count = 0
        #Generate a list of 25 random numbers
        self.number_list = random.sample(range(0, 999), 25)
        #Puts the numbers in a dictionary (number : buttonobject)
        self.holder = {i: tk.Button(win, text = str(i), command = game_grid.select_button(self, i)) for i in self.number_list}
        #pack each object into window by iterating rows and columns
        for key in self.holder:
            self.holder[key].grid(column = self.column_count, row = self.row_count)
            if self.column_count < 4:
                self.column_count += 1
            elif self.column_count == 4:
                self.column_count = 0
                self.row_count += 1
        print(self.holder)


    def select_button(self, number):
        if number > self.last_number:
            self.holder[number].config(state=tk.DISABLED)
            self.last_number = number
        else:
            pass


class stopclock():

    def __init__(self):
        #Stopclock variable initialisation
        self.time_begin  = 0
        self.time_end    = 0
        self.time_elapsed= 0

    def start(self):
        if self.time_begin == 0:
            self.time_begin = time.time()
            return("Timer started\nStart time: ", self.time_begin)
        else:
            return("Timer already active")

    def stop(self):
        self.time_end = time.time()
        self.time_elapsed = time_end - time_begin
        return("Timer finished\nEnd time: ", time_begin,"\nTime Elapsed: ", time_elapsed)       

play1 = game_grid(win)

win.mainloop()
Tool
  • 17
  • 3
  • ***AttributeError: 'game_grid' object has no attribute 'holder'***: That's correct, you try to use `self.holder` before it's defined. – stovfl Mar 08 '20 at 13:30

2 Answers2

0

Perhaps you meant:

command = self.select_button(self, i)

Update:

Though from research:How to pass arguments to a Button command in Tkinter?

It should be:

command = lambda i=i: self.select_button(i)
quamrana
  • 37,849
  • 12
  • 53
  • 71
  • Thanks so much - such a simple edit that now works flawlessly, i can crack on with refining my code. I now have a more accurate view i now have of how my code is seen and executed which will undoubtedly come in handy again. Also, that i should scrub-up on lambdas! – Tool Mar 08 '20 at 13:42
0

You call select_button from inside the dict comprehension of holder. select_button then tries to use holder, but it is not yet defined. You don't want to actually call select_button, but assign a function to the button, like that:

self.holder = {i: tk.Button(window, text=str(i), command=lambda i=i: self.select_button(i)) for i in self.number_list}
chuck
  • 1,420
  • 4
  • 19