TKinter CheckButtons won't properly update values - python

I know this question has been asked a few times, but not one of the other solutions has applied to my problem.
I have a variable list of "anomalies" stored as a tuple holding the anomaly name and either a 0 or 1, determining whether or not to notify the user of something. Because the list is of variable length, the checkbuttons need to be created in a for loop.
I want to create a popup that shows a list of checkbuttons, to allow the user to edit the notification values to their preference. However, the implementation of this idea that I've used causes the checkbuttons to not change the value of their variables or display the proper on/off state.
Here's my code:
notif_anoms = [("Anomaly 1", 1), ("Anomaly 2", 0)]
checkbox_vars = []
def select_desired_anomaly_checks(self):
popup = Tk()
popup.wm_title("Desired Anomalies")
len_a = len(self.notif_anoms)
for i in range(0, len_a):
msg, on = self.notif_anoms[i]
self.checkbox_vars.append(IntVar(value=on))
self.checkbox_vars[-1].set(on)
tk.Checkbutton(popup, text=msg, variable=self.checkbox_vars[-1], onvalue=1, offvalue=0, command=self.update_vars).grid(row=i, sticky=W)
popup.resizable(0, 0)
popup.mainloop()
def update_vars(self):
for i in range(0, len(self.checkbox_vars)):
var = self.checkbox_vars[i]
print(var.get())
self.notif_anoms[i] = (self.notif_anoms[i][0], var.get())
print('------------------')
The only problem I can think of with this is that I'm setting the IntVar inside of the for loop, but as far as I can think of, there's no other way to create a list of checkboxes at runtime with unknown length.
Any and all help is appreciated, thank you.

Related

How can I print total clicked number of checkboxes with a function in tkinter? [duplicate]

I made an application with tkinter which creates a list of checkboxes for some data. The checkboxes are created dynamically depending on the size of the dataset. I want to know of a way to get the input of each specific checkbox.
Here is my code, which you should be able to run.
from tkinter import *
root = Tk()
height = 21
width = 5
for i in range(1, height):
placeholder_check_gen = Checkbutton(root)
placeholder_check_gen.grid(row=i, column=3, sticky="nsew", pady=1, padx=1)
for i in range(1, height):
placeholder_scope = Checkbutton(root)
placeholder_scope.grid(row=i, column=4, sticky="nsew", pady=1, padx=1)
root.mainloop()
I looked over other answers and some people got away by defining a variable inside the checkbox settings "variable=x" and then calling that variable with a "show():" function that would have "variable.get()" inside. If anyone could please point me in the right direction or how I could proceed here. Thank you and much appreciated.
Normally you need to create an instance of IntVar or StringVar for each checkbutton. You can store those in a list or dictionary and then retrieve the values in the usual way. If you don't create these variables, they will be automatically created for you. In that case you need to save a reference to each checkbutton.
Here's one way to save a reference:
self.general_checkbuttons = {}
for i in range(1, self.height):
cb = Checkbutton(self.new_window)
cb.grid(row=i, column=3, sticky="nsew", pady=1, padx=1)
self.general_checkbuttons[i] = cb
Then, you can iterate over the same range to get the values out. We do that by first asking the widget for the name of its associated variable, and then using tkinter's getvar method to get the value of that variable.
for i in range(1, self.height):
cb = self.general_checkbuttons[i]
varname = cb.cget("variable")
value = self.root.getvar(varname)
print(f"{i}: {value}")

Why I can't loop a variable inside a list as a *args? [duplicate]

