TKinter Geometry Manager displaying multiple rows of widgets - python

I wish to display a list of sentences with missing words. The basic, one-line, idea is the following:
The construction of the above is a "label + entry + label + spacing + label". In order to make sure that the widgets were aligned left, I used the following code:
phraseLabel1 = tk.Label(questionFrame)
phraseLabel1.pack(side=tk.LEFT)
keyWordEntry = tk.Entry(questionFrame)
keyWordEntry.pack(side=tk.LEFT)
phraseLabel2 = tk.Label(questionFrame)
phraseLabel2.pack(side=tk.LEFT)
keyWordLabel = tk.Label(questionFrame)
keyWordLabel.pack(side=tk.LEFT,padx=30)
My objective is to present the users with an input screen for multiple sentence. As for example given in the following drawing:
Whilst I manage to create the labels via the underneath (experimental) code, I lack understanding to manage the geometry.
root = tk.Tk()
root.title("myTest")
root.geometry("700x700")
questionFrame = tk.Frame(root)
resultFrame = tk.Frame(root)
for frame in (questionFrame, resultFrame):
frame.grid(row=0, column=0, sticky='news')
#DB Query returning a set of phrases and Keywords
(zinPhrase1, zinPhrase2, keyWordFR, keyWordNL)=getPhrase()
#Init
lab1 = []
keyWordEntry = []
lab2 = []
keyWord = []
for i in range(4): #4 is entered as a dummy value
lab1.append(tk.Label(questionFrame))
lab1[i].pack()
keyWordEntry.append(tk.Entry(questionFrame))
keyWordEntry[i].pack()
lab2.append(tk.Label(questionFrame))
lab2[i].pack()
keyWord.append(tk.Label(questionFrame))
keyWord[i].pack()
lab1[i].config(text=zinPhrase1[i])
keyWordEntry[i].config(width=8)
lab2[i].config(text=zinPhrase2[i])
keyWord[i].config(text=keyWordNL[i],fg="red")
questionFrame.tkraise()
root.mainloop()
How can I manage the placement of the widgets line by line, as shown in the drawing above? Any help would gratefully appreciated.

Since you don't seem to want to organize your widgets in a grid, the most common solution to this problem is to create a frame for each row. The frames stack top-to-bottom, and the widgets inside the frame stack left-to-right.
In my experience, GUI code is much easier to visualize when you separate widget creation from widget layout, so I've done that in the following example to hopefully make it easier to comprehend.
for i in range(4): #4 is entered as a dummy value
rowFrame = tk.Frame(questionFrame)
rowFrame.pack(side="top", fill="x")
lab1.append(tk.Label(rowFrame))
keyWordEntry.append(tk.Entry(rowFrame))
lab2.append(tk.Label(rowFrame))
keyWord.append(tk.Label(rowFrame))
lab1[i].pack(side="left")
keyWordEntry[i].pack(side="left")
lab2[i].pack(side="left", padx=(0, 40))
keyWord[i].pack(side="left")
lab1[i].config(text=zinPhrase1[i])
keyWordEntry[i].config(width=8)
lab2[i].config(text=zinPhrase2[i])
keyWord[i].config(text=keyWordNL[i],fg="red")
The above code results in something like this:

Would something like the following suffice?
for i in range(4): #4 is entered as a dummy value
frames.append(tk.Frame(questionFrame))
frames[i].grid(row=i, column=0, stick='W')
for i in range(4):
lab1.append(tk.Label(frames[i]))
lab1[i].grid(row=0, column=0)
keyWordEntry.append(tk.Entry(frames[i]))
keyWordEntry[i].grid(row=0, column=1)
lab2.append(tk.Label(frames[i]))
lab2[i].grid(row=0, column=2)
keyWord.append(tk.Label(frames[i]))
keyWord[i].grid(row=0, column=3)
lab1[i].config(text=zinPhrase1[i])
keyWordEntry[i].config(width=8)
lab2[i].config(text=zinPhrase2[i])
keyWord[i].config(text=keyWordNL[i],fg="red")

Related

python Tkinter grid method not working as should be for some reason

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.

PhotoImage won't Display in tkinter treeview

