Problem: Can only check multiple checkbuttons together? - python-3.x

I would like to generate checkbuttons for multiple items. Due to the repetition, I used a loop to initialize the widget and the default state, saving the BooleanVar() and widgets in separate lists. But by doing so, I can only check either check or uncheck all of them together.
I already tried to set different value to the BooleanVar in the list from within the loop, but to no avail.
ckbtnVarList = [tk.BooleanVar()]*len(ckbtnDict["Tag"])
ckbtnWdtList = [None]*len(ckbtnDict["Tag"])
for idx in range(len(ckbtnDict["Tag"])):
ckbtnVarList[idx].set(1)
ckbtnWdtList[idx]=ttk.Checkbutton(mainfrm, text=ckbtnDict["Text"][idx], variable=ckbtnVarList[idx]).pack()

As specified in the comments above, you need to create your list of BooleanVar's with a list comprehension or a list. The below code shows how to do this.
Since you didn't provide a complete code example, I've had to make some assumptions about your input data.
import tkinter as tk
ckbtnDict = {}
ckbtnDict['Tag'] = ["Tag1","Tag2","Tag3"]
ckbtnDict["Text"] = ["Txt1","Txt2","Txt3"]
mainfrm = tk.Tk()
ckbtnVarList = [tk.BooleanVar() for i in range(len(ckbtnDict["Tag"]))]
ckbtnWdtList = [None for i in range(len(ckbtnDict["Tag"]))]
for idx in range(len(ckbtnDict["Tag"])):
ckbtnVarList[idx].set(1)
ckbtnWdtList[idx]=tk.Checkbutton(mainfrm, text=ckbtnDict["Text"][idx], variable=ckbtnVarList[idx])
ckbrnWdtList[idx].pack()
mainfrm.mainloop()

Related

Duplicating a Tkinter treeview

I'm trying to display a ttk treeview in another window. The only option, it seems, is to iterate through the original treeview and populate the new one accordingly.
However I can't seem to get all the (many) subfolders in the right place, everything is mixed up as of the 2d level (i.e., I get the root folders and their children right, and after that the subfolders seem to be inserted at random locations).
The function is :
def getsubchildren(item=''):
children = []
for child in original_treeview.get_children(item):
i = new_treeview.insert(item, 'end', text=original_treeview.item(child)
['text'],values=original_treeview.item(child)['values'])
children.append(i)
for subchild in children:
getsubchildren(subchild)
And calling the function with getsubchildren(item=''), to start iterating from the first level.
There must be something I'm doing wrong, but I can't identify the issue and my attempts at modifying the function have only given a poorer result.
Any idea ?
Thanks,
Without known the depth of the item you need to check if the item has children. If so you need the function to call itself in a loop. Here is a working exampel:
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
maintree = ttk.Treeview(root)
maintree.pack()
first = maintree.insert("","end",text='first')
second= maintree.insert(first,"end",text='second')
third= maintree.insert(second,"end",text='third')
fourth= maintree.insert(third,"end",text='fourth')
st = maintree.insert("","end",text='1st')
nd= maintree.insert(st,"end",text='2nd')
rd= maintree.insert(nd,"end",text='3rd')
th= maintree.insert(rd,"end",text='4th')
top = tk.Toplevel(root)
subtree = ttk.Treeview(top)
subtree.pack()
def copy_item_tree(item,child):
for child in maintree.get_children(child):
item = subtree.insert(item,"end",
text=maintree.item(child)['text'])
if maintree.get_children(child):
copy_item_tree(item,child)
def copy_tree():
for child in maintree.get_children():
item = subtree.insert("","end",text=maintree.item(child)['text'])
copy_item_tree(item,child)
button = tk.Button(root,text='Copy Tree', command=copy_tree)
button.pack(fill='x')
root.mainloop()

Efficiently create tkinter images using a loop

