ttk create multiple checkbuttons - python

I'm trying to make a GUI that display lots of checkbuttons, i create them from a list; make a dictionary form the list and assign each checkbutton a variable from the dictionary so i can check it's state later.
Problem is that all the checkbuttons are displayed in an 'alternate' state, even if i set the variable to either 0 or 1, i've also tried changing states, but nothing seems to help.
y = 0
for x in get_dir_names(r'D:\SKL\test\win10'):
drv_check[x] = Variable()
drv_check[x].set(0)
center_window(150, 500, top_child)
drv = ttk.Checkbutton(child_frame, text=x, variable=drv_check[x])
drv.grid(row=y, column=0, sticky=W)
y += 1
for reference
def get_dir_names(dir_path):
"""Get names only of all directories from a given path (none recursive)"""
drv_list = [x for x in os.walk(dir_path).__next__()[1]]
drv_name = dict({})
for y in drv_list:
tmp_ver = dir_path + r'\\' + y
drv_name[y] = (os.walk(tmp_ver).__next__()[1]).pop()
return drv_name

Figured it out, I've made a "toggle all" button and it seemed to fix it, but it's weird that it didn't work before.
here's the function i used:
def toggle_all(*args):
while True:
if toggle_all_var.get() == '1':
for name in drv_check:
drv_check[name].set('1')
elif toggle_all_var.get() == '0':
for name in drv_check:
drv_check[name].set('0')
ttk.Checkbutton(drv_frame, text='Toggle all', variable=toggle_all_var).grid(row=y, column=0, sticky=W)
Also i run the function in a new thread.

Related

Move Thru a Python List One Item at a Time

Still pretty new to Python, so my apologies in advance...
I'm trying to use a button to move thru a List one item at a time.
It works the first time the button is clicked and moves to the second item in the List, but subsequent clicks keep returning the same values
from tkinter import *
window=Tk()
window.title('nextArrItem')
window.geometry("300x200+10+10")
options = ["H9Iu49E6Mxs", "YuWZNV4BkkY", "mBf6kJIbXLg", "Hz-xbM6jaRY"]
print("options[0] = " + options[0])
curItemText = options[0]
nextItemText = options[1]
curItem = 0
print('curItemText = '+curItemText)
print('nextItemText = '+nextItemText)
def nextArrItem(curItem=curItem+1):
print("str(curItem) = "+str(curItem))
try:
curItemText = options[curItem]
nextItemText = options[curItem+1]
print('curItemText = '+curItemText)
print('nextItemText = '+nextItemText)
curItem = curItem + 1
except:
print("End of Array Reached")
nextButton = Button(window, text="Next Item", command=nextArrItem)
nextButton.place(x=130, y=110)
window.mainloop()
When the Window opens initially, these values are returned:
options[0] = H9Iu49E6Mxs
curItemText = H9Iu49E6Mxs
nextItemText = YuWZNV4BkkY
The first click returns the following:
str(curItem) = 1
curItemText = YuWZNV4BkkY
nextItemText = mBf6kJIbXLg
Subsequent clicks keep returning the same values, so it only advances the first time and I'm not sure how to fix it. Although it probably doesn't look like it, this is the culmination of a lot of work just to get it to this point but I'm not sure where to go from here. I have the feeling the solution is going to be a true Homer Simpson "D'oh!" moment but I've steered this boat into shallow waters and need someone to help me from running aground...
Thanks in advance!
Paul
You need to increment the parameter each time to the next highest value. Currently your code just feeds the nextArrItem function the same value each time.
You could also try something to put the curItem inside a mutable data type so that it can be updated from within the scope of the function call like this:
...
options = ["H9Iu49E6Mxs", "YuWZNV4BkkY", "mBf6kJIbXLg", "Hz-xbM6jaRY"]
curItem = [0]
...
def nextArrItem(label=label):
try:
option = options[curItem[0]]
print(option)
label["text"] = option # updates label on each call
curItem[0] += 1 # increments index for option text
except IndexError:
print("End of Array Reached")
...
nextButton = Button(window, text="Next Item", command=nextArrItem)
...
Another way of doing it would be to bind the curItem variable to the window itself like this:
from tkinter import *
window=Tk()
window.curItem = 0
window.title('nextArrItem')
window.geometry("300x200+10+10")
options = ["H9Iu49E6Mxs", "YuWZNV4BkkY", "mBf6kJIbXLg", "Hz-xbM6jaRY"]
label = Label(window, text=options[window.curItem])
label.place(x=130, y=50)
def nextArrItem(label=label):
try:
option = options[window.curItem]
print(option)
label["text"] = option # updates label on each call
window.curItem += 1 # increments index for option text
except IndexError:
print("End of Array Reached")
nextButton = Button(window, text="Next Item", command=nextArrItem)
nextButton.place(x=130, y=110)
window.mainloop()
The issue you have is that curItem is both a global variable and a local variable in your callback function. You only ever update the local variable, so the change doesn't persist.
Here's how you're currently setting up the local variable, as an argument with a default value:
def nextArrItem(curItem=curItem+1):
The default value comes from the global variable, but it is only evaluated once, at the time the function is defined. It does not keep checking the global value, nor do changes to the local variable in the function change the global value either.
There's a better way. You can use a global statement to make it so that your function's code can directly read and write the global variable, so that there's only one curItem that everything is accessing the same way.
def nextArrItem():
global curItem
# the rest can be the same

