Creating multiple widget using for loop in tkinter - python

I would like to create multiple button with each button in variable , for example btn_0,btn_1,....,btn_i
this is so configure and change them letter.
I have folder with images names q0.png,q1.png,..... each button takes an image
I am able to create and pack multiple buttons via (without images on button)
for i in range(5):
tk.Button(second_frame,text=f'Button No. {i}').pack()
-- for single instance
j=0
path = f'Questions/q{j}.png'
btn_img_path = tk.PhotoImage(file=path)
tk.Button(
second_frame,
image=btn_img_path,
borderwidth=0,
highlightthickness=0,
command=lambda:print('A'),
relief="flat"
).pack()
but trying to do it in a for loop -- only last button appear with image, other buttons don't appear at all, but their place is empty (last image is packed down in window) it seems like they are created and then overwritten
for i in range(5):
path_q = f'Questions/q{i}.png'
btn_img = tk.PhotoImage(file=path_q)
tk.Button(second_frame,
image=btn_img,
borderwidth=0,
highlightthickness=0,
command=lambda:print(i),
relief="flat").pack()

Here is piece of code you could add that I guess would meet your expectations:
for i in range(5):
path_q = f'Questions/q{i}.png'
btn_img = tk.PhotoImage(file=path_q)
btn_name = f'btn_{i}'
btn_name = tk.Button(second_frame,
image=btn_img,
borderwidth=0,
highlightthickness=0,
command=lambda:print(i),
relief="flat").pack()
pls tell me if this causes any problems as I cannot test the code if it works or not, by commenting. I guess the indenting is wrong but that is fixable

There are two issues in the code:
using same variable btn_img for all the images, so only the last image is being referenced and the rest are garbage collected. You need to keep references to all the images to avoid garbage collection.
command=lambda:print(i) will make all buttons to print the same value (it is 4 in your case) when the buttons are clicked. You need to use default value of argument to fix it: command=lambda i=i:print(i).
Below is the updated for loop:
for i in range(5):
path_q = f'Questions/q{i}.png'
btn_img = tk.PhotoImage(file=path_q)
btn = tk.Button(second_frame,
image=btn_img,
borderwidth=0,
highlightthickness=0,
command=lambda i=i:print(i), # used default value of argument
relief="flat")
btn.pack()
btn.image = btn_img # save reference of image
If you want to access the buttons later, it is better to use a list to store the references of the buttons.

Related

How to display widgets on a frame that is mounted on canvas in Tkinter?

