How to create widget list in python? - python

I want to add buttons to list, I know there is widget list and I already read is somewhere but I forgot where. I can't find any helpful posts on this question.
I want to change button's background in for loop, when some action happens, like mouse hovering.
I'm using python 3.10.4
Thanks for any help!

Step one: create an empty list
widget_list = []
Step two: add widgets to the list
for i in range(10):
button = tk.Button(root, text=f"Button #{i+1}", command=reset)
widget_list.append(button)
Step three: loop over the list to change the background:
for widget in widget_list:
widget.configure(background="red")
If the code in these steps are in separate functions, you'll need to declare widget_list as global variable or an instance variable.

Related

Declare buttons in a loop in Tkinter (Python3)

I have never created a GUI before and I decided to try to create one in Python3, using tkinter.
I would like to create a 5x5 "matrix" of buttons that change color and text when pressed. After some googling, I figure out how to do it for a single button and in principle I could just copy-paste and create the 25 buttons I need. However I'd like to find a more elegant way to do it inside a loop. My problem is that I need to give different names to each button, but I don't know how to do that.
I hope the question is clear enough. Thank you in advance for any help!!
Here is a very simple example on how to do so, by making a list of all the 25 color and then using the conventional matrix looping and assigning items to the buttons, like:
from tkinter import *
root = Tk()
colors = ['Red','Orange','Yellow','Green','Blue','Purple','Brown','Magenta',
'Tan','Cyan','Olive','Maroon','Navy','Aquamarine','Turquoise','Silver',
'Lime','Teal','Indigo','Violet','Pink','Black','White','Gray','crimson']
colors = list(reversed(colors)) # Reversing list bc pop returns last item
def color_changer(btn,color):
btn.config(fg=color) # Change the color of the corresponding button
for i in range(5): # Number of rows
for j in range(5): # Number of column
color = colors.pop() # First color
btn = Button(root,text=color,fg='black',width=25)
btn.grid(row=i,column=j) # Place the widget
btn['command'] = lambda btn=btn,color=color: color_changer(btn,color) # Assign a command
root.mainloop()
There is a caveat here, you should define exactly 25 colors, else, you should use try to catch the IndexError that comes up and ignore it.
How does the function work?:
You are using lambda to create a new nameless function that takes in btn and color and passes that btn and color to the color_changer(). This way we can store corresponding btn and color, unlike if you would normally assign it like lambda: color_changer(btn,color), it is just going to pass the last popped item. This is usually how we assign commands for buttons inside a loop.
Alternative(to lambda):
You can also use a nested function(avoiding lambdas). So the function would be like:
def nester(btn,color):
def color_changer():
btn.config(fg=color)
return color_changer
and the command would be like:
btn['command'] = nester(btn,color)
This is similar to what functools.partial does.

Tkinter: Update frequency of Listbox

Is it possible to control the update frequency of the Listbox widget? Right now I do a lot of insert and delete operations at a high frequency and the Listbox doesn’t refresh very well. Maybe there is a way to override some draw function of the Listbox to fix this issue?
I am not able to find a way to disable visual updates of your listbox so I had to build a work around. If someone knows if you can disable the visual update of listbox please let me know.
My workaround will involve a list and 2 functions.
My first function will take the data that is going to be added to the listbox and instead add it to a list. This function simply simulates new values being added faster than what we want to update for a good visual on the method. You can adapt this code to yours to see how it will work with your inserts.
My second function will run once a second and take all the new values of this list and add them to the listbox by index.
This is a simple example but it should be a good starting point for you.
import tkinter as tk
root = tk.Tk()
add_tracker = 1
new_lb_items = []
lb = tk.Listbox(root)
lb.pack()
def add_to_listbox():
global add_tracker, new_lb_items, root
new_lb_items.append([add_tracker, "Number {}".format(add_tracker)])
add_tracker += 1
root.after(250, add_to_listbox)
def update_listbox_display():
global lb, new_lb_items, root
for item in new_lb_items:
lb.insert(item[0], item[1])
new_lb_items = [] # resets the list so only new values are added next time.
root.after(1000, update_listbox_display)
add_to_listbox()
update_listbox_display()
root.mainloop()