Need to track variables from multiple Tkinter checkboxes

I have a dynamically created Tkinter checkbutton widget, which takes in the contents of a list of usernames. I then displayed those names with a checkbox alongside.
What I need to do is obviously collect which usernames have been checked, so I can pass that off to another function to action.
How should I write the variable part of this so it creates a new list of chosen usernames?
What I have thus far:
def delprof_results(users_delprof):
for i in range(len(users_delprof)):
c = Checkbutton(resultsFrame, text=users_delprof[i], variable=users_delprof[i])
c.pack(anchor=W)
def delprof_del():
users_chosen = []
print str(users_delprof[i]).get() # Works up until this point. How to get individual variable with ID.
del_but = Button(resultsFrame, text="Delete", width=7, height=1, command=delprof_del)
del_but.pack(side=LEFT)
Thanks in advance,
Chris.
If you want to reach individual objects, simply keep a reference to the individual objects instead of creating objects while overwriting the same variable with each iteration of a loop like:
for i in range(30):
a = i
How to reach a's state where it was 13? Well, you can't as it's overwritten.
Instead, use collection types. In the example below I used dict:
try: # In order to be able to import tkinter for
import tkinter as tk # either in python 2 or in python 3
except ImportError:
import Tkinter as tk
def upon_select(widget):
print("{}'s value is {}.".format(widget['text'], widget.var.get()))
if __name__ == '__main__':
root = tk.Tk()
names = {"Chester", "James", "Mike"}
username_cbs = dict()
for name in names:
username_cbs[name] = tk.Checkbutton(root, text=name,
onvalue=True, offvalue=False)
username_cbs[name].var = tk.BooleanVar()
username_cbs[name]['variable'] = username_cbs[name].var
username_cbs[name]['command'] = lambda w=username_cbs[name]: \
upon_select(w)
username_cbs[name].pack()
tk.mainloop()
You could make a list of values from the checkbuttons:
values = []
for i in range(len(users_delprof)):
v = IntVar()
c = Checkbutton(master, text="Don't show this again", variable=v)
c.var = v
values.append(v)
Now you can check the value by looking in the list values, and getting the value of a checkbutton with v.get().

Why is my calculator program not working - Python?

