how to generate multiple buttons with a loop? - python

I have programmed software that displays a "tuile".
Definition of a tuile:
A tuile is a Frame which contains a button which displays an image and an explanatory text.
I would like to display 3 tuiles with 3 different settings.
listes_icones = ["icone1.png","icone2.png","icone3.png"]
listes_relx = [".3",".4",".5"]
listes_text = ["SYSTEM", "USER", "GAME"]
for i in range(3):
gen_img = PhotoImage(file=listes_icones[i])
gen_cadre = Frame(home,width=100, height=100,bg=bg_root)
gen_cadre.place(anchor="c", relx=listes_relx[i], rely=.5)
gen_img_bouton = Button(gen_cadre, image=gen_img, relief="flat",bg=bg_root)
gen_img_bouton.pack()
gen_text = Label(gen_cadre, text=listes_text[i], bg=bg_root, fg=text_color,font="blocktastic 18")
gen_text.pack()
I manage to display the text but not the button and the image, the variable is overwritten. How to solve this problem?

The problem that you are facing is like you said, the variable is overwritten in your loop. To solve this you need to keep track of your generated images. A simple solution is to store them in a list and get them in the next step. Here is an exampel:
import tkinter as tk
import PIL
listes_icones = ["icone1.png","icone2.png","icone3.png"]
gen_icons = []
listes_relx = [".3",".4",".5"]
listes_text = ["SYSTEM", "USER", "GAME"]
home = tk.Tk()
for i in range(3):
gen_img = tk.PhotoImage(file=listes_icones[i])
gen_icons.append(gen_img)
gen_cadre = tk.Frame(home,width=100, height=100)
gen_cadre.place(anchor="c", relx=listes_relx[i], rely=.5)
gen_img_bouton = tk.Button(gen_cadre, image=gen_icons[i], relief="flat")
gen_img_bouton.pack()
gen_text = tk.Label(gen_cadre, text=listes_text[i], font="blocktastic 18")
gen_text.pack()
home.mainloop()

Related

Can load image, but when I load it again within the second round of a loop it crashes