This question already has answers here:
tkinter creating buttons in for loop passing command arguments
(3 answers)
Closed 6 months ago.
(As the 'homework' tag indicates, this is part of a big project in Computer Science.)
I am writing a Jeopardy! simulation in Python with tkinter, and I'm having a big problem regarding the use of the lambda function in buttons. Assume root = Tk() and categories is a list.
# Variable to keep the buttons
root._buttons = {}
# Display headers on top of page
for i in range(5):
# Get category name for display in main window
name = categories[i]
b = Label(root, text=fill(name.upper(), 10), width=18, height=3,\
bg="darkblue", fg="white", font=("Helvetica bold", "", 11))
b.grid(row=0, column=i)
# Create list of buttons in that variable (root._buttons)
btnlist = [None]*5
# Display individual questions
for j in range(5):
# Make a button for the question
b = Button(root, text="$" + str(200 * (j+1)), width=8, height=1,
bg="darkblue", fg="orange", font=("Impact", "", 30))
b.cat = name
b.value = 200 * (j + 1)
b.sel = lambda: select(b.cat, b.value)
# Add callback event to button
print(b.cat, b.value, b.sel)
b.config(command=b.sel)
# Add button to window
b.grid(row=j+1, column=i)
# Append to list
btnlist[j] = b
root._buttons[categories[i]] = btnlist
For all of the code, see my little Code Viewer (under construction!)
It's at lambda: select(b.cat, b.value) where the problem seems to occur, because when I click any button on the board, it always goes to the one last button on the board. I've tried other approaches, unfortunately all using lambda, and I have not seen any approach that does not involve lambda.
Change
lambda: select(b.cat, b.value)
to
lambda b = b: select(b.cat, b.value)
In your original code, b is not a local variable of the lambda; it is found in enclosing scope. Once the for-loop is completed, b retains it last value. That is why the lambda functions all use the last button.
If you define the lambda to take one argument with a default value, the default value is determined (and fixed) at the time the lambda is defined. Now b is a local variable of the lambda, and when the lambda is called with no arguments, Python sets b to the default value which happily is set to various different buttons as desired.
It would let you be more expressive if you replaced the lambda expression with a function factory. (presuming that you're going to call this multiple times). That way you can do assignments, add more complicated logic, etc later on without having to deal with the limitations of lambda.
For example:
def button_factory(b):
def bsel():
""" button associated with question"""
return select(b.cat, b.value)
return bsel
Given an input b, button_factory returns a function callable with () that returns exactly what you want. The only difference is that you can do assignments, etc.
Even though it may take up more lines of code initially, it gives you greater flexibility later on. (for example, you could attach a counter to bsel and be able to count how many times a particular question was selected, etc).
It also aids introspection, as you could make each docstring clearly identify which question it is associated with, etc.

Tkinter Checkbutton variable

I'm not great at tkinter or even python so I've run into what should be a simple problem.
I have something like this in the middle of my project:
visible = numLevels * [IntVar(value=1)]
top = Toplevel()
settingslabel = Label(top, text='Settings', height=0, width=100)
for i in range(0, numLevels ):
check = ttk.Checkbutton(settingslabel, text='Level ' + str(i), variable=visible[i])
check.grid(column = 0, row = i)
check.var = visible[i]
settingslabel.grid(column = 0, row=0)
I want to have settings screen with a checkbox for every level, while maintaining an array of integers that represent the status of each button.
However, all checkboxes are synchronized. Meaning, when I check a box, all other boxes also become checked. I believe that this is because of the 'variable' field of the checkbutton. As the loop continues, i is updated, and as a result, visible[i] changes as well. I want to preserve the variable when I created the checkbutton. I don't understand how tkinter/python work well enough to know.
The number is levels can be any integer > 0 and is determined at runtime so I can't just unroll the loop.
Is there a better way to do this? Thanks in advance.
This is a duplicate of this SO post, but explanation below.
Its because all your boxes are sharing the same tkinter.Intvar() object:
numlevels = 5
visible = numlevels * [IntVar(value = 1)]
for i in range(len(visible)):
print (hex(id(visible[i]))
# Outputs:
'0x67f8190'
'0x67f8190'
'0x67f8190'
'0x67f8190'
'0x67f8190'
To solve: visible = [IntVar(value = 1) for i in range(numlevels)]

how to set a list of entries text variables to another dynamic list

Is there anyway I can plug my list of integers into my list of entry boxes? The list of integers is constantly changing..
This would not be a problem if the list of integers and list of entry boxes had the same number of data points, however I can't determine that initially because I want user input to determine this entry list length in future code. I've tried using Insert to solve this problem, to no avail, given that I couldn't use the index of entry to configure its text option.
from tkinter import *
def entry_list_extender():
entrylist.extend(number)
gx=10
number=0
root=Tk()
frame=Frame(root)
frame.pack()
entry=[]
entrylist=[1,2,3,4]
var = []
entrybox=Entry(frame,bg='blue',textvariable=number)
entrybox.pack()
button=Button(frame,bg='red',command=entry_list_extender)
button.pack()
for i in range(gx):
entry.append(Entry(frame, textvariable=entrylist[i]))
entry[-1].pack()
root.mainloop()
A solution or path I could take to get the results I want would be appreciated.
Edit: my original question was quite ambiguous. This should make it more clear
UPDATE:
I am going to have to make an assumption here to make this work.
I am assuming that gx is the user defined variable you want to use down the road.
If that is the case then you need to change you your code a bit to re-create the entry fields when you press the button and also use the value of gx to decide on how many entry fields you should use.
Let me know if this is closer to what you are trying to do as it is still not very clear what your goal is.
from tkinter import *
root=Tk()
gx=10
number=0
entry=[]
entrylist=[1, 2, 3, 4]
var = []
def entry_list_extender():
global frame, entrylist, entry
entry = []
entrylist = []
for i in range(gx):
entrylist.append(i)
frame.destroy()
create_entry_fields()
entrybox=Entry(root, bg='blue', textvariable = number)
entrybox.pack()
button=Button(root, bg='red', command = entry_list_extender)
button.pack()
def create_entry_fields():
global frame, entrylist, entry
frame = Frame(root)
frame.pack()
print (len(entrylist))
for i in range(len(entrylist)):
entry.append(Entry(frame, textvariable = i))
entry[-1].pack()
create_entry_fields()
root.mainloop()

Tkinter lambda function [duplicate]

This question already has answers here:
tkinter creating buttons in for loop passing command arguments
(3 answers)
Closed 6 months ago.
(As the 'homework' tag indicates, this is part of a big project in Computer Science.)
I am writing a Jeopardy! simulation in Python with tkinter, and I'm having a big problem regarding the use of the lambda function in buttons. Assume root = Tk() and categories is a list.
# Variable to keep the buttons
root._buttons = {}
# Display headers on top of page
for i in range(5):
# Get category name for display in main window
name = categories[i]
b = Label(root, text=fill(name.upper(), 10), width=18, height=3,\
bg="darkblue", fg="white", font=("Helvetica bold", "", 11))
b.grid(row=0, column=i)
# Create list of buttons in that variable (root._buttons)
btnlist = [None]*5
# Display individual questions
for j in range(5):
# Make a button for the question
b = Button(root, text="$" + str(200 * (j+1)), width=8, height=1,
bg="darkblue", fg="orange", font=("Impact", "", 30))
b.cat = name
b.value = 200 * (j + 1)
b.sel = lambda: select(b.cat, b.value)
# Add callback event to button
print(b.cat, b.value, b.sel)
b.config(command=b.sel)
# Add button to window
b.grid(row=j+1, column=i)
# Append to list
btnlist[j] = b
root._buttons[categories[i]] = btnlist
For all of the code, see my little Code Viewer (under construction!)
It's at lambda: select(b.cat, b.value) where the problem seems to occur, because when I click any button on the board, it always goes to the one last button on the board. I've tried other approaches, unfortunately all using lambda, and I have not seen any approach that does not involve lambda.
Change
lambda: select(b.cat, b.value)
to
lambda b = b: select(b.cat, b.value)
In your original code, b is not a local variable of the lambda; it is found in enclosing scope. Once the for-loop is completed, b retains it last value. That is why the lambda functions all use the last button.
If you define the lambda to take one argument with a default value, the default value is determined (and fixed) at the time the lambda is defined. Now b is a local variable of the lambda, and when the lambda is called with no arguments, Python sets b to the default value which happily is set to various different buttons as desired.
It would let you be more expressive if you replaced the lambda expression with a function factory. (presuming that you're going to call this multiple times). That way you can do assignments, add more complicated logic, etc later on without having to deal with the limitations of lambda.
For example:
def button_factory(b):
def bsel():
""" button associated with question"""
return select(b.cat, b.value)
return bsel
Given an input b, button_factory returns a function callable with () that returns exactly what you want. The only difference is that you can do assignments, etc.
Even though it may take up more lines of code initially, it gives you greater flexibility later on. (for example, you could attach a counter to bsel and be able to count how many times a particular question was selected, etc).
It also aids introspection, as you could make each docstring clearly identify which question it is associated with, etc.

Categories