Efficiently create tkinter images using a loop - python-3.x

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'].

Related

How to open multiple persistent windows in PyQt?

I am trying to display JSON metadata in PyQt6/PySide6 QTreeView. I want to generalize for the case where multiple persistent windows (QtWidgets) pop up if my JSON metadata list has a length greater than 1.
for example:
def openTreeWidget(app, jmd):
view = QTreeView()
model = JsonModel()
view.setModel(model)
model.load(jmd)
app.w = view # app = `self` of a QMainWindow instance
app.w.show()
for md in jsonMetadataList:
openTreeWidget(self, md)
where TreeItem and JsonModel are based on: https://doc.qt.io/qtforpython/tutorials/basictutorial/treewidget.html
I stole the app.w idea from: https://www.pythonguis.com/tutorials/pyqt6-creating-multiple-windows/
In the current case, all pop ups (except one) close after momentarily opening. Only the last item in jsonMetadataList remains displayed in a persistent window. I believe that somehow I am not keeping the reference to previous windows and reopening/rewriting data on a single widget. How can I keep the reference?
Also, I am very new to PyQt/PySide so I'm just doing things no matter how ugly they look at the moment. This will, of course, get better with time :);
I managed to bodge it up by not destroying the reference. Here's how I did it.
def openTreeWidget(app, jmd):
"""
app is the parent QWidget (here, a QMainWindow)
jmd is JSON metadata stored in a string
"""
view = QTreeView()
model = JsonModel()
view.setModel(model)
model.load(jmd)
return view
# `self` of a QMainWindow instance
self.temp = [None]*len(jsonMetadataList) # a list storing individual handles for all JSON metadata entries
for ii, md in enumerate(jsonMetadataList):
self.temp[ii] = openTreeWidget(self, md) # get the reference for QTreeView and store it in temp[ii]
self.temp[ii].show() # show the ii-th metadata in QTreeView
Better ideas are still welcome :)

How do I dynamically set the name of a tkinter label

I have this code in my project:
def savespack():
savelabelcount = 0
for i in range(saveamount):
savelabelcount = savelabelcount + 1
savebuttontext = "Save " + str(savelabelcount)
Button(scrollable_frame, text=savebuttontext, width="53", height="5").pack()
The user can create multiple saves in the program, and when the project is reopened, the correct amount of save files will be shown as Tkinter buttons. I've created the buttons by reading a number from an external file. I need to make the buttons have different variable names to call the correct save file and load the correct properties when a button is pressed (for example "Save 2" needs to load save2.txt's properties)
Simply put I need to give Button(scrollable_frame, text=savebuttontext, width="53", height="5").pack() a new variable name for each iteration of the loop, I dont know how to do this. I read something about dictionaries and lists but it was very confusing and I dont know if it can be applied in this situation.
The variable names should be something like: savebutton1, savebutton2, savebutton3 etc.
Any way I could do this would be appreciated, thanks!
The example below shows how to save each button widget inside a list rather than as separate variables.
It also will call the saveFile function when the button is pressed which currently just prints to the screen the save file number but this function could be replaced with the code that actually saves each file. With this method, it might not even be necessary to keep the buttons in a list.
I'd normally do this using classes rather than globals but this will work for your purposes
import tkinter as tk
saveamount = 5
save_buttons = []
def saveFile(fileNumber):
print("Saving File ", fileNumber)
def savespack():
global save_buttons
savelabelcount = 0
for i in range(saveamount):
savelabelcount = savelabelcount + 1
savebuttontext = "Save " + str(savelabelcount)
currentButton = tk.Button(scrollable_frame, text=savebuttontext, width="53", height="5")
currentButton['command'] = lambda x=savelabelcount: saveFile(x)
currentButton.pack()
save_buttons.append(currentButton)
root = tk.Tk()
scrollable_frame = tk.Frame(root)
scrollable_frame.pack()
savespack()
root.mainloop()
You could use a dictionary rather than a list but given your use case, I'm not sure there is any benefit. Just use save_buttons[2] to access the third button.

Problem: Can only check multiple checkbuttons together?

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()

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