I am trying to display an image in tkinter treeview. I have been reading possible solutions but none seems to work, I keep getting a blank tree only with the column heading. For what I have been reading, I have to keep a reference to the PhotoImage, but regardless of how much I try it just won't happen. A simple code example can be found below:
import tkinter as tk
from tkinter import ttk
from PIL import ImageTk as itk
import PIL.Image
import io
s= tk.Tk()
s.title('No *£/**##* image showing')
s.geometry('400x400')
s.rowconfigure(1, weight = 1)
s.columnconfigure(1,weight=1)
headings=['Image']
p = '032f8072.gif'
img1 = PIL.Image.open('032f8072.gif')
#img1 = img1.resize((10,10))
img = itk.PhotoImage(img1)
tree = ttk.Treeview(s)
tree.grid(column=1,row=1,sticky='NSEW')
tree['columns']=headings
tree['show']='headings'
for i in headings:
tree.heading(i,text=i)
tree.column(0, width=125,stretch=True)
#tree.column(1, width=125,stretch=True)
tree.insert('','end','0', open =True, image= img)
tree.image = img
s.mainloop()
I have tried with .gif and .png, i have tried with both PIL.Image and Imagetk.PhotoImage togeter and individually. I have also tried keeping img inside a list to be called from the list to avoid a missing reference.
I really need to get this small piece of code right and I am really frustrated with this little piece holding me back. I would really appreciate if someone could help me with this.
kind regards
This took awhile to figure out! I eventually found this answer to a similar question from 10 months ago (not officially an answer but look in the comments under the question):
Treeview Image not displaying
The user said the comment solved their problem, so I tried applying it to your example. What the commenter means is that the line tree['show'] = 'headings' forces the Treeview to only display the headings, and not the main body of the tree. To fix this, replace that line with the following:
tree['show'] = ('headings', 'tree')
to show all of the tree, and the image should start showing up.
You can create the columns using tree['columns'], but as ne0n p1atypus say, you can't display them using tree['show']. You can still enter the name for the columns manually with explorer_tree.heading("#0",text="Image",anchor= 'center') note that '#0' refers to the column in which the image will be displayed. I didn't try entering the column name using for loop.
Also, when trying to construct a treeview to display images in different rows using a for loop, this algorithm has to be within the function that defines the treeview and all the PhotoImage objects must be append to a list to keep its reference, otherwise they'll be collected as garbage. I'll leave an example below.
global eow
global dffile
global temp_result
global explorer_tree
#Define window and treeview
explorer_headings = ["Name",'Code', 'Supplier Code']
temp_list=[]
eow= Toplevel(acm)
eow.title('Stock Explorer')
eow.geometry('400x650')
eow.geometry("+0+0")
eow.minsize(400,650)
eow.state('zoomed')
eow.grab_set()
#style
style2 = ttk.Style()
style2.theme_use("awdark")
style2.configure("2style.Treeview.Heading",font=('Calibri', 18,'bold')) # Modify the font of the headings
style2.configure("2style.Treeview", font=('Calibri', 20),rowheight=100)
eow['bg']='black'
#Columns and rows
eow.columnconfigure(0, weight=1)
eow.columnconfigure(1, weight =2)
eow.columnconfigure(2, weight =2)
eow.columnconfigure(3, weight =1)
eow.rowconfigure(1, weight=4)
eow.rowconfigure(2, weight=2)
eow.rowconfigure(3, weight=2)
eow.rowconfigure(4, weight=2)
eow.rowconfigure(5, weight=1)
#Treeview to display data
explorer_tree = ttk.Treeview(eow, style='2style.Treeview',height=3)
explorer_tree.grid(column=1,row=2,sticky='NSEW', columnspan=2, rowspan=2)
#treeview scrollbars
xscroll_file_data= tk.Scrollbar(eow, orient='horizontal', command= explorer_tree.xview)
yscroll_file_data= tk.Scrollbar(eow, orient='vertical', command= explorer_tree.yview)
xscroll_file_data.grid(column=1,row=5,sticky='NEW',columnspan=2)
yscroll_file_data.grid(column=3,row=2,sticky='WNS',rowspan= 3)
explorer_tree.configure(yscrollcommand=yscroll_file_data.set)
add_part_button = Button(eow, text='Add Part',font=('Calibri', 14,'bold'),
activebackground='white', activeforeground='black',relief='raised', borderwidth=5
, command = add_part_from_pic_list)
add_part_button.grid(column=1, row=5, sticky='NSEW', padx=100, pady=40)
notfound_button = Button(eow, text='Part Not In List',font=('Calibri', 14,'bold'),
activebackground='white', activeforeground='black',relief='raised', borderwidth=5)
notfound_button.grid(column=2, row=5, sticky='NSEW', padx=100, pady=40)
code_label = Label(eow, text='Code: '+str(code)+' Name: '+online_name,background='black',
foreground='white',font=('Calibri', 18))
code_label.grid(column=1,row=1, sticky = 'NSEW',pady=10, padx=20)
#Name the headings
explorer_tree['columns']=explorer_headings
explorer_tree.heading("#0",text="Image",anchor= 'center')
explorer_tree.heading(0,text="Name",anchor= 'center')
explorer_tree.heading(1,text="Code",anchor= 'center')
explorer_tree.heading(2,text="Supplier Code",anchor= 'center')
#Format the columns
explorer_tree.column('#0', width=130,stretch=False)
explorer_tree.column(0,width=200, anchor='center',stretch=True)
explorer_tree.column(1,width= 200, anchor='center', stretch=False)
explorer_tree.column(2, anchor='center',stretch=False)
explorer_tree.tag_configure('even',foreground='black',background='white')
children = explorer_tree.get_children()
eow.protocol("WM_DELETE_WINDOW",Instant_exit)
explorer_tree.bind("<Double-1>", add_part_from_pic_list)
#construct tree
temp_list=[]
for i in range(len(temp_result)):
for j in range(len(dffile['Name'])):
if temp_result[i] == dffile['Name'][j]:
children1 = explorer_tree.get_children()
temp_row = [dffile['Name'][j],dffile['Code'][j],dffile['Supplier Code'][j]]
p = temp_row[1]+".png"
pp = "images/"+temp_row[1]+".png"
np = 'images/noimage.png'
try:#Append the PhotoImage object to a list rather than to a variable This will avoid the image being collected as garbage
temp_list.append(ImageTk.PhotoImage(Image.open(pp).resize((100,100),Image.ANTIALIAS)))
except FileNotFoundError:
temp_list.append(ImageTk.PhotoImage(Image.open(np).resize((100,100),Image.ANTIALIAS)))
continue
if len(children1)%2 == 0: #When calling the image for tree.insert, call the image from the list i.e. temp_list[i]
explorer_tree.insert('','end',iid=(len(children1)), image=temp_list[i], values=(temp_row[0],temp_row[1],
temp_row[2]),tags=('even'))
else:
explorer_tree.insert('','end',iid=(len(children1)), image=temp_list[i], values=(temp_row[0], temp_row[1],
temp_row[2]),tags=('odd'))
eow.mainloop() ```
try this, for me worked.
tree = ttk.Treeview(master=canvas, columns=car_header, show="tree headings", height=11)
img = tk.PhotoImage(file=r"frames/imgs/image.gif")
tree.insert(parent="",index="end", image=img, text='Information', values=collection_tree[i], tags=('oddrow'))
tk.Label.image = img

Inserting new rows in Tkinter grid

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.

tkinter grid manager behaviour

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.

Tkinter grid of frames gets stacked vertically instead of hotizontal

I'm surely misunderstanding something with pack and grid...
I'm making the GUI for a python application that get debug messages in multiple terminal-like consoles. Making a Tkinter GUI is new to
First of all why do I need to pack buttonframe to have it shown? Shouldn't grid be equivalent to pack?
The GUI is made of a series of buttons on top in the buttonframe and a series of vertical consoles below showing up in columns. Instead what I get is all the consoles stacked vertically.
The consoles are frames with text inside which is written by other threads in the application.
If I don't grid the consoles[idx] it doesn't change (so I assume it doesn't work). If I don't pack the consoles[idx] the GUI starts flickering.
Please note that if I instead call create_Hterms, which instead stacks vertically a bunch of horizontal consoles.
How do I correctly put my consoles as vertical columns?
import Tkinter as tikei
NCONSOLES = 4
HORZ_CONSOLE_W = 10
HORZ_CONSOLE_H = 12
# FakeConsole: one of the consoles that are spawned by MainWindow.
class FakeConsole(tikei.Frame): # from Kevin in http://stackoverflow.com/questions/19479504/how-can-i-open-two-consoles-from-a-single-script
def __init__(self, root, cust_width, cust_height, *args, **kargs):
tikei.Frame.__init__(self, root, *args, **kargs)
self.grid(row=0)
#white text on black background
self.text = tikei.Text(root, bg="black", fg="white", width=cust_width, height=cust_height)
self.text.grid(row=0)
self.text.insert(tikei.END, 'ASD')
self.text.see(tikei.END)
def clearConsole(self):
self.text.delete(1.0,tikei.END)
class MainWindow(tikei.Frame):
counter = 0
consoles = []
consoleFormat = ''
e_Vvar = ''
e_Hvar = ''
var1 = ''
ctrlCmdRecipient = ''
def __init__(self, *args, **kwargs):
tikei.Frame.__init__(self, *args, **kwargs)
self.consoleFormat = 'H'
self.cF = tikei.StringVar()
self.grid(row=0)
self.buttonframe = tikei.Frame(self)
self.buttonframe.grid(row=0)
tikei.Button(self.buttonframe, text = "Clear Consoles", command=self.clearConsoles).grid(row=0, column=0)
tikei.Button(self.buttonframe, text = "start").grid(row=0, column=1)
tikei.Button(self.buttonframe, text = "stop").grid(row=0, column=2)
self.consoleFrame = tikei.Frame(self)
self.create_Hterms()
self.consoleFrame.grid(row=1)
# create a status bar
self.statusTxt = tikei.StringVar()
self.statusTxt.set('-')
self.statusLab = tikei.Label(self.buttonframe, text="Status: ", anchor=tikei.W, height=1).grid(row=1, column=0, sticky=tikei.W)
self.statusBar = tikei.Label(self.buttonframe, textvariable=self.statusTxt, anchor=tikei.W, height=1).grid(row=1, column=1, columnspan=4, sticky=tikei.W)
def clearConsoles(self):
for i in range(NCONSOLES):
self.consoles[i].clearConsole()
def create_Hterms(self):
ctr = NCONSOLES
idx = 0
cons_w = HORZ_CONSOLE_W
cons_h = HORZ_CONSOLE_H
while ctr > 0:
self.consoles.append(FakeConsole(self.consoleFrame, cons_w, cons_h)) #
self.consoles[idx].grid(row=idx+1, column=0)
ctr -= 1
idx += 1
return idx+1
root = tikei.Tk()
mainTk = MainWindow(root)
root.wm_title("TOOLS")
mainTk.grid(row=0)
root.mainloop()
First of all why do I need to pack buttonframe to have it shown?
Shouldn't grid be equivalent to pack?
No, generally speaking you do not need to pack buttonframe to have it shown. Use pack or grid, but not both on the same widget. In this specific case, however, the use of pack masks bugs deeper in your code, and makes it seem like calling pack is required.
The problem is that you use pack for self.consoleFrame, and both self.consoleFrame and self.buttonFrame have the same parent. Because they share the same parent you must use either pack or grid for both.
The GUI is made of a series of buttons on top in the buttonframe and a
series of vertical consoles below showing up in columns. Instead what
I get is all the consoles stacked vertically.
This is because you use grid, and then you use pack. You can only use one or the other, and the last one you use "wins". Thus, you're using pack, and you're not giving it any options, so it's defaulting to packing things top to bottom.
While it's perfectly safe to use both grid and pack in the same application, it's pointless to use both on the same widget, and it's an error to use different ones for widgets that share the same parent.
The solution to the problem is to be consistent with your use of grid and pack. If you intend to use grid, make sure you use it consistently for all widgets that share the same parent.
The same holds true for pack -- use it consistently. Also, I recommend to always explicitly add the side, fill and expand arguments to pack so that it is clear what your intention is.

Categories

Resources