Rob Murray
Rob Murray

Reputation: 1913

Python 3.4 tkinter checkbutton variable handling not working / responding

EDIT This has now moved to Python 3.4.3 tkinter - Program freezes on declaration of IntVar or any other tkinter data type as that is the root of the problem which has now been solved. Basically, NEVER name ANYTHING "master" in Python 3.x using tkinter, it causes an infinite loop :( . My full code has now been removed as it is my coursework and wouldn't want people nicking it :D EDIT

I'm relatively new to tkinter and can't see where I'm going wrong. I have tried using a StringVar() and a regular string to get multiple entry fields to disable when a Checkbutton is on. Here's the frame that the problem is in:

class CActivity(tk.Frame):

def Clear(self):
    pass # To be completed

def Today(self):
    if self.todayVar == "ON":
        self.day.configure(state="disabled")
        self.month.configure(state="disabled")
        self.year.configure(state="disabled")
    else:
        self.day.configure(state="normal")
        self.month.configure(state="normal")
        self.year.configure(state="normal")

def createWidgets(self):

    self.title = tk.Label(self)
    self.title["text"] = "Add an Activity"
    self.title["font"] = ("Times New Roman",30)
    self.title["fg"] = "purple"
    self.title.grid(row=0,column=0,sticky="W",padx=5,pady=5)

    self.todayVar = ""

    tk.Label(self,text="Activity Name:",font=("Times New Roman",15)).grid(row=1,column=0,sticky="W",padx=5,pady=5)
    name = tk.Entry(self).grid(row=1,column=1,columnspan=3,sticky="E",padx=5,pady=5)
    tk.Label(self,text="Priority:",font=("Times New Roman",15)).grid(row=2,column=0,sticky="W",padx=5,pady=5)
    priority = tk.Checkbutton(self).grid(row=2,column=1,sticky="W",padx=0,pady=5)
    tk.Label(self,text="Today?",font=("Times New Roman",15)).grid(row=3,column=0,sticky="W",padx=5,pady=5)
    today = tk.Checkbutton(self,onvalue="ON",offvalue="OFF",variable=self.todayVar,command=self.Today).grid(row=3,column=1,sticky="W",padx=0,pady=5) #problem possibly on this line
    tk.Label(self,text="Date (DD/MM/YYYY):",font=("Times New Roman",15)).grid(row=4,column=0,sticky="W",padx=5,pady=5)

    day = tk.Entry(self,width=2).grid(row=4,column=1,sticky="W",padx=2,pady=5)
    month = tk.Entry(self,width=2).grid(row=4,column=2,sticky="W",padx=2,pady=5)
    year = tk.Entry(self,width=4).grid(row=4,column=3,sticky="W",padx=2,pady=5)

    self.clear = tk.Button(self, command=self.Clear)
    self.clear["text"] = "Clear"
    self.clear["font"] = ("Times New Roman",15)
    self.clear["fg"] = "red"
    self.clear.grid(row=7,column=4,sticky="WE",padx=5,pady=5)

    self.back = tk.Button(self)
    self.back["text"] = "Back"
    self.back["font"] = ("Times New Roman",15)
    self.back["fg"] = "red"
    self.back["command"] = self.parent.Menu
    self.back.grid(row=8,column=4,sticky="WE",padx=5,pady=5)

def __init__(self, parent):

    tk.Frame.__init__(self, parent)
    self.pack()
    self.parent = parent
    self.createWidgets()

And here it is with a StringVar() instead of a standard Python string:

class CActivity(tk.Frame):

def Clear(self):
    pass # To be completed

def Today(self):
    if self.todayVar.get() == "ON":
        self.day.configure(state="disabled")
        self.month.configure(state="disabled")
        self.year.configure(state="disabled")
    else:
        self.day.configure(state="normal")
        self.month.configure(state="normal")
        self.year.configure(state="normal")

def createWidgets(self):

    self.title = tk.Label(self)
    self.title["text"] = "Add an Activity"
    self.title["font"] = ("Times New Roman",30)
    self.title["fg"] = "purple"
    self.title.grid(row=0,column=0,sticky="W",padx=5,pady=5)

    self.todayVar = tk.StringVar()

    tk.Label(self,text="Activity Name:",font=("Times New Roman",15)).grid(row=1,column=0,sticky="W",padx=5,pady=5)
    name = tk.Entry(self).grid(row=1,column=1,columnspan=3,sticky="E",padx=5,pady=5)
    tk.Label(self,text="Priority:",font=("Times New Roman",15)).grid(row=2,column=0,sticky="W",padx=5,pady=5)
    priority = tk.Checkbutton(self).grid(row=2,column=1,sticky="W",padx=0,pady=5)
    tk.Label(self,text="Today?",font=("Times New Roman",15)).grid(row=3,column=0,sticky="W",padx=5,pady=5)
    today = tk.Checkbutton(self,onvalue="ON",offvalue="OFF",variable=self.todayVar,command=self.Today).grid(row=3,column=1,sticky="W",padx=0,pady=5)
    tk.Label(self,text="Date (DD/MM/YYYY):",font=("Times New Roman",15)).grid(row=4,column=0,sticky="W",padx=5,pady=5)

    day = tk.Entry(self,width=2).grid(row=4,column=1,sticky="W",padx=2,pady=5)
    month = tk.Entry(self,width=2).grid(row=4,column=2,sticky="W",padx=2,pady=5)
    year = tk.Entry(self,width=4).grid(row=4,column=3,sticky="W",padx=2,pady=5)

    self.clear = tk.Button(self, command=self.Clear)
    self.clear["text"] = "Clear"
    self.clear["font"] = ("Times New Roman",15)
    self.clear["fg"] = "red"
    self.clear.grid(row=7,column=4,sticky="WE",padx=5,pady=5)

    self.back = tk.Button(self)
    self.back["text"] = "Back"
    self.back["font"] = ("Times New Roman",15)
    self.back["fg"] = "red"
    self.back["command"] = self.parent.Menu
    self.back.grid(row=8,column=4,sticky="WE",padx=5,pady=5)

def __init__(self, parent):

    tk.Frame.__init__(self, parent)
    self.pack()
    self.parent = parent
    self.createWidgets()

In the case of the use of a standard string the program runs fine until you click on the Checkbutton at which point the Checkbutton goes grey then the program stops responding. In the case of the use of the StringVar() the tk window doesn't load at all, due to this frame being initialised during the initialisation of the window. Thanks for your help and if you would like the full code to help find the problem just let me know.

Upvotes: 0

Views: 2784

Answers (2)

PM 2Ring
PM 2Ring

Reputation: 55499

Here's a cut-down version of your code that functions correctly:

#!/usr/bin/env python

''' Toggle disable / normal of Tkinter widgets

    Written by PM 2Ring & R. Murray 2015.11.15
    See http://stackoverflow.com/q/33711472/4014959
'''

#Python 3 / Python 2 Tkinter import
try:
    import tkinter as tk
except ImportError:
    import Tkinter as tk


class CActivity(tk.Frame):
    def today_cb(self):
        if self.todayVar.get():
            state = "disabled"
        else:
            state = "normal"
        #print(state)

        self.day.configure(state=state)
        self.month.configure(state=state)
        self.year.configure(state=state)


    def create_widgets(self):
        title_text = ("Click the 'Today' Checkbutton to\n"
            "disable / enable date Entry widgets")
        title = tk.Label(self, text=title_text)
        title.grid(row=0, column=0, sticky="W", padx=5, pady=5)

        self.todayVar = tk.IntVar()

        tk.Label(self,text="Today").grid(row=1, column=1, 
            sticky="W", padx=0, pady=5)
        today = tk.Checkbutton(self, variable=self.todayVar, 
            command=self.today_cb)
        today.grid(row=2, column=1, sticky="W", padx=0, pady=5)

        #Date Entry widgets
        tk.Label(self,text="Day").grid(row=3, column=1,
            sticky="W", padx=2, pady=5)
        self.day = tk.Entry(self, width=2)
        self.day.grid(row=4, column=1, sticky="W", padx=2, pady=5)

        tk.Label(self,text="Month").grid(row=3, column=2,
            sticky="W", padx=2,pady=5)
        self.month = tk.Entry(self, width=2)
        self.month.grid(row=4, column=2, sticky="W", padx=2, pady=5)

        tk.Label(self,text="Year").grid(row=3, column=3,
            sticky="W", padx=2, pady=5)
        self.year = tk.Entry(self, width=4)
        self.year.grid(row=4, column=3, sticky="W", padx=2, pady=5)


    def __init__(self, parent):
        tk.Frame.__init__(self, parent)
        self.pack()
        self.parent = parent
        self.create_widgets()


if __name__ == '__main__':
    master = tk.Tk()
    master.title("Disable widgets demo")
    frame = CActivity(master)
    master.mainloop()

There are a couple of problems with your code.

Your createWidgets method saves the date Entry widgets into local variables, eg day, but they need to be in instance attributes, like self.day, so they can be accessed by the Checkbutton's callback method.

Another problem is that you're calling the .grid method on the same line as the widget constructor. That method returns None, so doing

day = tk.Entry(self,width=2).grid(row=4,column=1)

sets day to None.

It's ok to use .grid (or .pack) on the same line as the constructor if you don't need to save a reference to the widget, but it just won't work when you do need that reference. :)

I've simplified the Checkbutton's callback method slightly, to reduce code repetition. I got rid of the onvalue="ON",offvalue="OFF" in the Checkbutton's constructor since the default 0 and 1 is adequate for this task, IMHO, (but feel free to change it back if you like :) ), and since we're using a numerical state I'm using an IntVar rather than a string or StringVar.

I've also change the name of the methods to conform to PEP 8 conventions.


You still have some confusion with local variables vs instance variables in your code. day is not the same thing as self.day, so you need to fix that, eg,

day = tk.Entry(self,width=2)
self.day.grid(row=4,column=1,sticky="W",padx=2,pady=5)

needs to be

self.day = tk.Entry(self,width=2)
self.day.grid(row=4,column=1,sticky="W",padx=2,pady=5)

Also, bear in mind what Bryan Oakley has said about widget variable and textvariable attributes requiring a reference to an instance of a Tkinter StringVar, IntVar, etc.

Upvotes: 1

Bryan Oakley
Bryan Oakley

Reputation: 386362

The variable and textvariable attributes require a reference to an instance of a Tkinter StringVar, IntVar, DoubleVar or BooleanVar. You cannot use normal variables.

Second, you must use the get method of these variables in order to retrieve the values before comparing them in an if statement.

self.todayVar = StringVar()
...
today = tk.Checkbutton(..., variable=self.todayVar, ...)
...
if self.todayVar.get() == "ON":

Upvotes: 0

Related Questions