I'm writing a program for a calculator but i'm experiencing a small problem.
Whenever I press one of the buttons, it increases numbers by 9 every time, even though it should be i (from the for loop).
Please can someone tell me why its always 9 please?
Code -
import tkinter
plus = True
numbers = 0
def main():
def numButton(i):
global numbers
if plus == False:
numbers-=i
else:
numbers+=i
def quitHandler():
root.destroy()
def entryHandler():
global numbers
numbers+=int(text.get())
text.set("")
def printHandler():
text2.set(numbers)
def restartHandler():
global numbers
root.destroy()
plus = True
numbers = 0
main()
def plusHandler():
global plus
plus = True
def minusHandler():
global plus
plus = False
root = tkinter.Tk()
frame = tkinter.Frame(root).pack(side=tkinter.TOP)
text = tkinter.IntVar()
text2 = tkinter.IntVar()
text.set("")
text2.set("")
tkinter.Entry(frame,bd =8,textvariable=text).pack()
tkinter.Button(frame,padx=8,pady=8,bd=8,text="Enter",command=entryHandler).pack()
tkinter.Button(frame,padx=8,pady=8,bd=8,text="Quit",command=quitHandler).pack(side=tkinter.RIGHT)
tkinter.Button(frame,padx=8,pady=8,bd=8,text="Restart",command=restartHandler).pack(side=tkinter.RIGHT)
tkinter.Button(frame,padx=8,pady=8,bd=8,text="Print",command=printHandler).pack(side=tkinter.LEFT)
tkinter.Entry(frame,bd =8,textvariable=text2).pack(side=tkinter.LEFT)
_padx = 16
_pady = 16
_bd = 8
for i in range (1,10):
tkinter.Button(frame, padx = _padx, pady = _pady, bd = _bd, text = str(i), command = lambda: numButton(i)).pack(side = tkinter.LEFT)
tkinter.Button(frame,padx=8,pady=8,bd=8,text="+",command=plusHandler).pack(side=tkinter.LEFT)
tkinter.Button(frame,padx=8,pady=8,bd=8,text="-",command=minusHandler).pack(side=tkinter.LEFT)
main()
Could someone also tell me how to put all of it inside the code thing on this site, I cant figure it out and the ways the site's help shows me isn't working ( or admin fix please ).
OK thanks guys, someone's emailed me the solution:
tkinter.Button(frame, padx = _padx, pady = _pady, bd = _bd, text = str(i), command = lambda i=i: numButton(i)).pack(side = tkinter.LEFT)
Had to add the i=i after the lamda.
Can someone explain to me what the i=i thing does please?
Thanks
command = lambda: numButton(i)
This doesn't look up the value of i at the time of the lambda's creation and insert it into the function. When the lambda is called, then it looks up i. i is always 9 by that time.
There are several ways to get around the problem, all focused on ensuring that i is looked up at command's creation time instead of execution time. The one I'd use is functools.partial, a tool designed to associate a function with arguments:
from functools import partial
...
command=partial(numButton, i)
You can also use a default argument, which is kind of kludgy:
command=lambda i=i: numButton(i)
Or write a factory function:
def closure_maker(i):
def closure():
numButton(i)
return closure
...
command=closure_maker(i)
The lambda functions used for the commands reference the variable i:
for i in range (1,10):
tkinter.Button(frame, padx = _padx, pady = _pady, bd = _bd, text = str(i),
command = lambda: numButton(i)).pack(side = tkinter.LEFT)
When the command is executed and the lambda function runs, it selects button number i. But at that time, when the lambda function is executed, i contains the value 9 (That's the value i ends up with after the for loop that created the buttons was complete.
To solve this issue, make sure the lambda functions don't all share the same global variable. One way would be to use a function which creates a new local scope:
def numCommand(x):
return (lambda: numButton(x))
for i in range (1,10):
tkinter.Button(frame, padx = _padx, pady = _pady, bd = _bd, text = str(i),
command = numCommand(i)).pack(side = tkinter.LEFT)
Here each lambda function refers to its own x variable from its numCommand() call.

Possible to remove/deactivate variables from an executed line?

Is it possible to remove/deactivate variables from a line of code once it has been executed? If not, what are my other options? I wrote a code here to demonstrate what I mean:
from Tkinter import *
root = Tk()
ent_var1 = StringVar()
ent_var2 = StringVar()
ent_var3 = StringVar()
cbtn_var1 = BooleanVar()
cbtn_var2 = BooleanVar()
cbtn_var3 = BooleanVar()
ent1 = Entry(textvariable=ent_var1).pack()
ent2 = Entry(textvariable=ent_var2).pack()
ent3 = Entry(textvariable=ent_var3).pack()
cbtn1 = Checkbutton(text=1, variable=cbtn_var1).pack(side = LEFT)
cbtn2 = Checkbutton(text=2, variable=cbtn_var2).pack(side = LEFT)
cbtn3 = Checkbutton(text=3, variable=cbtn_var3).pack(side = LEFT)
# prints what was written in entires
def set_variables():
lbl1 = ent_var1.get()
lbl2 = ent_var2.get()
lbl3 = ent_var3.get()
print lbl1, lbl2, lbl3
return
# calls set_variables
btn1 = Button(root, text="Done!", command=set_variables).pack()
root.mainloop()
When you fill the entries and press "Done!", what was written is printed. But how do I make it so that when I press the checkboxes, the entry linked to it will not be printed the next the I press "Done!"? The checkbox with the text "1" should be linked with the first entry, and so on.
I came up with this:
def should_print():
global lbl_print
if cbtn1:
lbl_print += lbl1
if cbtn2:
lbl_print += lbl2
if cbtn3:
lbl_print += lbl3
But it would only print the values of my variables at that very moment, not the variables themselves (meaning I'd have to run this code every time a variable changes).
Thank you!
Your question is very hard to understand. I think what you want is for set_variables to only print the variables associated with a checked checkbox. If so, does the following do what you want?
def set_variables():
to_print = []
if cbtn_var1.get():
to_print.append(ent_var1.get())
if cbtn_var2.get():
to_print.append(ent_var2.get())
if cbtn_var3.get():
to_print.append(ent_var3.get())
print " ".join(to_print)
return
There are other ways to accomplish the same thing, but I'm guessing your main goal is to decide what to print based on which checkbuttons are checked. This does that, albeit in a rather ham-fisted manner.
Why don't you simply check in your set_variables function if each button is pressed? For example:
def set_variables():
if not cbtn_var1.get():
print ent_var1.get(),
if not cbtn_var2.get():
print ent_var2.get(),
if not cbtn_var3.get():
print ent_var3.get(),
print
The commas at the end of each print statement will cause it to not print a newline, which is taken care of by the print at the end. Also, this will make so that if the box is checked, the value they entered won't print. If you want it to print only if the box is checked, then remove the nots.
If you refactor your code a little bit, you can do the same thing with one line. First, add this line:
cbtn_var1 = BooleanVar()
cbtn_var2 = BooleanVar()
cbtn_var3 = BooleanVar()
buttonsAndValues = [(cbtn_var1,ent_var1), (cbtn_var2,ent_var2), (cbtn_var3,ent_var3)]
With the variables in a list, you can use a list comprehension in some Python magic:
def set_variables():
print ' '.join(value.get() for checked, value in buttonsAndValues if checked.get())
If you haven't seen list comprehensions before, I'd suggest you read up about them - they can be very handy.

Having some trouble getting which radio button is selected and issuing a command based on the selection

I'm writing a program that converts an image to grayscale. I have it working fine and now I'm implementing radio buttons to let the user choose which type of grayscale to use. So far my problem is that when the radio buttons are first made the command = function is called immediately and sets all of my boolean values to True, as that's explicitly what I'm telling it to do by passing using function() instead of function. I'm trying to think of a way that I can use to store which radio button is selected, or hoping that there is something built in that I can do the check for. I do know that using global variables is not best practice, and that a class would eliminate the necessity for them. Here is the relevant code.
# Global variables for radio buttons----
radio1 = False
radio2 = False
radio3 = False
radio4 = False
def whichSelected(numberSelected):
global radio1
global radio2
global radio3
global radio4
if numberSelected == 4:
radio4 = True
if numberSelected == 3:
radio3 = True
if numberSelected == 2:
radio2 = True
if numberSelected == 1:
radio1 = True
# Radio Button Code---------------------------------------------------------
var = tkinter.IntVar()
option1 = tkinter.Radiobutton(window, text ='Average Grayscale',variable = var, value = 1,command = whichSelected(1))
option2 = tkinter.Radiobutton(window, text ='Lightness Grayscale',variable = var, value = 2, command = whichSelected(2))
option3 = tkinter.Radiobutton(window, text ='Luminosity Grayscale',variable = var, value = 3, command = whichSelected(3))
option4 = tkinter.Radiobutton(window, text ='Invert',variable = var, value = 4, command = whichSelected(4))
def change_pixel():
global tkPic2
global radio1
global radio2
global radio3
global radio4
# Treats the image as a 2d array, iterates through changing the
#values of each pixel with the algorithm for gray
rgbList = pic.load() #Get a 2d array of the pixels
for row in range(picWidth):
for column in range(picHeight):
rgb = rgbList[row,column]
r,g,b = rgb # Unpacks the RGB value tuple per pixel
if radio1 == True:
grayAlgorithm1 = grayAverage(r,g,b)
rgbList[row,column] = (grayAlgorithm1, grayAlgorithm1, grayAlgorithm1)
elif radio2 == True:
grayAlgorithm = lightness(r,g,b)
rgbList[row,column] = (grayAlgorithm1, grayAlgorithm1, grayAlgorithm1)
elif radio3 == True:
grayAlgorithm1= luminosity(r,g,b)
rgbList[row,column] = (grayAlgorithm1, grayAlgorithm1, grayAlgorithm1) # Gives each pixel a new RGB value
elif radio4 == True:
r,g,b= invertRGB(r,g,b)
rgbList[row,column] = (r, g, b) # Gives each pixel a new RGB value
# Converting to a tkinter PhotoImage
tkPic2 = ImageTk.PhotoImage(pic, master = window)
print(radio1,radio2,radio3,radio4)
canvas1.create_image(815,170, anchor='e',image = tkPic2)
I'm not completely sure why you need whichSelected in the first place. You should be able to tell from the value of var:
value = var.get()
if value == 1:
print "Average Grayscale"
elif value == 2:
print "Lightness Grayscale"
...
This has the added benefit of guaranteeing that you know which value is currently checked. With your previous approach, you need to add some logic to turn all the all the globals to False before making the one you want True. As your function is, if the user selects one of the radio buttons and then another, both will be marked as True in your global variables.
However, it's instructive to point out why your approach is failing as well. In addition to the problem previously mentioned, the command argument is supposed to be a function, you're passing the result of a function (in this case None). When you call the functions to get the result, you're setting your globals to True as a side-effect.
The quick fix is to use lambda to create a new function which calls the old one the way you want to. This way, you will defer setting the globals to True until your radio button is actually clicked:
option1 = tkinter.Radiobutton(window, text='Average Grayscale',
variable=var,
value=1,
command=lambda: whichSelected(1))
lambda ends up being very useful for this sort of thing in tkinter applications. (I can't imagine writing an app without it ...)

Categories