Checkbutton in tkinter is not storing values into variables - python

I'm just doing my first steps in tkinter and I'm stuck trying to figure out why this code is not working:
from tkinter import *
from tkinter import ttk
root = Tk()
spam = StringVar()
checkbutton = ttk.Checkbutton(
root, text="SPAM?", variable=spam, onvalue="Yes, SPAM!", offvalue="Boo, SPAM!")
checkbutton.pack()
print(spam.get())
root.mainloop()
The variable spam is empty, no matter if my checkbutton is checked or unchecked. Looking at the examples and documentation was a dead end too. Why is my variable still empty?

Replace:
print(spam.get())
with:
checkbutton['command'] = lambda arg=spam: print(arg.get())
In order to see that the variable indeed does store the values.
The problem is when your print is called spam.get() equals "" as:
spam = StringVar()
is identical to:
spam = StringVar(value="")
The checkbutton is initially on a default neither-on, nor-off state(as spam is neither the off nor the on value), but it's hard to notice for the ttk version(if at all), replace:
checkbutton = ttk.Checkbutton(...
with:
checkbutton = Checkbutton(...
to use the default Checkbutton from tkinter, it is much more distinctively displayed.
Also further note that the Checkbutton requires being used in order to call spam.set(checkbutton['onvalue']) or spam.set(checkbutton['offvalue']).

Related

Unable to update Label text in Python Tkinter without calling pack() again

from tkinter import *
from tkinter.ttk import *
root = Tk()
first_run = True
def update(txt):
global first_run
text1 = Label(root, text='')
if first_run:
text1.pack()
text1['text'] = txt
first_run = False
update('1')
update('2')
update('3')
root.mainloop()
When I run this, the text stays at '1', and the following 2 function calls are ignored. I find out that only if I use pack() again then it will be updated, but it creates a duplicate label and I do not want that.
Of course, I know that I am supposed to use a StringVar, but I have been using this method for all other widgets (buttons, label frames etc) and all of them works. I do not know why this particular case does not work.
Running on Python 3.9.9 on Windows 11
You aren't updating the label, you are creating a new label each time the function is called. To update any widget, use the configure method. For that, you need to create the label outside of the function (or, leave it in the function but add logic so that it's only created once). Usually it's best to create it outside the function so that the function is only responsible for the update.
from tkinter import *
from tkinter.ttk import *
root = Tk()
def update(txt):
text1.configure(text=txt)
text1 = Label(root, text='')
text1.pack()
update('1')
update('2')
update('3')
root.mainloop()
Note: since you call your function multiple times before the window is drawn you'll only see the final value. There are plenty of solutions to that on this site. Without knowing more about what your real program looks like it's hard to recommend the best solution to that problem.

Python tkinter Checkbutton not appearing selected in Topframe window

When programming with tkinter I have found a very strange behaviour of the Checkbutton widget. I have re-created the bug with the code below:
import tkinter
from tkinter import *
def displayWelcomeScreen(root):
root2 = Toplevel(root)
root2.geometry('600x380')
root2.focus_set()
Checked = IntVar()
CheckButton1 = Checkbutton(root2, variable=Checked)
CheckButton1.place(relx=0.5, rely=0.5, anchor=CENTER)
CheckButton1.select()
# Create a dummy button that makes the Checkbutton appear checked to the user
#Button(root2, command= lambda event: Checked.get())
root = Tk()
root.geometry('700x400')
displayWelcomeScreen(root)
root.mainloop()
When a new window is created with Toplevel(root) and I put a Checkbutton inside it, it does not appear checked to the user even though I use the .select() method.
However, when I create a dummy button whose command mentions the IntVar associated with my Checkbutton, somehow it is initialised as checked properly. It's almost as if the compiler checks whether the Checkbutton will be useful and decides based on that whether it will display it as selected or not.
EDIT: The Checkbutton is definitely checked under the hood because if I run print(Checked.get()) before and after the CheckButton1.select() command, the value is changed, it just doesn't appear to the user.
Does anyone know why this happens?
EDIT 2: Thanks to jasonharper's explanation, I have added the line CheckButton1.intvar = Checked and it worked without needing the dummy button. When the function went out of scope, the Checked variable got lost so the Checkbutton had nowhere to store its state, therefore we needed to keep a reference to it so it didn't disappear.

Dynamic Addition of Option Menu tKinter Python

I'm quite new to python. I have a requirement where I have to read a data file and based on the number of records inside the file, I have to create the same number of option menus related to that data. But option menu's options can be fetched from simple list. Please find the code below.
import tkinter as tk
from tkinter import *
root=tk.Tk()
root.title("GridAssign")
Grids=('Grid1','Grid2','Grid3','Grid4','Grid5')
file=open(Datafilepath,"r")
ing=file.read().splitlines()
print (len(ing))
variable = StringVar(root)
variable.set(Grids[0])
for i in range(len(ing)):
label = tk.Label(root,text=ing[i])
label.grid(row=i,pady=3,sticky=W)
w=OptionMenu (root, variable, *tuple(Grids)))
w.grid(row=i,column=2,pady=3,sticky=E)
root.mainloop()
The above code is able to give me n number of option menus but if I choose one value in one option menu other option menu's value also changes. Can someone please let me know how to achieve this.
Thanks,
Santty.
This is happening because each of your OptionMenu are using the same StringVar. So naturally when one OptionMenu makes a change to that StringVar, the other menus also reflect that change.
To prevent this you can use a separate StringVar for each menu:
import tkinter as tk
from tkinter import *
root=tk.Tk()
root.title("GridAssign")
Grids=('Grid1','Grid2','Grid3','Grid4','Grid5')
file=open(Datafilepath,"r")
ing=file.read().splitlines()
print (len(ing))
variable_dict = {}
for i in range(len(ing)):
variable = StringVar(root)
variable.set(Grids[0])
variable_dict[i] = variable
label = tk.Label(root,text=ing[i])
label.grid(row=i,pady=3,sticky=W)
w=OptionMenu (root, variable, *tuple(Grids)))
w.grid(row=i,column=2,pady=3,sticky=E)
root.mainloop()
You can then access each of the StringVars through the dictionary variable_dict