I created a main root with two frames.
-One frame is for program toolbar.
-Other frame is for canvas where data will be displayed and a scrollbar widget.
-Inside of the canvas is a third smaller frame which will be used for scrolling trough data.
However, when I try to define new widgets and place them on that third smaller frame, nothing happens. I'm defining new widgets inside of a function call of a button command. I have also tried declaring everything as global variables but without luck.
Hint: I tried placing the code from the function to the top level of the code and it works. Also, if I try to mount these widgets on the toolbar frame it also works. It seems that the only thing I can't do is to mount these new widgets on the small frame that is inside the canvas.
I used a simple for loop to create labels just for testing.
Could anyone tell what I am doing wrong?
from tkinter import *
from tkinter import ttk
#Creating main window
root = Tk()
root.resizable(width=False, height=False)
#Defining Background
toolbar = Frame(root, width=613, height=114)
toolbar.grid(row=0, column=0)
background_frame = Frame(root, width=615, height=560)
background_frame.grid(row=1, column=0)
background = Canvas(background_frame, width=615, height=560)
background.pack(side=LEFT, fill=BOTH, expand=1)
scroll_bar = ttk.Scrollbar(background_frame, orient=VERTICAL, command=background.yview)
scroll_bar.pack(side=RIGHT, fill=Y)
background.configure(yscrollcommand=scroll_bar.set)
background.bind('<Configure>', lambda e:background.configure(scrollregion = background.bbox('all')))
second_frame = Frame(background)
background.create_window(150,100, window=second_frame, anchor='nw')
def confirm1():
for x in range(100):
Label(second_frame, text = x ).grid(row=x, column=1)
show_labels = Button(toolbar, text= "Show labels", fg="black", command=confirm1)
show_labels.grid(row=0, column=2)
root.mainloop()
Picture of the app so far
I surely can't reproduce the issue with your current code, but looking at the previous edit it is pretty clear what your problem is.
(taken from your previous edit)
def confirm1():
global background_image1
background.delete('all') # <--- this line of code
for x in range(100):
Label(second_frame, text = x ).grid(row=x, column=1)
Here you delete all your items from your canvas:
background.delete('all')
hence no item appears.
You should instead delete only those items that you want to remove by passing the id or tags to delete method. You can delete multiple items together at once by giving the same tags.
Another option would be to recreate the frame item again on canvas using create_window (Do note: your frame is not deleted/destroyed, it's only removed from canvas)

tkinter button reffer self atribute

A have a Grid of 38 buttons (num0-num38).
I'm trying to pass the button attribute (text) to the print function.
Code:
def collect_num(num):
print(num)
num0 = tk.Button(buttons, text="0", padx=10, pady=5, fg="white", bg="green", command=collect_num(thisItem.text))
Is there something similar to thisItem.text in Python?
So basically, I would like to print the button name (text="0" - text="38") when the correspondent button is pressed.
Any help with that is appreciated.
Since you asked for an example, take a look here:
from tkinter import *
root = Tk()
MAX_BUTTON = 25 # Maximum number of buttons to be made
def collect_num(num):
print(num) # Print the number
btns = [] # A list to add all the buttons, to make them reusable later
for i in range(MAX_BUTTON): # Looping 25 times or maximum number of buttons, times
btns.append(Button(root,text=i,width=10)) # Creating a button and adding it to list
btns[-1]['command'] = lambda x=btns[-1].cget('text'): collect_num(x) # Changing the command of the button
btns[-1].pack() # Packing it on
root.mainloop()
Here cget() returns the current properties of the widget.
It is highly recommended to do this, instead of creating buttons one by one manually. There is a principle in software engineering called DRY which means Don't Repeat Yourself, which usually means if your repeating yourself, there will possibly be a way to not repeat your self and reduce the code written. And this looping and creating a widget and using lambda to make the command is quiet used frequently when needed to make more than one widget with a pattern.
you need pass the button text using partial(<function>, *args) function
from functools import partial
def collect_num(num):
print(num)
# create the button first and add some specs like `text`
button = tk.Button(
buttons, text="0", padx=10, pady=5, fg="white", bg="green"
)
# add command after creation with the above function and button's name
button.config(command=partial(collect_num, button["text"]))
with the help of this stack question:
text = button["text"]
this is the method with which you can get the text.
or
text = button.cget('text')
or (with the help of #TheLizzard)
text = button.config("text")[-1]

Python - Tkinter Event Generation

I am doing a GUI using for my code and finding it hard to develop what i want.
First ...
on the window an image and label appear and disappear after couple of seconds. after that a frame should arrive, followed by a menu option, when the menu option is selected it should disappear from frame and another image should appear.
self.gui = Tk()
def configure_window(self):
self.gui.geometry('700x500')
self.gui.resizable(height=False, width=False)
self.gui.update()
self.welcome_label()
self.welcome_image()
def welcome__image(self):
image = Image.open(image path)
photo = ImageTk.PhotoImage(image)
welcome_image = Label(master=self.gui, image=photo, justify='center',relief='raised')
welcome_image.image = photo
welcome_image.pack(padx=100, pady=100)
welcome_image.place(bordermode='outside', x=200,y=100)
welcome_image.after(ms=1000, func=welcome_image.place_forget)
def welcome_label(self):
welcome_label = Label(master=self.gui, text=welcome_label_text,font=welcome_label_font, justify='center',
state='active', fg='blue', anchor='center', pady=10, relief='raised' )
welcome_label.place(x=100, y=350)
welcome_label.after(ms=1000, func=welcome_label.place_forget)
def first_frame(self):
self.first_frame = LabelFrame( master = self.gui,text='Select First File',font= 'arial 10 bold underline', bg=self.background_colour,
height=200,width=690, bd=1, padx=5, pady=5)
self.first_frame.pack()
self.first_frame.place(x=0, y=0)
def select_button1(self):
self.file_open_button1 = Button(master=self.first_frame,text='...',state='disabled',command=self.cmd_file_select_button1)
when i run the code, welcome_image and welcome_frame places and forgets places after 1000 ms, but, first_frame also loads with parent widget. i want frame to arrive after the welcome_label disappears.
and the same sequence continues for LabelFrame
i have widgets in LabelFrame in which i have OptionMenu and button, i want optionMenu to disappear when user selects an option and another button should appear, same sequence in the button, button returns some sting and a label should appear in the place of button etc.
how to generate the sequence e.g. if an widget has place_forget() a sequence will be generated and another widget should be bind on that sequence. please help.
apology, if i am unable to make you understand my problem.

Updating a label from an entry field on button push with tkinter in Python 3.5.2

I am trying to create a window with a line label, an entry field, a current value label, and an "Update Value" button.
Here is an example:
This is what I have so far. I can get the entered value to print to console, but I can't seem to work out how to get an entered value and change the currentValue Label to reflect that value by pressing the button:
from tkinter import*
main=Tk()
#StringVar for currentValue in R0C2
currentValue = StringVar(main, "0")
#Called by the setValues button, looks for content in the entry box and updates the "current" label
def setValues():
content = entry.get()
print(content)
#This kills the program
def exitProgram():
exit()
#Title and window size
main.title("Title")
main.geometry("350x200")
#Descriptions on the far left
Label(main, text="Duration (min): ").grid(row=0, column=0)
#Entry boxes for values amidship
entry=Entry(main, width=10)
entry.grid(row=0, column=1)
#Displays what the value is currently set to.
currentValue = Label(textvariable=currentValue)
currentValue.grid(row=0,column=2)
#Takes any inputted values and sets them in the "Current" column using def setValues
setValues=Button(text='Set Values',width=30,command=setValues)
setValues.grid(row=9, column=0, columnspan=2)
#Red button to end program
exitButton=Button(main, text='Exit Program',fg='white',bg='red',width=30, height=1,command=exitProgram)
exitButton.grid(row=20, column = 0, columnspan=2)
main.mainloop()
There are a couple of problems with your code.
Firstly, you are overwriting the setValues function with the setValues Button widget, and similarly, you are overwriting the currentValue StringVar with the currentValue Label.
To set a StringVar, you use its .set method.
Don't use plain exit in a script, that's only meant to be used in an interactive interpreter session, the proper exit function is sys.exit. However, in a Tkinter program you can just call the .destroy method of the root window.
Here's a repaired version of your code.
import tkinter as tk
main = tk.Tk()
#StringVar for currentValue in R0C2
currentValue = tk.StringVar(main, "0")
#Called by the setValues button, looks for content in the entry box and updates the "current" label
def setValues():
content = entry.get()
print(content)
currentValue.set(content)
#This kills the program
def exitProgram():
main.destroy()
#Title and window size
main.title("Title")
main.geometry("350x200")
#Descriptions on the far left
tk.Label(main, text="Duration (min): ").grid(row=0, column=0)
#Entry boxes for values amidship
entry = tk.Entry(main, width=10)
entry.grid(row=0, column=1)
#Displays what the value is currently set to.
currentValueLabel = tk.Label(textvariable=currentValue)
currentValueLabel.grid(row=0,column=2)
#Takes any inputted values and sets them in the "Current" column using def setValues
setValuesButton = tk.Button(text='Set Values',width=30,command=setValues)
setValuesButton.grid(row=9, column=0, columnspan=2)
#Red button to end program
exitButton = tk.Button(main, text='Exit Program',fg='white',bg='red',width=30, height=1,command=exitProgram)
exitButton.grid(row=20, column = 0, columnspan=2)
main.mainloop()
BTW, it's a Good Idea to avoid "star" imports. Doing from tkinter import * dumps 130 names into your namespace, which is unnecessary and creates the possibility of name collisions, especially if you do star imports from several modules. It also makes the code less readable, since the reader has remember which names you defined and which ones came from the imported module(s).
In my opinion the easiest way to do this would be using an object orientated method. This way you could declare a button with a command that calls a def which runs self.label.configure(text=self.entry.get()).
This can be seen below:
import tkinter as tk
class App:
def __init__(self, master):
self.master = master
self.label = tk.Label(self.master)
self.entry = tk.Entry(self.master)
self.button = tk.Button(self.master, text="Ok", command=self.command)
self.label.pack()
self.entry.pack()
self.button.pack()
def command(self):
self.label.configure(text=self.entry.get())
root = tk.Tk()
app = App(root)
root.mainloop()
The above creates a label, entry and button. The button has a command which calls a def within the class App and updates the value of the label to be the text contained within the entry.
This all works very smoothly and cleanly and more importantly is drastically easier (in my opinion) to read and update in the future.
From your code you are setting the 'currentValue', which is a StringVar:
#StringVar for currentValue in R0C2
currentValue = StringVar(main, "0")
to an object Label further down in your code. You cannot do this!
#Displays what the value is currently set to.
currentValue = Label(textvariable=currentValue) ** this line is wrong
currentValue.grid(row=0,column=2)
You should name the label something different like:
#Displays what the value is currently set to.
lblCurrentValue = Label(textvariable=currentValue)
lblCurrentValue.grid(row=0,column=2)
Then in your "setValues" method you should use 'StringVar.set(value) to update the label like so:
def setValues():
content = entry.get()
currentValue.set(entry.get())------------------Here I set the value to the entry box value
print(content)
I tend to avoid stringVar and just use:
Label.config(text='*label's text*')
If you need more help I can post you my solution but try and solve it first becasue its the best way to learn. My tip is to make sure you are using correct naming conventions. In tkinter I tend to use lbl..., entryBox... etc before widgets so I know what they are and not to confuse them with variables.

Trying to use "grid" and "place" in one frame after using "pack" in another frame but widgets are not visible

I'm currently working on an invoice generator in python tkinter for my coursework. I am quite new to programming and I have created a basic login page so when the login button is pressed (haven't setup restraints yet) the GUI moves to the next 'screen.' I am using frames to do this. However, on the next 'page' I can only pack() widgets, if I try and place them or use a grid they simply don't appear and I get an empty GUI window.
#!/usr/bin/python
# -- coding: utf-8 --
import tkinter as tk
root = tk.Tk()
def go_to_login():
f1.pack()
f2.pack_forget()
def go_to_first():
f1.pack_forget()
f2.pack()
root.geometry('1280x800')
root.title('Invoice')
f1 = tk.Frame(root)
label_US = tk.Label(f1, text='Username')
label_US.pack()
label_PW = tk.Label(f1, text='Password')
label_PW.pack()
entry_US = tk.Entry(f1)
entry_US.pack()
entry_PW = tk.Entry(f1, show='*')
entry_PW.pack()
checkbox_LI = tk.Checkbutton(f1, text='Keep me logged in')
checkbox_LI.pack()
but_LI = tk.Button(f1, text='login', command=go_to_first)
but_LI.pack()
but_QT = tk.Button(f1, text='Quit', command=quit)
but_QT.pack()
f2 = tk.Frame(root)
but_LO = tk.Button(f2, text='Logout', command=go_to_login)
but_LO.pack() # try to change pack here
but_HP = tk.Button(f2, text='Help')
but_HP.pack() # try to change pack here
but_NX1 = tk.Button(f2, text='Next', command=quit)
but_NX1.pack() # try to change pack here
f1.pack()
root.mainloop()
What I basically want is to be able to place or use grid to set the locations of my widgets also on the second frame, but unless I use pack I get an empty GUI screen. What have I done wrong or how can I place the widgets instead to packing them?
I'm not sure if this is the best way to do things and I have no experience using classes etc but I'm open to suggestions.
As long as you specify the row and column when using .grid(), the code works fine for both 'screens'. I can think of two possible reasons why you're code wasn't working
1) If you try and use .pack and .grid for two labels in the same frame, you will get an error
2) There may be something else in your code causing your error, if so, add it to your question
Anyway, here's the code that worked for me:
f2 = tk.Frame(root)
but_LO = tk.Button(f2, text='Logout', command=go_to_login)
but_LO.grid(row = 0, column = 0) # specify row and column
but_HP = tk.Button(f2, text='Help')
but_HP.grid(row = 1, column = 0)
but_NX1 = tk.Button(f2, text='Next', command=quit)
but_NX1.grid(row = 2, column = 0)

Categories

Resources