Currently I loop through a list of image names and make them into a tkinter PhotoImage. These are saved as variables under their respective names from the list using vars()[var].
In this case vars()[var] represents the value of the item in a list making it into a variable name for the images.
example shown below:
list = ["a","b","c","d"] #and so on...
image_id = []
for x in range((len)list):
var = list[x]
vars()[var] = tk.PhotoImage(file = var + ".gif")
image_id.append(vars()[var]) #this adds the identity of all photoimages to a list for later use
According to my tests I can use the images via the variable names or identities as long as it is within the same function. This is due to the variables being local.
The problem is that even though the image identities are in a list they do not work when used outside of the original function. I believe this happens because the images are tied to their variable names and since those are all local tkinter garbage collects them resulting in my error which is "pyimagenumber does not exist." This is because the identity for a tkinter image is just "pyimage" and its corresponding number. My line of thinking is that I want to make all variable names that store images to be global without needing an extra line per image since that defeats the purpose of using a loop and list. Is there any way i can set the "vars()[var]" to be global while also giving it a value?
For any solutions I would like to avoid fundamental changes or using pillow.
Any help is appreciated and if you have further questions about context please ask!
Simply move your empty list outside of your function. Also instead of for x in range(len(list)), use for x in something instead. It could be something like this:
import tkinter as tk
root = tk.Tk()
image_id = []
def image_creation():
imlist = ["a","b","c","d"] #don't name it as list - it shadows the built in list
for var in imlist:
image_id.append(tk.PhotoImage(file = var + ".gif"))
... #the rest of your code
root.mainloop()
There's no need to use vars(). Just create a dictionary in the global namespace and add your images to it. You can even use the original item from the list as the key.
Example:
global images
...
list = ["a","b","c","d"]
images = {}
for x in list:
images[x] = tk.PhotoImage(file = var + ".gif")
Then, the image for "a" can be accessed at any time as images['a'].

Tkinter - How to trace expanding list of variables

What I am trying to do track when any values in a list of StringVar change, even when the list is expanding. Any additions to the list before the trace statement will result in the callback. But any additions afterward, such as when pressing a button, will not cause any callback.
import tkinter as tk
root = tk.Tk()
frame = tk.Frame(root)
frame.grid(row=0)
L = []
def add_entry(event):
L.append(tk.StringVar())
tk.Entry(frame,textvariable=L[len(L)-1]).grid(row=len(L),padx=(10,10),pady=(5,5))
add = tk.Button(frame,text='add Entry',command='buttonpressed')
add.grid(row=0)
add.bind('<Button-1>',add_entry)
for i in range(2):
L.append(tk.StringVar())
tk.Entry(frame,textvariable=L[len(L)-1]).grid(row=len(L),padx=(10,10),pady=(5,5))
for i in L:
i.trace('w',lambda *arg:print('Modified'))
root.mainloop()
Modifying the first two Entry's prints out Modified, but any Entry's after the trace is run, such as the ones produced when a button is pressed, will not.
How do I make it so that trace method will run the callback for the entire list of variables even if the list is expanded?
Simple suggestion, change your add_entry function to something like this:
def add_entry(event):
L.append(tk.StringVar())
tk.Entry(frame,textvariable=L[len(L)-1]).grid(row=len(L),padx=(10,10),pady=(5,5))
L[len(L)-1].trace('w',lambda *arg:print('Modified'))
Extra suggestions:
This add = tk.Button(frame,text='add Entry',command='buttonpressed') is assigning a string to command option, means it will try to execute that string when button is clicked(which will do nothing). Instead, you can assign your function add_entry to command option and it will call that function when button is clicked and you can avoid binding Mouse Button1 click to your Button(Note: No need to use argument event in function when using like this). Read more here
Python supports negative indexing of List, so you can call L[-1] to retrieve the last element in the list instead of calling L[len(L)-1]).
Once you change your add_entry function as suggested, you can reduce your code to
import tkinter as tk
root = tk.Tk()
frame = tk.Frame(root)
frame.grid(row=0)
L = []
def add_entry():
global L
L.append(tk.StringVar())
tk.Entry(frame,textvariable=L[-1]).grid(row=len(L),padx=(10,10),pady=(5,5))
L[-1].trace('w',lambda *arg:print('Modified'))
add = tk.Button(frame,text='add Entry',command=add_entry)
add.grid(row=0)
for i in range(2):
add_entry()
root.mainloop()