Title, it returns the error "_tkinter.TclError: image "pyimage3" doesn't exist" I have tried changing the root = tk.Tk
() to root = tk.TopLevel() as suggested by previous users who received the same error.
The script goes through a list of image files on a CSV file, the idea is to create a slideshow and the CSV file saves the formats of the different slides.
The "iam" if statement refers to an "image and message" slide which is what's currently drawing the errors. I have tried opening the image using the same calls as the regular image slide type but it crashes in a new and unique way every time.
I can post more information as needed but if anybody has any ideas as to how I could fix this I would love to hear them.
# import required modules
import tkinter as tk
from tkinter import *
from PIL import Image
from PIL import ImageTk
import pandas as pd
import datetime
import display_save
# Initialize tkinter window
root = tk.Tk()
# Retreive data table
frame = pd.read_csv("data.csv")
# Establish variables
show_size = len(frame.index)
img = ImageTk.PhotoImage(Image.open("teapot.png"))
bg_img = ImageTk.PhotoImage(Image.open("teapot.png"))
time_step = 1
# Initialize image label as empty
img_lbl = Label(root)
img_lbl.pack()
# Initialize text label
txt_lbl=Label(root)
txt_lbl.pack()
img_txt_lbl = Label(root)
img_txt_lbl.pack()
# Keypress event management
res_lbl = Label(root)
def keypress(event):
global res_lbl
if(event.char == "f"):
root.attributes('-fullscreen', False)
elif(event.char == "r"):
res_lbl.pack()
res_lbl.config(text= str(root.winfo_width()) + " x " + str(root.winfo_height()))
def keyrelease(event):
global res_lbl
if (event.char == "f"):
root.attributes('-fullscreen', True)
elif (event.char == "r"):
res_lbl.pack_forget()
# bind key events
root.bind("<KeyPress>",keypress)
root.bind("<KeyRelease>",keyrelease)
x = 0
# Function to rotate images
def runtime():
global x
global img
global img_lbl
global txt_lbl
global img_txt_lbl
global bg_img
if(x <= show_size):
df = pd.read_csv('data.csv')
df = df.iloc[[x - 1]]
t = df.iloc[0]['type']
date_remv = df.iloc[0]['date_remove']
# If type is image, initialize
if(t == "img"):
img_lbl.pack()
txt_lbl.pack_forget()
img_txt_lbl.pack_forget()
root.config(bg='white')
p = df.iloc[0]['data']
temp = Image.open(p)
temp = temp.resize((root.winfo_width(), root.winfo_height()))
img = ImageTk.PhotoImage(temp)
# If type is message, initialize
elif (t == "msg"):
txt_lbl.pack()
img_lbl.pack_forget()
img_txt_lbl.pack_forget()
m = df.iloc[0]['data']
c = df.iloc[0]['data2']
txt_lbl.config(bg =c, text=m, anchor=CENTER, height=20, wraplength=1000, font=("Arial", 50))
root.config(bg=c)
# If type is an image and a message, initialize
elif (t == "iam"):
img_txt_lbl.pack()
txt_lbl.pack_forget()
img_lbl.pack_forget()
p = df.iloc[0]['data']
temp = Image.open("teapot.png")
temp = temp.resize((root.winfo_screenwidth(), root.winfo_screenheight()))
temp = ImageTk.PhotoImage(temp)
bg_img = temp
m = df.iloc[0]['data2']
img_txt_lbl.config(text=m, height=root.winfo_screenheight(), width=root.winfo_screenwidth(), wraplength=1000, font=("Arial", 50), compound='center')
root.config(bg='white')
# Check to make sure the slides list is up-to date
if(datetime.datetime.strptime(date_remv, display_save.format) <= datetime.datetime.now()):
index = df.iloc[0]['id']
display_save.delete_row(index)
root.after(time_step * 1000, runtime)
else:
x = 0
root.after(0, runtime)
x = x + 1
img_lbl.config(image=img)
img_txt_lbl.config(image=bg_img)
runtime()
root.attributes('-fullscreen', True)
root.mainloop()
Whenever you create a new image using ImageTk.PhotoImage() you have to make sure that image variable stays during the entire time your code runs. This means for example if you create the first image in a variable img then you must not use the SAME variable for a new image because once the old image is rewritten it's gone. So the trick here is to store the old image somewhere where it doesn't change. To do this simply append the old image to a new list where you can store all PHOTOIMAGEs as a memory. I recently answered the same type of question here and there might be a better explanation: How to create multiple images in tkinter that can scale.
I haven't tested if this works but I believe it should work.

Using a for loop to create buttons in tkinter and give them each a unique input

Hello I am working on a screenshot project. For part of it I would like to open up a window with a button for every screenshot then be able to click on one and get the image to show up.
I so far used this function to create the buttons when it opens the window.
def create_secondwindow_button():
x = 1
y = 0
mypath = "C:\\Users\\Link\\OneDrive\\Desktop\\python stuff\\screenshot example\\All snapshot images"
for I in listdir(mypath):
btp = mypath +"\\"+str(I)
print(btp)
screenshot_snap = Button(sec_window,text = str(I), bg = "chocolate3",activebackground = "white",padx= 80,pady =10,command =lambda: image_replacer(y))
screenshot_snap.grid(column = 4, row = int(x),rowspan = 5,columnspan = 5,padx = 50,pady =10)
x += 10
if y < 3:
y += 1
The problem is the input for all the buttons just remains at the last value of y for all of them.
I need to have a variable input as the amount of images are going to be constantly changing.
Does anyone know how I can solve this issue. Thanks a bunch!

Python Tkinter Checkbutton-text Alignment Issue