How to update the command of an OptionMenu

I am trying to set or update the command of an OptionMenu after its instantiation.
The widget.configure(command=foo) statement works for Button and CheckButton, but not for OptionMenu.
The following code raises this error: _tkinter.TclError: unknown option "-command"
from Tkinter import Tk, OptionMenu, StringVar
root = Tk()
var = StringVar()
def foo(val):
print val, var.get()
widget = OptionMenu(root, var, "one", 'two')
widget.configure(command=foo)
widget.pack()
root.mainloop()
Good question! Its a good thing I never had to do this in any one of my projects before because (unless someone proves me wrong here) you can't set/update the command of a OptionMenu widget once its already defined.
If Tkinter wanted you to be able to do that, it definitely would've included it to be edited by .configure()
There is a handy function called .keys() which you can call with a widget object to see all available traits that can be used with .configure().
Button example:
from tkinter import *
master = Tk()
def callback():
print ("click!")
b = Button(master, text="OK", command=callback)
print (b.keys()) #Printing .keys()
b.pack()
mainloop()
Which results in :
Notice how in this huge list of keys, 'command' is on the second line? That is because a button's command CAN be used in .configure()
OptionMenu example:
from tkinter import *
root = Tk()
var = StringVar()
def foo(val):
print ("HI")
widget = OptionMenu(root, var, "one", 'two')
print(widget.keys())
widget.pack()
root.mainloop()
Which results in:
Notice how there is no 'command' on line 2 this time. This is because you cant configure command with an OptionMenu widget.
Hopefully this problem doesn't hinder your program too much and I hope my answer helped you understand better!
I think what you're really asking is how to associate a command to an Optionmenu, rather than update a command (there is no command, so there's nothing to update).
If you want a function to be called every time a value is selected from an Optionmenu, you can add a trace on the related variable. The trace will call a function whenever that variable changes, whether through the Optionmenu or any other means.
For example:
...
var = tk.StringVar()
def foo(*args):
print "the value changed...", var.get()
var.trace("w", foo)
...
When the function is called it will pass three arguments, which you can safely ignore in this case.
For more information on variable traces see http://effbot.org/tkinterbook/variable.htm
You might also want to consider switching to the ttk combobox. It supports binding to <<ComboboxSelected>>, which is every so slightly less clunky than doing a variable trace.
It is possible to change the commands associated with OptionMenu widets if you're careful (as #Bryan Oakley commented). Below is an example of doing it.
The tricky part is you have to reconfigure all the menu items, not just one of them. This requires some extra bookkeeping (and some processing overhead, but it's unnoticeable).
Initially the menu has three items, each with a different function to be called when selected, one of which changes the menu. If the latter is selected the menu is changed to only have two menu items both of which call the same function.
from tkinter import *
root = Tk()
var = StringVar()
var.set('Select')
def foo(value):
var.set(value)
print("foo1" + value)
def foo2(value):
var.set(value)
print("foo2 " + value)
def foo3(value):
var.set(value)
print("foo3 " + value)
def change_menu(value):
var.set('Select')
print('changing optionmenu commands')
populate_menu(optionmenu, one=foo3, two=foo3)
def populate_menu(optionmenu, **cmds):
menu = optionmenu['menu']
menu.delete(0, "end")
for name, func in cmds.items():
menu.add_command(label=name, command=
lambda name=name, func=func: func(name))
optionmenu = OptionMenu(root, var, ()) # no choices supplied here
optionmenu.pack()
Label(root, textvariable=var).pack()
populate_menu(optionmenu, one=foo, two=foo2, change=change_menu)
root.mainloop()

How to connect a variable to Entry widget?

I'm trying to associate a variable with a Tkinter entry widget, in a way that:
Whenever I change the value (the "content") of the entry, mainly by typing something into it, the variable automatically gets assigned the value of what I've typed. Without me having to push a button "Update value " or something like that first.
Whenever the variable gets changed (by some other part of the programm), I want the entry value displayed to be adjusted automatically. I believe that this could work via the textvariable.
I read the example on http://effbot.org/tkinterbook/entry.htm, but it is not exactly helping me for what I have in mind. I have a feeling that there is a way of ensuring the first condition with using entry's "validate". Any ideas?
I think you want something like this. In the example below, I created a variable myvar and assigned it to be textvariable of both a Label and Entry widgets. This way both are coupled and changes in the Entry widget will reflect automatically in Label.
You can also set trace on variables, e.g. to write to stdout.
from tkinter import *
root = Tk()
root.title("MyApp")
myvar = StringVar()
def mywarWritten(*args):
print "mywarWritten",myvar.get()
myvar.trace("w", mywarWritten)
label = Label(root, textvariable=myvar)
label.pack()
text_entry = Entry(root, textvariable=myvar)
text_entry.pack()
root.mainloop()

Categories