Ever expanding amount of Entries with TKinter

What I'm trying to do is to make a GUI where when you start typing in an entry-box another shows up just beneath the one you are typing in. Then when you start typing in the one that popped up, another pops up. Is this possible with TKinter and Python?
Edit:
So what I currently have is this:
entry1 = StringVar()
numberLabel3 = Label(window, text = "3. External meeting attendees")
r+=1
numberLabel3.grid(column = 0, row = r, sticky = W)
externalAtendeesLabel = Label(window, text = "input name of external meeting atendee: ")
r+=1
externalAtendeesLabel.grid(column = 1, row = r, sticky = E)
externalAtendeesEntry = Entry(window, textvariable = entry1)
externalAtendeesEntry.grid(column = 2, row = r)
#Note to self: Find a smart way of dynamically expanding this "list" of entries
(There is more code above and below this, but this is the relevant code for my question)
where r is a variable I made to make it easier to insert stuff into the middle of my rather long code.
The imports I'm using are:
from tkinter import *
from PIL import ImageTk
from PIL import Image
import os
I use the image modules and OS to insert an image further up in my GUI.
What I was thinking was to make a function that I could somehow setup to check the newest Entry-box, but I've run into the problem that for this to be potentially infinite I would have to dynamically create new variables, so that I can access the information that the user inputs. These variables would save the info just like my entry1 variable does it for the externalAtendeesEntry.
I would also have to dynamically make variables for more entries.
How do I dynamically create a potentially infinite amount of variables?
I know that this is kind of a re-post, but the other ones I've found all say that you should use dictionaries, but in that case it can't be infinite. It can only be finite to the point where my dictionary is no longer.
For one, you don't need to use StringVar. It only complicates your code without providing any real value. The other part of the answer is to store the entries in a list.
For example, create a function called addEntry that creates an entry and adds it to a list:
entries = []
...
def addEntry():
entry = tk.Entry(...)
entry.pack(...)
entries.append(entry)
To get the values at a later date, just iterate over the list:
for entry in entries:
print(entry.get())
With that, you can add entries whenever you want. You could, for example, bind to <Any-KeyRelease> to create a new entry as the user types (being sure to only do it if there isn't already a blank entry). Or, bind to <Return> or <FocusOut>, or on the click of a "new person" button, or however else you decide.

Access element of list by variable name

How can I access a list element using the name of the list?
I would like to allow a user to edit the code in determine a single variable to be inputted into a function. For example:
blah = [1,2]
blah2 = 5
toBeChanged = "blah2"
def foo():
print(blah)
def changeVariable():
globals()[toBeChanged] += 1
for time in range(5):
changeVariable()
simulate
This works for blah2 since it is a simple variable, however it will not work for blah[0] since it is part of a list. I've also tried placing my variables into a dictionary as other answers have suggested, but I still am unable to change list elements through a simple string.
Is there a way to do this that I am missing? Thanks!
Rather than using globals() and altering directly it would be much, much better to use a dictionary to store the variables you want the user to alter, and then manipulate that:
my_variables = {
'blah': [1,2]
'blah2': 5
}
toBeChanged = "blah2"
def foo():
print(my_variables['blah'])
def changeVariable():
my_variables[toBeChanged] = my_variables.get(toBeChanged,0) + 1
for time in range(5):
changeVariable()
This has the added advantage that if a user enters a variable that doesn't exist a default is chosen, and doesn't override any variables that might be important for future execution.

Resources