I am fooling around with GUI programming using Tkinter on python, with the goal being a GUI app which will allow a user to add a task or delete (to be saved to a txt file).
Right now, I am just trying to figure out the basics of adding/removing checkbuttons.
The issue I am having is that when I run the script, the check boxes are not aligned with the text for them. The picture below shows the issue.
The code used to pull tasks from a txt file and add them as checkbuttons is as follows:
tasks = []
with open(r"C:\Users\patte\Desktop\tasks.txt") as file:
tasks = file.readlines()
row = 2
for task in tasks:
self.task_checkbox = Checkbutton(self.master,text=task,font=("Arial",12),relief='groove')
self.task_checkbox.grid(row=row,column=0,sticky=W)
row+=1
Note that row is initialized to 2 as the title and entry bar take up rows 0 and 1. I have tried changing fonts, etc., but the result stays the same.
Any sort of advice or criticism is greatly appreciated.
The full code is as follows:
from tkinter import *
class ToDo():
def __init__(self,master):
self.master = master # Geometry and layout when initialized
master.geometry("800x800")
master.title("TO DO")
self.gui_title = Label(self.master,text="Tasks:",font = ("Arial Bold",26))
self.gui_title.grid(row=0,column=0,sticky=W)
self.task_entry_label = Label(self.master,text="Enter Task:",font = ("Arial",12))
self.task_entry_label.grid(row=2,column=0,sticky=W)
self.task_entry = Entry(self.master)
self.task_entry.grid(row=2,column=1,sticky=W)
self.task_entry.focus_set()
tasks = []
with open(r"C:\Users\patte\Desktop\tasks.txt") as file:
tasks = file.readlines()
row = 3
for task in tasks:
self.task_checkbox = Checkbutton(self.master,text=task,font=("Arial",12),relief='groove')
self.task_checkbox.grid(row=row,column=0,sticky=W)
row+=1
self.QuitButton = Button(self.master,text="Quit",font=("Arial Bold",12),command = lambda: self.master.quit())
self.QuitButton.grid(sticky=SW,row=7)
root = Tk()
Tasks = ToDo(root)
root.mainloop()
Thanks!

How do you use tkinter entry boxes to make a variable?

