Home > database >  Python Tkinter name [button] is not defined in a function
Python Tkinter name [button] is not defined in a function

Time:09-03

I'm trying to make my button change on and off whenever the user clicks on it. But I keep getting a name error. I've tried changing the order of the function and button creation. If I place the button creation above the function then it won't be able to call the function.

Here is my Code:

#import all of tkinter and also updated widgets
from tkinter import *
from tkinter import ttk
import main

#sets up main application window
root = Tk()
root.title("Winstreak Counter GUI")

#creating a content frame (frame widget, hold content of user interface)
#we do this because it'll allow us to control the background color and allow for more themed widgets
mainframe = ttk.Frame(root, padding="3 3 12 12")
mainframe.grid(column=10, row=10, sticky=(N, W, E, S))
root.columnconfigure(10, weight=1)
root.rowconfigure(10, weight=1)

f = open('/Users/ripkoye/Desktop/WinstreakProject/winstreak.txt', 'r')
#store the information into a variable
winstreak = StringVar(mainframe)
winstreak.set(f.read())
#creates a winstreaklabel
winstreaklabel = ttk.Label(mainframe,width=20, padding='5 5 5 5')
winstreaklabel.grid()
winstreaklabel['textvariable'] = winstreak
#assigns the text to the variable named winstreak
f.close()

is_on = True

def switch():
    global is_on

    if is_on:
        buttonOnOff.config(image = off)
        is_on = False
    else:
        buttonOnOff.config(image = on)
        is_on = True

on = PhotoImage(file='on.png')
off = PhotoImage(file='off.png')
buttonOnOff = ttk.Button(mainframe, image=on, width=0.5, padding='5 5 5 5', command=switch())
buttonOnOff.grid()


root.mainloop()

This is the error:

Traceback (most recent call last):
  File "/Users/ripkoye/Desktop/WinstreakProject/gui.py", line 42, in <module>
    buttonOnOff = ttk.Button(mainframe, image=on, width=0.5, padding='5 5 5 5', command=switch())
  File "/Users/ripkoye/Desktop/WinstreakProject/gui.py", line 34, in switch
    buttonOnOff.config(image = off)
NameError: name 'buttonOnOff' is not defined

CodePudding user response:

You calling the switch function directly, but you need to give callable object to command parameter.

buttonOnOff will be created when ttk.Button return. But before the ttk.Button return, switch func will be called because you do command=switch().

You need to modify it like ;

buttonOnOff = ttk.Button(mainframe, image=on, width=0.5, padding='5 5 5 5', command=switch)

CodePudding user response:

@VeyselOlgun is partly correct, you'll run into an issue with buttonOnOff's command parameter - you should change it from switch() to switch

However, the issue you're having is caused by the fact that buttonOnOff is outside the scope of your switch() function.

A clean solution is to wrap your button and it's associated command callback in a simple class like so

#import all of tkinter and also updated widgets
import tkinter as tk  # star imports are not your friend!
from tkinter import ttk
import main

#sets up main application window
root = tk.Tk()  # properly namespaced w/ 'tk.' prefix
root.title("Winstreak Counter GUI")

#creating a content frame (frame widget, hold content of user interface)
#we do this because it'll allow us to control the background color and allow for more themed widgets
mainframe = ttk.Frame(root, padding="3 3 12 12")
mainframe.grid(column=10, row=10, sticky=(N, W, E, S))

root.columnconfigure(10, weight=1)
root.rowconfigure(10, weight=1)

wsp_path = '/Users/ripkoye/Desktop/WinstreakProject/winstreak.txt'
#store the information into a variable
winstreak_var = tk.StringVar(mainframe)
with open(wsp_path, 'r') as wsp:
    winstreak_var.set(wsp.read())

#creates a winstreaklabel
winstreaklabel = ttk.Label(
    mainframe,
    textvariable=winstreak_var,  # now you can set textvariable here
    width=20,
    padding='5 5 5 5'
)
winstreaklabel.grid()

ButtonOnOff(mainframe)  # call your button class with mainframe as parent


# I like to inherit from tk.Frame as my generic base class
class ButtonOnOff(tt.Frame):  
    def __init__(self, parent):
        super().__init__(parent)  # init the container frame
        # 'parent' is set to 'mainframe' when you init the class
        self.is_on = True
        self.on = tk.PhotoImage(file='on.png')
        self.off = tk.PhotoImage(file='off.png')
        # create your button
        self.buttonOnOff = ttk.Button(
            self,  # the button is now a child of the ttk.Frame container
            image=self.on,
            width=0.5,
            padding='5 5 5 5',
            command=self.switch  # bind to the 'switch' method below
        )
        button.grid()  # populate the button

    def switch(self):
        if self.is_on:
            self.buttonOnOff.config(image=self.off)
            self.is_on = False
        else:
            self.buttonOnOff.config(image=self.on)
            self.is_on = True

Edit - I took the liberty of cleaning things up a bit using a context manager to handle the reading of winstreak.txt. This handles opening, reading, and closing for you in one fell swoop!

Another Edit - I've gotten rid of the star import in favor of import tkinter as tk and properly namespaced the necessary classes with tk.. See here for more info on why star imports can be a headache

  • Related