I made this program where I am putting labels on a grid without saving them in a variable. I do this because then I can for loop through a list of classes and get the data from each class in and add them to a row. This is a small piece of it:
self.collum = 0
for i in self.gui_resource_list:
Label(text=i.get_name(), relief="groove", width=15).grid(column=self.column, row=0)
Label(text=i.get_buyPrice(), relief="groove", width=15).grid(column=self.column, row=1)
Label(text=i.get_salePrice(), relief="groove", width=15).grid(column=self.column, row=2)
Label(text=i.arrow, relief="groove", width=15).grid(column=self.column,row=3)
self.column += 1
So this will generate a table-like layout. Then there is a button that updates all the values runs that for loop again. So it basically draws the new labels on top of the older ones. This is not good because when you are on the turn 300 there are 300 labels times the all the resource instances in gui_resource list. A way to fix this is to delete the old labels.
Is there a way to delete an unsaved label? Something like:
delete_grid(column=2,row=3)
And that would delete all of the things in the grid at position 2,3?
You can ask grid to give a list of widgets that it manages. You could then iterate over that list to find out which widget is in each row and column.
For example, if you wanted to be able to modify the text in the widget at a specific row or column, you could do something like this:
def set_item_text(master, row, column, text):
for child in master.grid_slaves():
grid_info = child.grid_info()
if grid_info['row'] == row and grid_info['column'] == column:
child.configure(text=text)
Here's an example that will change the text in row 2 column 2 to "hello, world":
import Tkinter as tk
def set_item_text(master, row, column, text):
for child in master.grid_slaves():
grid_info = child.grid_info()
if grid_info['row'] == row and grid_info['column'] == column:
child.configure(text=text)
root = tk.Tk()
for row in range(4):
for col in range(5):
tk.Label(
root, text="row %s col %s" % (row, col)
).grid(row=row, column=col, padx=8)
set_item_text(root, 2,2, "hello, world")
root.mainloop()
You could just as easily delete the widget, though if you're just wanting to refresh a "table-like thing" it's more efficient just to change the data than to delete and recreate all of the widgets.
from pprint import pprint
from tkinter import Tk, Label
root = Tk()
Label(root, text='MyLabel').pack()
Label(root, text='MyLabel').pack()
Label(root, text='MyLabel').pack()
# as you did not kept references to the labels
# you have to look into the childrens of root
pprint(root.children) # show root children names
print()
root.children['!label2'].destroy() # do what you asked on the second Label
pprint(root.children) # check that it's gone
Related
i want to add multiple values that the user select from different comboboxes. I then want to add together the values and display the total value next to the boxes. The problem seems to be that the "values" from the comboboxes are strings, and can therefore not be added together.
If you wanna run the program, just comment out the variable "totalvalues before the last for loop. (i know it looks wierd, i just tried to make a new program to show my problem)
import tkinter as tk
from tkinter import ttk
from tkinter import scrolledtext
win=tk.Tk() #window
win.title("Moskalkylator")
antalcomboboxes=4
mighty=ttk.LabelFrame(win,text='Welcome')
mighty.grid(column=0,row=0,padx=8 ,pady=4)
Items=['Wood','Iron','Plastic','Glass']
def click():
calculate = ttk.Label(win, text="Your total number of items is : " + totalvalues.get()) # See row 27
calculate.grid(column=1, row=0)
buttons_frame=ttk.LabelFrame(mighty)
buttons_frame.grid(column=1,row=8, padx=3, pady=3,sticky=tk.W)
calculate=ttk.Button(buttons_frame,text='Count', command=click).grid(column=1,row=8)
#Creates combobox 1
list1=list(range(11))
number_chosen1= ttk.Combobox(mighty,values=list1, state="readonly") #The number you pick in the combobox
number_chosen1.grid(column=1,row=1,sticky=tk.W)
#Creates combobox 2
list2=list(range(11))
number_chosen2= ttk.Combobox(mighty,values=list2, state="readonly")
number_chosen2.grid(column=1,row=3,sticky=tk.W)
###############
totalvalues=number_chosen1+number_chosen2 #This does not work! COMMENT THIS
###############
#Loop to get the names of the items above combobox
Items_order=0
row_move_loop2=0
for element in Items:
article = ttk.Label(mighty, text=Items[Items_order])
article.grid(column=1, row=row_move_loop2)
Items_order=Items_order+1
row_move_loop2=row_move_loop2+2
win.mainloop()
You have to use the get() method to get the values from combobox and convert them to integers. Also, don't keep creating new label everytime you want to display the count instead create the label once and use configure method to change the text.
Here is your corrected code:
import tkinter as tk
from tkinter import ttk
from tkinter import scrolledtext
win=tk.Tk() #window
win.title("Moskalkylator")
antalcomboboxes=4
mighty=ttk.LabelFrame(win,text='Welcome')
mighty.grid(column=0,row=0,padx=8 ,pady=4)
Items=['Wood','Iron','Plastic','Glass']
def click():
global totalvalues
if number_chosen1.get() != '' and number_chosen2.get() != '':
totalvalues= int(number_chosen1.get()) + int(number_chosen2.get())
calculate.configure(text="Your total number of items is : " + str(totalvalues))
buttons_frame=ttk.LabelFrame(mighty)
buttons_frame.grid(column=1,row=8, padx=3, pady=3,sticky=tk.W)
calculate=ttk.Button(buttons_frame,text='Count', command=click).grid(column=1,row=8)
#Creates combobox 1
list1=list(range(11))
number_chosen1= ttk.Combobox(mighty,values=list1, state="readonly") #The number you pick in the combobox
number_chosen1.grid(column=1,row=1,sticky=tk.W)
#Creates combobox 2
list2=list(range(11))
number_chosen2= ttk.Combobox(mighty,values=list2, state="readonly")
number_chosen2.grid(column=1,row=3,sticky=tk.W)
###############
print(number_chosen1.get())
totalvalues = 'None'
calculate = ttk.Label(win) # See row 27
calculate.grid(column=1, row=0)
###############
#Loop to get the names of the items above combobox
Items_order=0
row_move_loop2=0
for element in Items:
article = ttk.Label(mighty, text=Items[Items_order])
article.grid(column=1, row=row_move_loop2)
Items_order=Items_order+1
row_move_loop2=row_move_loop2+2
win.mainloop()
when u retrieve values as a string u need to convert them.
totalvalues=int(number_chosen1)+int(number_chosen2)
or
totalvalues=float(number_chosen1)+float(number_chosen2)
I am trying to create a program that allows the user to select any number of check boxes and hit a button to return a random result from those check boxes. Since I am basing my list off the roster of Smash bros ultimate, I am trying to avoid creating 70+ variables just to place check boxes. However, I am unable to figure out how to iterate this. The various values set for rows are just placeholders until I can figure this out. I would also like to have a reset button at the top that allows the user to automatically uncheck every box. This code is what I have so far. Any help would be greatly appreciated.
#!/usr/bin/python3
from tkinter import *
window = Tk()
#window name and header
window.title("Custom Random SSBU")
lbl = Label(window, text="Select the fighters you would like to include:")
lbl.grid(column=1, row=0)
f = [] #check boxes
ft = open("Fighters.txt").readlines() #list of all the character names
fv=[0]*78 #list for tracking what boxes are checked
ff=[] #list to place final character strings
def reset():
for i in fv:
fv[i]=0
rst = Button(window, text="Reset", command=reset)
rst.grid(column=0, row=3)
for y in range (0,77):
f[y] = Checkbutton(window, text = ft[y], variable = fv[y])
f[y].grid(column=0, row=4+y)
def done():
for j in fv:
if fv[j] == 1:
ff.append(fv[j])
result = random.choice(ff)
r=Label(window, text=result)
d = Button(window, text="Done", command=done)
d.grid(column=0, row = 80)
window.mainloop()
Unfortunately I'm afraid you are going to have to create variables for each checkbox.
tkinter has special purpose Variable Classes for holding different types of values, and if you specify an instance of one as the variable= option when you create widgets like Checkbutton, it will automatically set or reset its value whenever the user changes it, so all your program has to do is check its current value by calling its get() method.
Here's an example of the modifications to your code needed to create them in a loop (and use them in the done() callback function):
import random
from tkinter import *
window = Tk()
#window name and header
window.title("Custom Random SSBU")
lbl = Label(window, text="Select the fighters you would like to include:")
lbl.grid(column=1, row=0)
with open("Fighters.txt") as fighters:
ft = fighters.read().splitlines() # List of all the character names.
fv = [BooleanVar(value=False) for _ in ft] # List to track which boxes are checked.
ff = [] # List to place final character strings.
def reset():
for var in fv:
var.set(False)
rst = Button(window, text="Reset", command=reset)
rst.grid(column=0, row=3)
for i, (name, var) in enumerate(zip(ft, fv)):
chk_btn = Checkbutton(window, text=name, variable=var)
chk_btn.grid(column=0, row=i+4, sticky=W)
def done():
global ff
ff = [name for name, var in zip(ft, fv) if var.get()] # List of checked names.
# Randomly select one of them.
choice.configure(text=random.choice(ff) if ff else "None")
d = Button(window, text="Done", command=done)
d.grid(column=0, row=len(ft)+4)
choice = Label(window, text="None")
choice.grid(column=1, row=3)
window.mainloop()
I wasn't sure where you wanted the Label containing the result to go, so I just put it to the right of the Reset button.
variable = fv[y]
This looks up the value of fv[y] - i.e, the integer 0 - at the time the Checkbutton is created, and uses that for the variable argument.
You need to use an instance of one of the value-tracking classes provided by TKinter, instead. In this case we want BooleanVar since we are tracking a boolean state. We can still create these in a list ahead of time:
text = open("Fighters.txt").readlines()
# Let's not hard-code the number of lines - we'll find it out automatically,
# and just make one for each line.
trackers = [BooleanVar() for line in text]
# And we'll iterate over those pair-wise to make the buttons:
buttons = [
Checkbutton(window, text = line, variable = tracker)
for line, tracker in zip(text, trackers)
]
(but we can not do, for example trackers = [BooleanVar()] * len(text), because that gives us the same tracker 78 times, and thus every checkbox will share that tracker; we need to track each separately.)
When you click the checkbox, TKinter will automatically update the internal state of the corresponding BooleanVar(), which we can check using its .get() method. Also, when we set up our options for random.choice, we want to choose the corresponding text for the button, not the tracker. We can do this with the zip trick again.
So we want something more like:
result_label = Label(window) # create it ahead of time
def done():
result_label.text = random.choice(
label
for label, tracker in zip(text, trackers)
if tracker.get()
)
This question already has answers here:
tkinter creating buttons in for loop passing command arguments
(3 answers)
Closed 6 months ago.
I am trying to program a minesweeper game on python using tkinter. I started off by creating a grid of buttons using a two dimensional list, and the generation of the button and everything works. The only issue I have is that I don't know how to determine which button in my grid is clicked. My goal is to be able to click on a button and through that I know the coordinates of that in my grid [row][col].
This is the code I have so far.
from tkinter import *
from functools import partial
from itertools import product
# Here, we are creating our class, Window, and inheriting from the Frame
# class. Frame is a class from the tkinter module. (see Lib/tkinter/__init__)
class Window(Frame):
# Define settings upon initialization. Here you can specify
def __init__(self, master=None):
# parameters that you want to send through the Frame class.
Frame.__init__(self, master)
#reference to the master widget, which is the tk window
self.master = master
#with that, we want to then run init_window, which doesn't yet exist
numRows = int(input("# of Rows: "))
numCols = int(input("# of Cols: "))
self.init_window(numRows, numCols)
#Creation of init_window
def init_window(self, rowNum, colNum):
# print(x, y)
# changing the title of our master widget
self.master.title("GUI")
# allowing the widget to take the full space of the root window
self.pack(fill=BOTH, expand=1)
# creating a button instance
#quitButton = Button(self, text="Exit",command=self.client_exit)
# placing the button on my window
#quitButton.place(x=0, y=0)
but = []
for row in range(0, rowNum):
curRow = []
for col in range(0, colNum):
curRow.append(Button(self, bg="gray", width=2,height=1, command=lambda: self.open_button(row, col)))
curRow[col].grid(row=row,column=col)
but.append(curRow)
#but[1][1].config(state="disabled")
#but[1][1]["text"] = "3"
#but[1][1]["bg"] = "white"
def open_button(self, r, c):
print(r, " : ", c)
# root window created. Here, that would be the only window, but
# you can later have windows within windows.
root = Tk()
root.geometry("600x600")
#creation of an instance
app = Window(root)
#mainloop
root.mainloop()
Whenever I click on the grid, it gives me the very last button...
For example, a 9x9 grid always gives me "9 : 9" whenever I click any button.
Solutions welcomed! I want an easy way to get the coordinates without changing too much of the code (if possible).
Thanks!
The row and col variables are assigned each value in the ranges. At the end of the loop that generates the buttons, the values for those variables are left at the last values in the ranges, e.g. "9 : 9".
Try replacing the line
curRow.append(Button(self, bg="gray", width=2,height=1, command=lambda: self.open_button(row, col)))
with
curRow.append(Button(self, bg="gray", width=2,height=1, command=lambda rw=row, cl=col: self.open_button(rw, cl)))
This assigns the values of row and col at the time the button is created to the variables rw and cl, which remain the same for each button as the for-loop iterates.
See this link:
Tkinter assign button command in loop with lambda
Let's say I have a Tkinter app with 2 rows displaying 2 widgets:
from tkinter import *
from tkinter.ttk import *
root = Tk()
Label(root, text="Some Data").grid(row=0)
Label(root, text="Some Data").grid(row=1)
root.mainloop()
Now this will display two widgets on row0 and row1.
Now if I want to insert another (one or more) widget between these two rows at a later stage (say as a response to a button click event), what would be the best way to do that.
Current output:
Some Data
Some Data
Expected output:
Some Data
<<New data>>
Some Data
<<New Data>> will be inserted at a later stage as a response to a button click.
<<New Data>> may be one or more rows.
I do have a simple solution for you.
If you are expecting to insert a widget later and you know you will be then you can simply place your 2nd label on grid row 2 and then place your new widget on grid row 1 later. If you need to have more than one row you could place your 2nd label even further down the line.
from tkinter import *
from tkinter.ttk import *
root = Tk()
def add_new_data():
Label(root, text="<<New Data>>").grid(row=1)
Label(root, text="Some Data").grid(row=0)
Label(root, text="Some Data").grid(row=2)
Button(root, text="Add New Data", command=add_new_data).grid(row=3)
root.mainloop()
Results:
The reason this works is because Tkinter's geometry manager will collapse rows and columns to nothing if there is nothing in them so you can use this behavior to your advantage when working with something like this.
Now if you wanted something that could work with any number of label then we can use a list to help us accomplish that.
My next example with be written in class and will show the use of a list to do what we want.
We can store widgets in a list and because we can do this we are also able to decide where in that list to put stuff and use the lists index to our advantage when setting up the grid.
First thing is to create our Some Data labels and append them to a list. The next is the add the button to that list at the end of the list. This button we will used to call a class method that will insert() a new label into our list.
Next that same method will forget the grid for all widgets inside of our label frame and then it will perform a for loop over the list and re add all the old widgets and the new one in the correct order.
Take a look at the below example.
import tkinter as tk
class App(tk.Frame):
def __init__(self, master, *args, **kwargs):
tk.Frame.__init__(self, master, *args, **kwargs)
self.master = master
self.label_frame = tk.Frame(self.master)
self.label_frame.grid()
self.label_list = []
for i in range(2):
self.label_list.append(tk.Label(self.label_frame, text="Some Data"))
self.label_list[i].grid(row=i)
self.label_list.append(tk.Button(self.label_frame, text="Add new data", command=self.add_new_data))
self.label_list[2].grid(row=2)
def add_new_data(self):
self.label_list.insert(1, tk.Label(self.label_frame, text="<<New Data>>"))
for widget in self.label_frame.children.values():
widget.grid_forget()
for ndex, i in enumerate(self.label_list):
i.grid(row=ndex)
if __name__ == "__main__":
root = tk.Tk()
my_app = App(root)
root.mainloop()
Results:
We can add as many new labels as we like.
So i want to build an assistant off sorts which will do auto backs ups etc and instead of using .place i would like a proper grid to place widgets.
I cannot find a good example of the grid manager.
self.parent = tk.Frame(window, bg = BLACK)
username_label = ttk.Label(self.parent, text = "Username")
password_label = ttk.Label(self.parent, text = "Password")
self.parent.grid(column = 0, row = 0)
username_label.grid(column = 1, row = 1)
password_label.grid(column = 2, row = 2)
self.parent.grid_columnconfigure(0, weight = 1)
I want...
Button
Button
Label Entry Button
Label Entry Button
Button
I don't understand how i can position them like this as i want a blank space above the labels. so far grid has only let me place things next to each other.
Honestly, any websites or code examples would be greatly appreciated
So, if you want blank space above the label, you can either set pady as an argument to the grid method or simply put them in the corresponding row. Consider the following example:
import tkinter as tk
root=tk.Tk()
for i in range(6):
tk.Button(root,text='Button %d'%i).grid(row=i,column=1)
tk.Label(root,text='Label 0').grid(row=2,column=0,pady=20)
tk.Label(root,text='Label 1').grid(row=3,column=0)
root.mainloop()
Notice the effect of the pady argument. Also, if you only want a blank line above the Label, you can try to put a blank Label in the row above. E.g.:
import tkinter as tk
root=tk.Tk()
for i in range(6):
tk.Button(root,text='Button %d'%i).grid(row=i,column=1)
tk.Label(root,text='Label 0').grid(row=2,column=0,pady=20)
tk.Label(root,text='Label 1').grid(row=3,column=0)
tk.Label(root,text='').grid(row=6)
tk.Label(root,text='This is a Label with a blank row above').grid(row=7,columnspan=2)
root.mainloop()
You can refer to effbot for more information, which is the blog of tkinter's developer.