I'm currently trying to create an "orbit simulator" and this part of the code is part of it. However an errors occur when I try to run it. The get() function seems to not work as it simply outputs that it doesn't exist.
I'm really stumped at this.
import tkinter
runwin = tkinter.Tk()
runwin.title("Orbit Sim")
runwin.geometry("320x320")
def run21():
dt=ent21.get("1.0")
tg=ent22.get("1.0")
xz=ent23.get("1.0")
yz=ent24.get("1.0")
velz=ent25.get("1.0")
runwin.destroy()
lbl21 = tkinter.Label(runwin, text="How long to simulate for?").pack()
ent21 = tkinter.Entry(runwin).pack()
lbl22 = tkinter.Label(runwin, text="How many seconds pass per check?").pack()
ent22 = tkinter.Entry(runwin).pack()
lbl23 = tkinter.Label(runwin, text="Starting Positon? Please state X then Y.").pack()
ent23 = tkinter.Entry(runwin).pack()
ent24 = tkinter.Entry(runwin).pack()
lbl24 = tkinter.Label(runwin, text="Starting Velocity").pack()
ent25 = tkinter.Entry(runwin).pack()
btn21 = tkinter.Button(runwin, text="Submit", command=run21).pack()
runwin.mainloop()
t=0
while t < dt:
r3, t =m.sqrt((xz*xz)+(yz*yz)), t+tg
P.S. I'm not a genius at coding and the way i've written this code is pretty much the only way I can understand it without sufficient notes.
You have 3 problems I can see.
1st problem is you are using the grid manager on the widget directly and this will cause your get() method to error out. That is be cause the grid manager is returning None.
We can fix this by calling the grid manager on a new line.
2nd you are putting "1.0" in the get method and this is going to error out.
Just leave it blank like this get().
3rd you need to define your variables to be ran after the program closes as outside of the tkinter instance. Then you need to set the global call up in your function.
Take a look at the below code:
import tkinter
# the 5 below variables are listed outside of tkinter so your while statement
# after the mainloop can use the data.
dt = ""
tg = ""
xz = ""
yz = ""
velz = ""
runwin = tkinter.Tk()
runwin.title("Orbit Sim")
runwin.geometry("320x320")
def run21():
# this global is needed to tell the run21 function the variables are
# in the global namespace.
global dt, tg, xz, yz, velz
dt=ent21.get()
tg=ent22.get()
xz=ent23.get()
yz=ent24.get()
velz=ent25.get()
runwin.destroy()
lbl21 = tkinter.Label(runwin, text="How long to simulate for?").pack()
ent21 = tkinter.Entry(runwin)
ent21.pack()
lbl22 = tkinter.Label(runwin, text="How many seconds pass per check?").pack()
ent22 = tkinter.Entry(runwin)
ent22.pack()
lbl23 = tkinter.Label(runwin, text="Starting Positon? Please state X then Y.").pack()
ent23 = tkinter.Entry(runwin)
ent23.pack()
ent24 = tkinter.Entry(runwin)
ent24.pack()
lbl24 = tkinter.Label(runwin, text="Starting Velocity").pack()
ent25 = tkinter.Entry(runwin)
ent25.pack()
btn21 = tkinter.Button(runwin, text="Submit", command=run21).pack()
t=0
runwin.mainloop()
print(dt, tg, xz, yz, velz)
# commented out as it is not testable without knowing what "m" is.
# while t < dt:
# r3, t = m.sqrt((xz*xz)+(yz*yz)), t+tg
Don't define a widget and use the layout manager on the same line if you wish to use the Widget.
i.e DON'T DO THIS
ent21 = tkinter.Entry(runwin).pack()
DO THIS
ent21 = tkinter.Entry(runwin)
ent21.pack()
Like so
import tkinter
runwin = tkinter.Tk()
runwin.title("Orbit Sim")
runwin.geometry("320x320")
dt = ""
tg = ""
xz = ""
yz = ""
velz = ""
def run21():
global dt, tg, xz, yz, velz
dt=ent21.get()
tg=ent22.get()
xz=ent23.get()
yz=ent24.get()
velz=ent25.get()
runwin.destroy()
lbl21 = tkinter.Label(runwin, text="How long to simulate for?").pack()
ent21 = tkinter.Entry(runwin)
ent21.pack()
lbl22 = tkinter.Label(runwin, text="How many seconds pass per check?").pack()
ent22 = tkinter.Entry(runwin)
ent22.pack()
lbl23 = tkinter.Label(runwin, text="Starting Positon? Please state X then Y.").pack()
ent23 = tkinter.Entry(runwin)
ent23.pack()
ent24 = tkinter.Entry(runwin)
ent24.pack()
lbl24 = tkinter.Label(runwin, text="Starting Velocity").pack()
ent25 = tkinter.Entry(runwin)
ent25.pack()
btn21 = tkinter.Button(runwin, text="Submit", command=run21).pack()
runwin.mainloop()
One of the other commenters is correct too. Any code after .mainloop will not run until after the window is closed. Consider doing this inside the run21 function so it happens when the button is pressed.
I've removed "1.0" from your get since the get method of an entry widget doesn't take any arguments.
Also please consider better naming for your variables. Instead of ent21 consider entrySimulationTime or instead of ent24 consider entryStartingPosY. Your code will be much easier to understand that way.
EDIT: Added globals to expand the scope of dt etc.

Change the color of a single word in a tk option menu?

