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.
Related
i am trying to get my listbox to move to the bottom of the gui but no matter how high of a row value i give it it wont budge. you can see my listbox in the Creat_Gui method in my code. im not sure why this is happpening it cant be the button because the button is in row 1 so im not sure whats causing this.
i tried using sticky='s' that didnt work i tried changing the rows multiple times didnt work. i tried using the root.rowconfigure(100,weight=1) this worked kind of but messes with thte grid which is annoying
import tkinter as tk
class Manager:
def __init__(self):
self.root=tk.Tk()
self.root.title('password_manager')
self.root.geometry('500x600')
self.create_GUI()
self.storage = {}
self.root.mainloop()
def create(self):
pass
def open_page(self):
print('openpage')
def add_new_button(self):
pass
def add_new(self):
self.app_title=tk.Label(text='test')
self.app_title.grid(row=2,column=50)
self.application_name=tk.Entry(self.root,width=20,font=('arial',18))
self.username=tk.Entry(self.root,width=20,font=('arial',18))
self.password=tk.Entry(self.root,width=20,font=('arial',18))
self.application_name.grid(row=2, column=1)
self.username.grid(row=3, column=2)
self.password.grid(row=4, column=3)
self.app_name_label = tk.Label(self.root, text='Application Name:')
self.username_label = tk.Label(self.root, text='Username:')
self.password_label = tk.Label(self.root, text='Password:')
self.app_name_label.grid(row=2, column=0)
self.username_label.grid(row=3, column=1)
self.password_label.grid(row=4, column=2)
self.password.bind('<Return>',self.hide)
def hide(self,thing):
#store user info to pass onto dictionary and hide textboxes
username=self.username.get()
password=self.password.get()
app_name=self.application_name.get()
self.application_name.grid_forget()
self.username.grid_forget()
self.password.grid_forget()
self.add_to_memory(username,password,app_name)
def add_to_memory(self,username,password,app_name):
#store username password and application name in dictionary
if app_name in self.storage.keys():
return
else:
self.storage[app_name]=(username,password)
print(self.storage)
def create_GUI(self):
#create initial interface
#self.root.columnconfigure(100, weight=1)
#self.root.rowconfigure(100, weight=1)
self.listbox=tk.Listbox(self.root,width=100)
self.listbox.grid(row=200,column=0)
self.button=tk.Button(self.root,text='add new',font=('arial',18),command=self.add_new)
self.button.grid(row=1,column=0)
Manager()
If you want to use grid, and you want a widget to be at the bottom of it's parent, you need to designate some row above that widget to take all of the extra space, forcing the widget to the bottom. Here's one example:
def create_GUI(self):
self.listbox=tk.Listbox(self.root,width=100)
self.expando_frame = tk.Frame(self.root)
self.button=tk.Button(self.root,text='add new',font=('arial',18),command=self.add_new)
self.root.grid_rowconfigure(2, weight=1)
self.button.grid(row=1,column=0)
self.expando_frame.grid(row=2, sticky="nsew")
self.listbox.grid(row=3,column=0)
With that, the listbox will be at the bottom, with extra space above it. If you want to add more widgets, you can add them to self.expando_frame rather than self.root and they will appear above the listbox.
Using frames in this way is a valuable technique. You could, for example, use three frames in root: one for the top row of buttons, one for the listbox on the bottom, and one for everything in the middle. You could then use pack on these frames and save a line of code (by virtue of not having to configure the rows). You can then use grid for widgets inside the middle frame.
I am aware that you cannot use different types of geometry managers within the same Tkinter window, such as .grid() and .pack(). I have a window that has been laid out using .grid() and I am now trying to add a status bar that would be snapped to the bottom of the window. The only method I have found online for this is to use .pack(side = BOTTOM), which will not work since the rest of the window uses .grid().
Is there a way that I can select the bottom of the window to place widgets from when using .grid()?
from tkinter import *
from tkinter.ttk import *
import tkinter as tk
class sample(Frame):
def __init__(self,master=None):
Frame.__init__(self, master)
self.status = StringVar()
self.status.set("Initializing")
statusbar = Label(root,textvariable = self.status,relief = SUNKEN, anchor = W)
statusbar.pack(side = BOTTOM, fill = X)
self.parent1 = Frame()
self.parent1.pack(side = TOP)
self.createwidgets()
def createwidgets(self):
Label(self.parent1,text = "Grid 1,1").grid(row = 1, column = 1)
Label(self.parent1,text = "Grid 1,2").grid(row = 1, column = 2)
Label(self.parent1,text = "Grid 2,1").grid(row = 2, column = 1)
Label(self.parent1,text = "Grid 2,2").grid(row = 2, column = 2)
if __name__ == '__main__':
root = Tk()
app = sample(master=root)
app.mainloop()
So using labels since I was kinda lazy to do other stuff, you can do frames to ensure that each section of your window can be packed/grid as required. Frames will be a useful tool for you to use when trying to arrange your widgets. Note that using a class can make things a little easier when deciding your parents. So imagine each frame is a parent and their children can be packed as required. So I would recommend drawing out your desired GUI and see how you will arrange them. Also if you want to add another frame within a frame simply do:
self.level2 = Frame(self.parent1)
You can check out additional settings in the docs
http://effbot.org/tkinterbook/frame.htm
PS: I am using a class hence the self, if you don't want to use classes then its okay to just change it to be without a class. Classes make it nicer to read though
Just give it a row argument that is larger than any other row. Then, give a weight to at least one of the rows before it.
Even better is to use frames to organize your code. Pack the scrollbar on the bottom and a frame above it. Then, use grid for everything inside the frame.
Example:
# layout of the root window
main = tk.Frame(root)
statusbar = tk.Label(root, text="this is the statusbar", anchor="w")
statusbar.pack(side="bottom", fill="x")
main.pack(side="top", fill="both", expand=True)
# layout of the main window
for row in range(1, 10):
label = tk.Label(main, text=f"R{row}")
label.grid(row=row, sticky="nsew")
main.grid_rowconfigure(row, weight=1)
...
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
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
I'm trying to clear a tkinter window completely. However, I need a way to clear every widget on the window all at once without using pack.forget().
You could use a simple recursive loop to list all the children wigets of your main window :
def all_children (window) :
_list = window.winfo_children()
for item in _list :
if item.winfo_children() :
_list.extend(item.winfo_children())
return _list
Then just use this list :
widget_list = all_children(window)
for item in widget_list:
item.pack_forget()
What you need to do is set a frame to your main window and place everything that is to be cleared out at some point inside that frame. Then you simply do frame_name.destroy()
The following example has a button that creates a frame with several label widgets and a button.
The button calls a method that will destroy the frame and everything in it.
Then you can create the frame again with the first button.
Let me know if you have any question:
import tkinter as tk
class ExampleApp(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.master = master
self.some_frame = None
tk.Button(self.master, text="Create new frame with widgets!", command = self.create_stuff).pack()
def create_stuff(self):
if self.some_frame == None:
self.some_frame = tk.Frame(self.master)
self.some_frame.pack()
for i in range(5):
tk.Label(self.some_frame, text = "This is label {}!".format(i+1)).pack()
tk.Button(self.some_frame, text="Destroy all widgets in this frame!",
command= self.destroy_some_frame).pack()
def destroy_some_frame(self):
self.some_frame.destroy()
self.some_frame = None
root = tk.Tk()
my_example = ExampleApp(root)
root.mainloop()
You can use destroy method for each widget for example if it's a button you write btn1.destroy() and do it for all widgets. The forget method isn't recommended for it only remove the widgets from appearance.