I have a code that I'm trying to handle the geometry of a button in a frame and entry in another frame. But it doesn't seem to work independently of the main window they're both children of.
try: # In order to be able to import tkinter for
import tkinter as tk # either in python 2 or in python 3
except ImportError:
import Tkinter as tk
class NumPad(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.button = tk.Button(text=0)
self.button.grid(row=1, column=0, sticky='nsew')
class CalcFrame(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.entry = tk.Entry(self)
self.entry.pack()
if __name__ == '__main__':
root = tk.Tk()
frame1 = CalcFrame(master=root)
frame2 = NumPad(master=root)
frame1.grid(row=0, column=0)
frame2.grid(row=1, column=0, sticky='nsew')
root.mainloop()
In the above code if I replace:
self.button.grid(row=0, column=0, sticky='nsew')
with:
self.button.grid(row=1, column=0, sticky='nsew')
the widget in frame2 overlaps the widget on frame1. How can I have an inner grid per widget basis? Right now it seems like there's only one top-level grid.
As Bryan Oakley pointed out in the comments above, when you declare the Button widget on this line...
self.button = tk.Button(text=0)
You aren't assigning it a parent meaning that it just dumps itself into the Tk() window by default.
On a side note, you have variables which by their name suggest that they are Frame widgets (namely frame1 and frame2) but actually appear to be references to classes which don't ever use Frame widgets.
Frame widgets are very powerful and can be used to easily separate sets of widgets in the same window. An example of using Frames can be found below:
from tkinter import *
root = Tk()
frame1 = Frame(root, borderwidth=1, relief="solid")
frame2 = Frame(root, borderwidth=1, relief="solid")
frame1.pack(side="left", fill="both", expand=True, padx=10, pady=10)
frame2.pack(side="right", fill="both", expand=True, padx=10, pady=10)
label1 = Label(frame1, text="I'm inside a frame")
label2 = Label(frame2, text="I'm inside a different frame")
label1.pack()
label2.pack()
root.mainloop()
This shows that you can have widgets using a different geometry manager to their parents:
from tkinter import *
root = Tk()
frame1 = Frame(root)
frame2 = Frame(root)
frame1.pack(side="left")
frame2.pack(side="right")
label1 = Label(frame1, text="I'm grid")
label2 = Label(frame1, text="I'm grid")
label3 = Label(frame2, text="I'm pack")
label4 = Label(frame2, text="I'm pack")
label1.grid(row=0, column=0)
label2.grid(row=0, column=1)
label3.pack()
label4.pack()
root.mainloop()
Related
import tkinter as tk
import tkinter.ttk as ttk
root = tk.Tk()
left_frame_1 = tk.Frame(root, background="#ff0000")
left_frame_1.grid(row=0, column=0)
left_frame_2 = tk.Frame(left_frame_1)
left_frame_2.grid(row=0, column=0)
left_label_1 = tk.Label(left_frame_2, text="HELLO")
left_label_2 = tk.Label(left_frame_2, text="WORLD")
left_label_3 = tk.Label(left_frame_2, text="=D")
left_label_1.grid(row=0, column=0)
left_label_2.grid(row=1, column=0)
left_label_3.grid(row=2, column=0)
right_frame1 = tk.Frame(root, background="#00ff00")
right_frame1.grid(row=0, column=1, sticky="nsew")
right_frame_2 = tk.Frame(right_frame1, background="#0000ff")
right_frame_2.grid(row=0, column=0)
right_label_1 = tk.Label(right_frame_2, text="CENTER ME!")
right_label_1.grid(row=0, column=0)
root.mainloop()
When my parent frame expands to all its free space, the child frame doesn't, instead it just stays on top.
I've been testing if .grid() has something to do with it, but haven't found anything.
Even if I add sticky="nsew" to both the frame and the label, there is still no change.
right_frame1 = tk.Frame(root, background="#00ff00")
right_frame1.grid(row=0, column=1, sticky="nsew")
right_frame_2 = tk.Frame(right_frame1, background="#0000ff")
right_frame_2.grid(row=0, column=0, sticky="nsew")
right_label_1 = tk.Label(right_frame_2, text="CENTER ME!")
right_label_1.grid(row=0, column=0, sticky="nsew")
My goal is for the parent frame (the one with the green color) to expand to all available space (which I've achieved), and for the child frame containing the label to expand.
right_frame_2 looks because it expands.
right_frame_1 is not visible because it is completely covered by right_frame_2.
I hope your help, thank you.
To get the result of the last image in the question, you need to:
change sticky options of .grid() for right_frame_2 and right_label_1
set weight options of .rowconfigure() and .columnconfigure() on root, right_frame1 and right_frame_2
import tkinter as tk
import tkinter.ttk as ttk
root = tk.Tk()
left_frame_1 = tk.Frame(root, background="#ff0000")
left_frame_1.grid(row=0, column=0)
left_frame_2 = tk.Frame(left_frame_1)
left_frame_2.grid(row=0, column=0)
left_label_1 = tk.Label(left_frame_2, text="HELLO")
left_label_2 = tk.Label(left_frame_2, text="WORLD")
left_label_3 = tk.Label(left_frame_2, text="=D")
left_label_1.grid(row=0, column=0)
left_label_2.grid(row=1, column=0)
left_label_3.grid(row=2, column=0)
right_frame1 = tk.Frame(root, background="#00ff00")
right_frame1.grid(row=0, column=1, sticky="nsew")
right_frame_2 = tk.Frame(right_frame1, background="#0000ff")
right_frame_2.grid(row=0, column=0, sticky="nsew") # expand to fill available space
right_label_1 = tk.Label(right_frame_2, text="CENTER ME!")
right_label_1.grid(row=0, column=0, sticky="ew") # expand horizontally
root.rowconfigure(0, weight=1) # make left and right frame expand vertically
root.columnconfigure(1, weight=1) # make right frame expand horizontally
# allocate all space to right_frame_2
right_frame1.rowconfigure(0, weight=1)
right_frame1.columnconfigure(0, weight=1)
# allocate all space of right_frame_2 to right_label_1
right_frame_2.rowconfigure(0, weight=1)
right_frame_2.columnconfigure(0, weight=1)
root.mainloop()
Result:
When the window is resized:
Reading through other stackoverflow questions, and other sources I do see that bind can be used to call a function. Currently I'm working on a program that will communicate with a database (most likely mongodb), and so far I've set up a frame that has 2 inputs per row (key-value). I haven't completely decided whether I want one row per document, or one row per field. Right now, if a user has a lot to type then it wouldn't be ideal for them because you can't see everything you write. So what I was thinking is that, if the user clicks on the entry widget, then the box would become bigger and show them everything they have written. My current line of thinking is that maybe I could create another frame for it and somehow pass onto the information to that?
This is what it currently looks like
Then what I'd ideally want it to look like
Here's the code if interested how I made it (Images are from the "CreatePage" section):
from tkinter import *
import tkinter as tk
class Database_Project(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
stack_frame_container = tk.Frame(self)
stack_frame_container.grid_columnconfigure(0, weight=1)
stack_frame_container.grid_rowconfigure(0, weight=1)
stack_frame_container.pack(side="top", fill="both", expand=True)
self.frameslist = {}
for frame in (MainPage, CreatePage):
frame_occurrence = frame.__name__
active_frame = frame(parent=stack_frame_container, controller=self)
self.frameslist[frame_occurrence] = active_frame
active_frame.grid(row=0, column=0, sticky="snew")
self.current_frame("MainPage")
def current_frame(self, frame_occurrence):
active_frame = self.frameslist[frame_occurrence]
active_frame.tkraise()
class MainPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label_create = tk.Label(self, text="Create and insert data").grid(row=0, column=0, padx=50, pady=(50,0))
create_button = tk.Button(self, text="CREATE", command=lambda: controller.current_frame("CreatePage")).grid(row=1, column=0)
label_read = tk.Label(self, text="Query over data").grid(row=0, column=1, padx=50, pady=(50,0))
read_button = tk.Button(self, text="READ").grid(row=1, column=1)
label_update = tk.Label(self, text="Modify existing data").grid(row=2, column=0, padx=50, pady=(50,0))
update_button = tk.Button(self, text="UPDATE").grid(row=3, column=0, pady=(0,50))
label_delete = tk.Label(self, text="Remove data").grid(row=2, column=1, padx=50, pady=(50,0))
delete_button = tk.Button(self, text="DELETE").grid(row=3, column=1, pady=(0,50))
class CreatePage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.inputlist = []
self.newinputlist = []
labels = [tk.Label(self, text="Enter unique field"), tk.Label(self, text="Enter corresponding the value/s")]
self.inputlist.append(labels[:])
for toplabels in range(1):
self.inputlist[toplabels][0].grid(row=toplabels, column=0, padx=10, pady=5)
self.inputlist[toplabels][1].grid(row=toplabels, column=1, padx=10, pady=5)
for entries in range(2):
for entrynum in range(0, 1):
print("column:", entries)
print("row", entrynum)
self.newinputlist.append(tk.Entry(self, borderwidth=5))
for x in range(len(self.newinputlist)):
self.newinputlist[x].grid(row=1, column=x, padx=10, pady=5)
self.inputlist.append(self.newinputlist[:])
button_input_1 = [tk.Button(self, text="ADD FIELD/VALUE", command=self.add_insert), tk.Button(self, text="BACK", command=lambda: controller.current_frame("MainPage"))]
self.inputlist.append(button_input_1[:])
button_input_2 = [tk.Button(self, text="IMPORT FILE"), tk.Button(self, text="SUBMIT DATA")]
self.inputlist.append(button_input_2[:])
for button in range(len(self.inputlist) - 2, len(self.inputlist)):
self.inputlist[button][0].grid(row=button, column=0, padx=10, pady=5)
self.inputlist[button][1].grid(row=button, column=1, padx=10, pady=5)
def add_insert(self):
add_input = [tk.Entry(self, borderwidth=5), tk.Entry(self, borderwidth=5)]
self.inputlist.insert(-2, add_input)
self.newinputlist.append(add_input)
for widget in self.children.values():
widget.grid_forget()
for index, widgets in enumerate(self.inputlist):
widget_one = widgets[0]
widget_two = widgets[1]
print(str(index), widget_one, widget_two)
widget_one.grid(row=index, column=0, padx=10, pady=5)
widget_two.grid(row=index, column=1, padx=10)
if __name__ == "__main__":
NoSQL_Project = Database_Project()
NoSQL_Project.title("NoSQL Database Project")
NoSQL_Project.mainloop()
It's pointless to resize an Entry widget since they can only ever hold a single line. I'll give an example using the Text widget instead, though the technique works with any widget.
There's really no trick, just bind to <FocusIn> and <FocusOut>. In the following example I've created two Text widgets that have this resize behavior:
import tkinter as tk
def resizer(event):
if event.widget == event.widget.focus_get():
event.widget.configure(height=8)
else:
event.widget.configure(height=1)
root = tk.Tk()
root.geometry("400x200")
text1 = tk.Text(root, height=1, width=20)
text2 = tk.Text(root, height=1, width=20)
text1.pack(side="left")
text2.pack(side="right")
for widget in (text1, text2):
widget.bind("<FocusIn>", resizer)
widget.bind("<FocusOut>", resizer)
root.mainloop()
The actual behavior depends on how you've laid out your widget. This could cause widgets to jump around or the window resize, but every app will be different so it's hard to give a solution that works everywhere.
I am using this code to navigate between frames in my Tkinter text editor app. And when I want to organize the pageone with multiple frames in order to collect all the buttons in the same frame and other widgets in some other possible frames I got an error: _tkinter.TclError: cannot use geometry manager grid inside . which already has slaves managed by pack. if you could help me, ty.
here is the code I used
import tkinter as tk
from tkinter import ttk
LARGE_FONT=("Verdana",12)
def popupmsg(msg):
popup=tk.Tk()
popup.wm_title("!")
label=ttk.Label(popup,text=msg,font=LARGE_FONT)
label.pack()
b1=ttk.Button(popup,text="OKAY",command=popup.destroy)
b1.pack()
popup.mainloop()
class MainWindow(tk.Tk):
def __init__(self,*arg,**kwargs):
tk.Tk.__init__(self,*arg,**kwargs)
container=tk.Frame(self)
container.pack(side="top",fill="both",expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
menubar=tk.Menu(container)
filemenu=tk.Menu(menubar,tearoff=0)
filemenu.add_command(label="Save settings", command=lambda:popupmsg("Not supported yet!"))
filemenu.add_command(label="Exit",command=quit)
menubar.add_cascade(label="File", menu=filemenu)
tk.Tk.config(self,menu=menubar)
self.frames={}
for F in(StartPage,PageOne):
frame=F(container,self)
self.frames[F]=frame
frame.grid(row=0,column=0,sticky="nsew")
self.show_frame(StartPage)
def show_frame(self,cont):
frame=self.frames[cont]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self,parent,controller):
tk.Frame.__init__(self,parent)
label_startpage=ttk.Label(self,text="Start Page",font=LARGE_FONT)
label_startpage.pack(padx=10,pady=10)
button_to_pageONE=ttk.Button(self,text="Go to Page ONE",command= lambda:
controller.show_frame(PageOne)).pack()
class PageOne(tk.Frame):
def __init__(self,parent,controller):
tk.Frame.__init__(self,parent)
self.rowconfigure(0, minsize=200, weight=1)
self.columnconfigure(1, minsize=200, weight=1)
txt_edit = tk.Text(self).grid(row=0, column=1, sticky="nsew")
button_frame = tk.Frame(self,bg="lightblue").grid(row=0, column=0, sticky="ns")
btn_open = ttk.Button(button_frame, text="Open").grid(row=0, column=0, sticky="ew", padx=5, pady=5)
btn_save = ttk.Button(button_frame, text="Save As...").grid(row=1, column=0, sticky="ew", padx=5)
button_to_startpage = ttk.Button(button_frame, text="Back to Start Page",
command=lambda:
controller.show_frame(StartPage)).grid(row=2, column=0,
sticky="ew", padx=5,
pady=5)
app=MainWindow()
app.geometry("1280x720")
app.mainloop()
You cannot use pack as well as grid on container
container=tk.Frame(self)
container.pack(side="top",fill="both",expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
pack and grid just don't work well together.
See this entry for more info
You're making a very common mistake on this line:
button_frame = tk.Frame(self,bg="lightblue").grid(row=0, column=0, sticky="ns")
Because you are doing tk.Frame(...).grid(...), button_frame is being set to the result of .grid(...). That makes button_frame None. When you do btn_open = ttk.Button(button_frame, ...), that places the button as a child of the root window rather than a child of button_frame. You're using pack in the root window, so you can't use grid for any other widgets in the root window.
The solution is to properly create button_frame by separating widget creation from widget layout:
button_frame = tk.Frame(...)
button_frame.grid(...)
I'm using a button to add a new entry widget every time it is pressed, which works. However, whenever I itierate over the list to get the values in the entry widgets, I get an error:
AttributeError: 'list' object has no attribute 'get'
What am I missing? I found similar questions but the answers arent' working for me.
Steps to reproduce:
Run this code
Click "Add system block" to add Entry widgets
Type something into entry blocks
Click "Get Entry Values"
Expected Results: Whatever was typed in entry blocks are printed to console
from tkinter import *
import tkinter as tk
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import(FigureCanvasTkAgg, NavigationToolbar2Tk)
import numpy as np
class MyApp:
def __init__(self, parent):
self.myParent = parent ###remember my parent, the root
parent.geometry("500x300+0+0") #Default window size
global system_block_list, num_of_block
system_block_list = []
num_of_block = 0
self.frm_detail=tk.Frame(
master=root,
relief=tk.RAISED,
borderwidth=1,
height=100,
bg="#e0e0e0"
)
self.frm_detail.rowconfigure([0,1], weight=0)
self.frm_detail.columnconfigure(0, weight=1, minsize=50)
self.frm_detail.columnconfigure(1, weight=10, minsize=50)
self.frm_detail.grid(row=0,column=0, sticky="nsew")
lbl_detail = tk.Label(master=self.frm_detail, text="System Diagram", bg="white", fg="black")
lbl_detail.grid(row=0,column=0, columnspan=2, sticky="new")
"""""Diagram Buttons Frame"""""
self.frm_diagram_btns=tk.Frame(
master=self.frm_detail,
relief=tk.RAISED,
borderwidth=1,
height=300,
bg="#e0e0e0"
)
self.frm_diagram_btns.grid(row=1,column=0, rowspan=1, sticky="nsew")
self.btn_add_block = tk.Button(
text="Add System Block",
width=20,
height=1,
bg="#c4fffe",
fg="black",
master=self.frm_diagram_btns,
relief=RAISED
)
self.btn_add_block.pack()
self.btn_add_block.pack_propagate(0)
self.btn_add_block.bind("<Button-1>", self.handle_add_block)
self.btn_get_values = tk.Button(
text="Get Entry Values",
width=20,
height=1,
bg="#c4fffe",
fg="black",
master=self.frm_diagram_btns,
relief=RAISED
)
self.btn_get_values.pack()
self.btn_get_values.pack_propagate(0)
self.btn_get_values.bind("<Button-1>", self.handle_button1)
"""""Block Diagram Frame"""""
self.frm_block_diagram=tk.Frame(
master=self.frm_detail,
relief=tk.RAISED,
borderwidth=1,
height=300,
bg="#e0e0e0"
)
self.frm_block_diagram.grid(row=1,column=1, rowspan=1, sticky="nsew")
'''TEST STARTS HERE'''
def handle_button1(self, event):
#Get value in filename entry
global system_block_list
values = [int(entry.get()) for entry in system_block_list]
print(values)
return values
def handle_add_block(self, event) :
global system_block_list, num_of_block
'''Generate a new Entry Widget and use grid to place it in frm_block_diagram'''
system_block_list.append([tk.Entry(self.frm_block_diagram, relief="raised",width=10,bg="white",fg="black")])
system_block_list[-1][0].grid(row=0, column=num_of_block, sticky='nsew', ipadx=5, ipady=10, padx=10, pady=10)
system_block_list[-1][0].grid_propagate(0)
'''reconfigure frm_block_diagram for another column'''
self.frm_block_diagram.columnconfigure(num_of_block, weight=1)
num_of_block += 1
return num_of_block
#Run the event loop
root = tk.Tk()
myapp = MyApp(root)
root.mainloop()
As pointed out by jasonharper. system_block_list is not a list of entries, but a list of lists of entries. Therefore changing:
system_block_list.append([tk.Entry(self.frm_block_diagram, relief="raised",width=10,bg="white",fg="black")])
system_block_list[-1][0].grid(row=0, column=num_of_block, sticky='nsew', ipadx=5, ipady=10, padx=10, pady=10)
system_block_list[-1][0].grid_propagate(0)
to
entry = tk.Entry(self.frm_block_diagram, relief="raised",width=20,bg="white",fg="black")
system_block_list.append(entry)
system_block_list[-1].grid(row=0, column=num_of_block, sticky='nsew', ipadx=5, ipady=10, padx=10, pady=10)
system_block_list[-1].grid_propagate(0)
All better!
I'm trying to create a custom frame in tkinter, Python v2.7. I have done this just fine once (a frame with a scrollbar), but my second attempt isn't working. I compare it to the Frame that does work, and I can't understand what I have done differently.
What I want is a frame that has a little separator line underneath it, so I'm creating a "normal" frame, a thin frame to use as a separator under it, and a bigFrame to hold it.
Everything I create in the class works, except the frame itself. Hopefully my comments explain what is and isn't showing.
from Tkinter import *
class FunFrame(Frame):
def __init__(self, master, lbl, **kwargs):
self.bigFrame = Frame(master)
Frame.__init__(self, self.bigFrame, width=280, height=200, bg="red", **kwargs)
self.grid(row=0, column=0, pady=3) #this is in bigFrame, and doesn't display
#however the padding is still respected
self.separator = Frame(self.bigFrame, height=2, bd=1, width=280, relief = SUNKEN)
self.separator.grid(row=1, column=0) #this is in bigFrame, and displays
self.l = Label(self, text=lbl) #this is in self and doesn't display
self.l.grid(row=0, column=0)
def grid(self, **kwargs):
self.bigFrame.grid(**kwargs)
if __name__ == "__main__":
root=Tk()
Frame1=FunFrame(root, "hello")
Frame2=FunFrame(root, "world")
Frame1.grid(row=0, column=0)
Frame2.grid(row=1, column=0)
root.mainloop()
If you call self.grid in __init__, it calls your own grid, not Tkinter's version.
Try following (renamed grid to grid_):
from Tkinter import *
class FunFrame(Frame):
def __init__(self, master, lbl, **kwargs):
self.bigFrame = Frame(master)
Frame.__init__(self, self.bigFrame, width=280, height=200, bg="red", **kwargs)
self.grid(row=0, column=0, pady=3)
self.separator = Frame(self.bigFrame, height=2, bd=1, width=280, relief=SUNKEN)
self.separator.grid(row=1, column=0)
self.l = Label(self, text=lbl)
self.l.grid(row=0, column=0)
def grid_(self, **kwargs): ######## grid -> grid_
self.bigFrame.grid(**kwargs)
if __name__ == "__main__":
root=Tk()
Frame1 = FunFrame(root, "hello")
Frame2 = FunFrame(root, "world")
Frame1.grid_(row=0, column=0) ######## grid -> grid_
Frame2.grid_(row=1, column=0) ######## grid -> grid_
root.mainloop()
I'd rather code as follow (if '....' was used to represent hierarchy visually):
from Tkinter import *
class FunFrame(Frame):
def __init__(self, master, lbl, **kwargs):
Frame.__init__(self, master)
if 'inside outer frame (self)':
innerFrame = Frame(self, width=280, height=200, bg="red", **kwargs)
innerFrame.grid(row=0, column=0, pady=3)
if 'inside inner frame':
self.l = Label(innerFrame, text=lbl)
self.l.grid(row=0, column=0)
separator = Frame(self, height=2, bd=1, width=280, relief=SUNKEN)
separator.grid(row=1, column=0)
if __name__ == "__main__":
root = Tk()
Frame1 = FunFrame(root, "hello")
Frame2 = FunFrame(root, "world")
Frame1.grid(row=0, column=0)
Frame2.grid(row=1, column=0)
root.mainloop()