So I'm grabbing links of events off a website and putting them into a drop down menu to be selected. My code for the menu:
import Tkinter as tk
from Tkinter import StringVar
selectMenu = tk.Tk()
# #-> this is what I have
# Followed by what you can use
#var = Vars()
#events = var.GetVars('Event')
events = " "
options = []
links = []
#forms = (driver.find_elements_by_class_name("with-cats")) #This is what I have
forms = ["Yolo ","Dad? Closed","Anotha One","Normies! Closed"] #This is so you can try it for yourself
for x in forms:
#info = x.text
info = x #Again, this is so you can try it for yourself
if events in info.lower():
links.append(x)
for link in range(0,len(links)):
#options.append(links[link].text)
options.append(links[link])
list(set(options))
selection = []
for link in range(0,len(options)):
selection.append(options[link])
select = StringVar(selectMenu)
select.set("--None Selected--")
menu = tk.OptionMenu(selectMenu, select, *(selection))
msg = "Which one would you like to attend?"
label = tk.Label(selectMenu, text=msg, font="Helvedica 14")
label.pack(side='top', pady=10)
menu.pack(side="top", pady=10)
selectMenu.attributes('-topmost', True)
selectMenu.mainloop()
So this works fine and dandy, but I would like to improve the look to make it more obvious which events are open. To clarify, an event found that is open and put into the menu may look like "This is a cool event", but one that is closed would be read as "This is a cool event Closed". My aim is to be able to make the foreground red of either just the word Closed or the string containing Closed, whichever is possible if any (And I'm not sure if it's possible because menus and buttons on osx are usually defaulted to system settings, maybe there is a way around this?).
Current: Desired:
According to the documentation for OptionMenu here and here I don't think there is a way to set the color of text.
You might be able to get something close to what you want by using a listBox instead. See post here for the listBox example.
Found a solution! Using a Menu inside of a MenuButton the same way Tkinter creates MenuOptions, I was able to create a custom MenuOption. If you want to add more options, you can use the menbutton.configure() option to edit the button, and menbutton.menu to edit the menu items.
import Tkinter as tk
from Tkinter import Menu, Menubutton
class Vars():
global vari
vari = {}
def GetVars(self, var):
return vari.get(str(var))
def SendVars(self, var, val):
vari[str(var)] = val
class App():
def buttselect(self, link, menbutton, selectMenu):
var = Vars()
var.SendVars("Selection", link) # Store selected event
menbutton.configure(text=link) # Set menu text to the selected event
def prnt(self, link):
var = Vars()
print var.GetVars("Selection") # Print event
def __init__(self, selectMenu):
events = " "
options = []
links = []
forms = ["Yolo ","Dad? Closed","Anotha One","Normies! Closed"] #This is so you can try it for yourself
menbutton = Menubutton (selectMenu, text="--None Selected--", relief="raised")
menbutton.grid()
menbutton.menu = Menu (menbutton, tearoff=0)
menbutton["menu"] = menbutton.menu
#Get a list of event names
for x in forms:
info = x #Again, this is so you can try it for yourself
#If desired event keyword is in an event name, add it to the correct links
if events in info.lower():
links.append(x)
#Remove duplicates
for link in range(0,len(links)):
options.append(links[link])
list(set(options))
#Final list of event names turned into menu commands
for link in options:
if "Closed" in link:
menbutton.menu.add_command( label= link, command= lambda link=link: self.buttselect(link, menbutton, selectMenu), foreground='red')
else:
menbutton.menu.add_command( label= link, command= lambda link=link: self.buttselect(link, menbutton, selectMenu))
b = tk.Button(selectMenu, text="Selection", command= lambda link=link: self.prnt(link)) #Print selected event
b.pack()
msg = "Which one would you like to attend?"
label = tk.Label(selectMenu, text=msg, font="Helvedica 14")
label.pack(side='top', pady=10)
menbutton.pack(side="top", pady=10)
selectMenu = tk.Tk()
selectMenu.attributes('-topmost', True)
app = App(selectMenu)
selectMenu.mainloop()
This results in exactly the result desired:
I found a way!
Let's say x is an optionmenu with options:
options=['Red','Blue','Green']
defopt=tk.StringVar(options[0]) #StringVariable to hold the selected option.
x=tk.OptionMenu(self.optmenuframe,defopt,*options)
Now, get the menu object from the optionmenu and use entryconfig method. That's it!
x.children['menu'].entryconfig(0,foreground='red')
x.children['menu'].entryconfig(1,foreground='blue')
x.children['menu'].entryconfig(2,foreground='green')
#0 is the index of the option you want to apply the configurations to.

Categories