How can I get the button id when it is clicked? [duplicate]

This question already has answers here:
tkinter creating buttons in for loop passing command arguments
(3 answers)
Closed 5 years ago.
I don't get how to reference the button being clicked in tkinter.
My code:
for file in files:
btn = Button(root, text=file).pack()
Now e.g. 50 buttons are generated from files which is the source.
However when I click any button, only the LAST button is being referenced, but not the button I really want to use/click.
In JavaScript we use this to reference the object we really clicked, however I couldn't find any solution in Python for this.
This can be done with something like the below:
from tkinter import *
root = Tk()
files = [] #creates list to replace your actual inputs for troubleshooting purposes
btn = [] #creates list to store the buttons ins
for i in range(50): #this just popultes a list as a replacement for your actual inputs for troubleshooting purposes
files.append("Button"+str(i))
for i in range(len(files)): #this says for *counter* in *however many elements there are in the list files*
#the below line creates a button and stores it in an array we can call later, it will print the value of it's own text by referencing itself from the list that the buttons are stored in
btn.append(Button(root, text=files[i], command=lambda c=i: print(btn[c].cget("text"))))
btn[i].pack() #this packs the buttons
root.mainloop()
So what this does is create a list of buttons, each button has a command assigned to it which is lambda c=i: print(btn[c].cget("text").
Let's break this down.
lambda is used so that the code following isn't executed until the command is called.
We declare c=i so that the value i which is the position of the element in the list is stored in a temporary and disposable variable c, if we don't do this then the button will always reference the last button in the list as that is what i corresponds to on the last run of the list.
.cget("text") is the command used to get the attribute text from a specific tkinter element.
The combination of the above will produce the result you want, where each button will print it's own name after being pressed, you can use similar logic to apply it to call whatever attribute or event you need.

Is it ok to create and place a tkinter widget at the same time?

I am new in Python and in tkinter so the question may seems naive: is it ok to create and place widgets at the same time if I don't need to change them?
It works but is it a good practice? And if not why?
An example of what I mean:
import tkinter as tk
window=tk.Tk()
tk.Label(window,text='Lost Label').pack()
window.mainloop()
To expand upon #Skynet's answer....
Whenever you do Widget(*args, **kwargs).pack() the pack() method returns None as would other geometry managers, so if you tried to assign this to a variable the variable would be None.
In this case then probably not, since you probably actually want to be storing the reference to the widget.
If you don't need a reference then there's not really a problem with it. As the other answer notes you don't need a definitve reference to every single widget in your GUI unless you plan to use this reference in some way. Unless I plan on changing the label text / modifying it in someway then I typically use your method to save some space. No need to write more code than you have to!
For example you're creating a Button widget.
btn = Button(blabla, text="Button1")
This returns a button object and if you need later to configure it or get information about it you can do it by through the btn variable.
But if you use something like btn = Button(blabla, text="Button1").pack() it returns None and not a button object so you won't be able to change anything about the button or get information about it later.
Another example is with the Entry widget
entry = Entry(blabla)
Using that later you can do entry.get() to get the text inside the entry
but you won't be able to do it if you use entry = Entry(blabla).pack() since it doesn't return an entry object, it just packs the widget and you won't be able to access it for later use.
There is nothing wrong with that approach and I have already seen it quite a few times. You don't have to keep a reference to every widget in your GUI.

Clear the contents of a window and replace them with new contents?

Is there a command/function in Python to erase the contents of a Tk() (window) and re-use the frame within which the window is inside?
I want to ask a user for input, then erase the labels/buttons in the window, and set up new labels/buttons inside the same window.
Also how do I create a list of labels with which to loop through and add to a Tk() window?
Keep a reference to all the widgets, and call the destroy() method on each one. Or, put all of the widgets inside another frame and destroy the frame -- destroying a frame will automatically destroy all children widgets.
I don't understand the question about creating labels in a loop. You do it like you create anything else in a loop. You can save the references in a list, though a dictionary is also convenient:
labels = {}
for name in ("one", "two", "three"):
labels[name] = tk.Label(..., text=name)
labels[name].pack(...)

Categories