In short, I have such a code (it is a fragment, but the important one).
class DF(tk.Toplevel):
def __init__(self, master,width):
tk.Toplevel.__init__(self)
self.root_frame = Frame(self, bg="red")
self.root_frame.grid(row=0, column=0, padx=20, pady=20, sticky="ne")
bottom_frame = Frame(self)
bottom_frame.grid(row=1, column=0, padx=5, pady=5, sticky="ne")
cancel_button = Button(master=bottom_frame)
cancel_button.pack(side="right")
For example, this is how I dynamically create consecutive _main_form elements.
def add(self):
label = tk.Label(master=self._main_frame)
label.grid(row=self._rows, column=0, columnspan=2, sticky="we")
tk.Entry(master=self._main_frame)
textbox.grid(row=self._rows, column=2, columnspan=2, sticky="ne")
However, the window does not adjust to the window's width. What can I do in such a situation? Thanks to bg you can see as if these frames were not at all in their entire width. I also add a screen.
The root of the problem is that you're using grid in the Toplevel, but you haven't told grid what to do with extra space. As a rule of thumb, you should always call rowconfigure and columnconfigure to give a positive weight to at least one row and one column.
self.grid_columnconfigure(0, weight=1)
In this case, however, since you only have two full-width frames inside the Toplevel, it's simpler to use pack for both the top and bottom frames.
bottom_frame.pack(side="bottom", fill="x")
self._main_frame.pack(side="top", fill="both", expand=True)
Either of those solutions solve the problem of the bottom and top frame not taking the full width of the window. You need to be equally diligent to add weight to columns and rows inside of self._main_frame.
For a deeper description of how the weight option works, along with some visual aids, see What does 'weight' do in tkinter?
Related
I am currently trying to make a game in Tkinter which uses multiple different windows.
However, as I am trying to create the layout of a secondary window, I can't seem to get my Return to Menu button underneath the list box, and aligned to the left. I have tried it using .pack() and .grid() methods, but they don't make sense to me.
I've tried using .pack():
header = Frame(wn).pack()
title = Label(header, text='Single-Player',font=('Arial bold',20),bg=bgcolor).pack(anchor='center')
footer = Frame(wn).pack(side=BOTTOM)
return_to_menu = Button(footer, text='Return to Main Menu',font=('Arial',16),bg=bgcolor,command=lambda: menu()).pack(side=BOTTOM,padx=20,pady=250)
# body frame (left side)
bodyL = Frame(wn).pack(side=LEFT)
#output box
output = Listbox(bodyL, width=50, font=("Arial", 20)).pack(side=LEFT,padx=15)`
And I've tried using .grid():
header = Frame(wn).grid(sticky=N)
title = Label(header, text='Single-Player',font=('Arial bold',20),bg=bgcolor).grid(sticky=N+E+W,row=0,column=0)
footer = Frame(wn).grid(sticky=S)
return_to_menu = Button(footer, text='Return to Main Menu',font=('Arial',16),bg=bgcolor,command=lambda: menu()).grid(sticky=S,padx=20,row=0,column=0)
# body frame (left side)
bodyL = Frame(wn).grid(sticky=W)
#output box
output = Listbox(bodyL, width=50, font=("Arial", 20)).grid(sticky=W,padx=15, )`
However using .grid() doesn't align my title to the center of the screen anymore.
Is there a way to center it more efficiently - I didn't like using padx=450 to get it centered.
What happens with the button and the list box, is the button appears to the left of the list box, instead of on the bottom. I have attempted several other methods, such as incrementing row numbers, but my script doesn't appear to do what I anticipated.
Here is an example of how you can set up the weight of specific columns and row to get widgets to stick to a specific location on the screen.
With the use of grid() we need to use columnconfigure() and rowconfigure() to make this work.
These 2 methods are used to define at what ratio the column or row will expand in relation to the columns or rows around it as the container grows or shrinks.
See below example and let me know if you have any questions:
import tkinter as tk
root = tk.Tk()
for i in range(3):
root.columnconfigure(i, weight=1)
root.rowconfigure(1, weight=1)
tk.Label(root, text='Top left').grid(row=0, column=0, sticky='w')
tk.Label(root, text='Top center').grid(row=0, column=1)
tk.Label(root, text='Top right').grid(row=0, column=2, sticky='e')
tk.Label(root, text='center').grid(row=1, column=1)
tk.Label(root, text='Bottom left').grid(row=2, column=0, sticky='w')
tk.Label(root, text='Bottom center').grid(row=2, column=1)
tk.Label(root, text='Bottom right').grid(row=2, column=2, sticky='e')
root.mainloop()
Example:
Here is another example but this time I have a title label outside of a frame so that we can make it easier to manage the title being centered and then working with all the other content of the frame is separate from the title label.
import tkinter as tk
root = tk.Tk()
root.columnconfigure(0, weight=1)
root.rowconfigure(1, weight=1)
frame = tk.Frame(root)
frame.grid(row=1, column=0, sticky='nsew')
for i in range(3):
frame.columnconfigure(i, weight=1)
frame.rowconfigure(1, weight=1)
tk.Label(root, text='Title centered').grid(row=0, column=0)
tk.Label(frame, text='Top left').grid(row=0, column=0, sticky='w')
tk.Label(frame, text='Top center').grid(row=0, column=1)
tk.Label(frame, text='Top right').grid(row=0, column=2, sticky='e')
tk.Label(frame, text='Center').grid(row=1, column=1)
tk.Label(frame, text='Bottom left').grid(row=2, column=0, sticky='w')
tk.Label(frame, text='Bottom center').grid(row=2, column=1)
tk.Label(frame, text='Bottom right').grid(row=2, column=2, sticky='e')
tk.Label(root, text='Footer centered').grid(row=2, column=0)
root.mainloop()
Example:
To address your problem in the comments you cannot use grid() or any other geometry manager for that matter on the same line you create your container. This will always cause the variable for that frame to return None as the geometry managers return None when called.
See this image as to what happens when you use grid() on the same line you create your container.
Now if you delete the grid() part on the row that your container is created and then write it on the next line as the commented out section of the above images shows it will work as expected. See below image of proper use of grid() for containers.
To address your 2nd question in the comments you can add this line to provide a button at the bottom left.
tk.Button(root, text='Bottom left button').grid(row=3, column=0, sticky='w')
Example:
I have a treeview inside of a frame that sits on top of another frame containing buttons. I would like the top frame to expand when I resize the window but keep the button frame from doing the same.
Code in Python 2.7.5:
class MyWindow(Tk.Toplevel, object):
def __init__(self, master=None, other_stuff=None):
super(MyWindow, self).__init__(master)
self.other_stuff = other_stuff
self.master = master
self.resizable(True, True)
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
# Top Frame
top_frame = ttk.Frame(self)
top_frame.grid(row=0, column=0, sticky=Tk.NSEW)
top_frame.grid_columnconfigure(0, weight=1)
top_frame.grid_rowconfigure(0, weight=1)
top_frame.grid_rowconfigure(1, weight=1)
# Treeview
self.tree = ttk.Treeview(top_frame, columns=('Value'))
self.tree.grid(row=0, column=0, sticky=Tk.NSEW)
self.tree.column("Value", width=100, anchor=Tk.CENTER)
self.tree.heading("#0", text="Name")
self.tree.heading("Value", text="Value")
# Button Frame
button_frame = ttk.Frame(self)
button_frame.grid(row=1, column=0, sticky=Tk.NSEW)
button_frame.grid_columnconfigure(0, weight=1)
button_frame.grid_rowconfigure(0, weight=1)
# Send Button
send_button = ttk.Button(button_frame, text="Send",
command=self.on_send)
send_button.grid(row=1, column=0, sticky=Tk.SW)
send_button.grid_columnconfigure(0, weight=1)
# Close Button
close_button = ttk.Button(button_frame, text="Close",
command=self.on_close)
close_button.grid(row=1, column=0, sticky=Tk.SE)
close_button.grid_columnconfigure(0, weight=1)
I make the instance elsewhere like this:
window = MyWindow(master=self, other_stuff=self._other_stuff)
What I have tried:
Tried locking resizability which only made the buttons disappear. I also tried changing weights around but my current configuration is the only way everything shows up on screen.
What it should always look like no matter how long the height:
What I want to prevent:
Thanks in advance.
The problem isn't that the button frame is growing, it's that the top frame is growing but isn't using all of it's space. This is because you are giving row 1 of top_frame a weight of 1 but you don't put anything in row 1. Extra space is being allocated to row 1 because of its weight, but row 1 is empty.
An easy way to visualize this is to change top_frame to a tk (rather than ttk) frame, and temporarily give it a distinctive background color. You will see that when you resize the window, top_frame fills the window as a whole, but that it is partially empty.
Create top_frame like this:
top_frame = Tk.Frame(self, background="pink")
... yields a screen like the following image when you resize the window. Note that the pink top_frame is showing through, and that button_frame remains its preferred size.
You can fix this by simply removing this one line of code:
top_frame.grid_rowconfigure(1, weight=1)
Now I only use the method place() to manipulate objects place. Like this:
self.s_date_label = Label(self, text = 'Start Date: ')
self.s_date_label.place(x=0,y=0)
self.start_date = Entry(self, bd=1)
self.start_date.place(x=70,y=0)
self.s_date_label2 = Label(self, text = 'example: 20160101'
self.s_date_label2.place(x=130,y=0)
However, I believe this is a stupid way.
Because when I have to control a lot of objects in the same line. All I can do is only control them by the parameter x in place().
Is there any better method to manage the location of objects?
The two alternatives are to use grid and pack
grid
grid is best if you need a table-like layout using rows and columns. You can specify specific cells, and you can have items span multiple rows and/or multiple columns.
For example:
l1 = tk.Label(parent, text="Username:")
l2 = tk.Label(parent, text='Password:")
username_entry = tk.Entry(parent)
password_entry = tk.Entry(parent)
l1.grid(row=1, column=0, sticky="e")
username_entry.grid(row=1, column=1, sticky="ew")
l2.grid(row=2, column=0, sticky="e")
password_entry.grid(row=2, column=1, sticky="ew")
parent.grid_rowconfigure(3, weight=1)
parent..grid_columnconfigure(1, weight=1)
For more information see http://effbot.org/tkinterbook/pack.htm
pack
pack is great for laying out objects in horizontal or vertical groups. pack is great for toolbars, and and is often what I use for the overall layout where you have a toolbar at the top, a statusbar at the bottom, and content in the middle.
For example:
toolbar_frame = tk.Frame(root)
statusbar_frame = tk.Frame(root)
content_frame = tk.Frame(root)
toolbar_frame.pack(side="top", fill="x")
statusbar_frame.pack(side="bottom", fill="x")
content_frame.pack(side="top", fill="both", expand=True)
For more information see http://effbot.org/tkinterbook/pack.htm
Mixing pack and grid
You can (and should) use both grid and pack in the same application. However, you cannot use both on widgets that share a common parent.
This won't work because toolbar and statusbar have the same parent:
toolbar = tk.Frame(root)
sstatusbar = tk.Frame(root)
toolbar.grid(...)
statusbar.pack(...)
This will work, because toolbar and save_button have different parents.
toolbar = tk.Frame(root)
save_button = tk.Button(toolbar, ...)
toolbar.pack(side="top", fill="x")
save_button.pack(side="left")
Use the grid() method to place the objects in a layout.
With the help of padding (padx and pady) you can also space your objects.
For details check: grid and sample
I'm trying to understand how tk grid layouts work since the interface isn't looking the way I thought it would. I'm trying to place a label followed by 2 buttons on the same row, and a treeview on the next row that spans beyond the width of the label and buttons. The only way to get this to look how I want is if I use a huge value for the treeview's columnspan. Here is my code:
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
columnHeadings = ("Heading 1", "Heading 2")
def printMsg():
print("Ok")
frame = ttk.Frame(root).grid(row=0, column=0)
label1 = tk.Label(frame, text="Label here").grid(row=0, column=0, columnspan=1)
button1 = tk.Button(frame, text="Yes", width=2, command=printMsg).grid(row=0, column=1)
button2 = tk.Button(frame, text="No", width=2, command=printMsg).grid(row=0, column=2)
#Label and buttons too far apart
#treeview1 = ttk.Treeview(frame, columns=columnHeadings, show='headings').grid(row=1,column=0, columnspan=3)
#Right distance but that's a huge columnspan
treeview1 = ttk.Treeview(frame, columns=columnHeadings, show='headings').grid(row=1,column=0, columnspan=100)
root.mainloop()
When columnspan is 3, the first row has a lot of spaces between the label and buttons. When columnspan is 100, the label and button spacing looks a lot better but I know this isn't the correct way. What's the correct way to do this?
You have several things conspiring against you in this little program. For one, frame is set to None, so you are actually putting all these widgets in the root window rather than the frame. This is because x=y().z() always sets x to the result of z, and grid() always returns None.
Second, a good rule of thumb for grid is that you need to give at least one (and usually exactly one) row and one column to have a weight, so that tkinter knows how to allocate extra space. You also need to use the sticky option so that your widgets expand to fill the space that's been given them. You are using neither of these techniques.
Third, in my experience I think it makes it very hard to debug layout problems when your layout statements are scattered throughout your code. It's best to group them altogether so you can more easily visualize your layout.
Solving the problem with grid
You can solve your problem by giving column 3 a weight of 1, and then having your treeview span that column. This prevents the columns that your buttons are in from expanding. If you also fix the problem with frame being None, and if you use the appropriate sticky options, you can get the look you want.
Here's an example:
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
columnHeadings = ("Heading 1", "Heading 2")
def printMsg():
print("Ok")
frame = tk.Frame(root)
frame.grid(row=0, column=0, sticky="nsew")
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
label1 = tk.Label(frame, text="Label here")
button1 = tk.Button(frame, text="Yes", width=2, command=printMsg)
button2 = tk.Button(frame, text="No", width=2, command=printMsg)
treeview1 = ttk.Treeview(frame, columns=columnHeadings, show='headings')
label1.grid(row=0, column=0, columnspan=1)
button1.grid(row=0, column=1)
button2.grid(row=0, column=2)
treeview1.grid(row=1,column=0, columnspan=4, sticky="nsew")
frame.grid_columnconfigure(3, weight=1)
frame.grid_rowconfigure(1, weight=1)
root.mainloop()
An alternate solution, using pack
All that being said, I think there are better solutions than to try to get everything to fit in a grid. My philosophy is to use the right tool for the job, and in this case the right tool is pack because it excels at stacking things top-to-bottom OR left-to-right (and visa versa).
In your case, you have two distinct regions in your program: a toolbar across the top, and a treeview down below. Since that's all you have, it makes sense to use pack, to place one on top of the other. So I would start by creating two frames and packing them:
toolbar = ttk.Frame(root)
treeframe = ttk.Frame(root)
toolbar.pack(side="top", fill="x")
treeframe.pack(side="bottom", fill="both", expand=True)
Now, anything you put in toolbar will not affect things in treeframe and visa versa. You are free to do whatever you want in each of those frames. As your program grows, you'll find that having distinct regions makes layout problems much easier to solve.
Since the toolbar contains buttons that are left-justified, you can use pack there too. Just make sure their parent is the toolbar frame, and you can pack them like this:
label1 = tk.Label(toolbar, ...)
button1 = tk.Button(toolbar, ...)
button2 = tk.Button(toolbar, ...)
label1.pack(side="left")
button1.pack(side="left")
button2.pack(side="left")
Finally, if you only have a treeview in the bottom (ie: no scrollbars or other widgets), you can use pack. Make sure it's parent is treeframe. You can use grid if you want, but pack is a bit more straight-forward.
treeview1.pack(fill="both", expand=True)
The nice thing about pack is you can truly put everything on one line. You don't have to take the extra step and give a row or column a weight.
Here's a complete example:
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
columnHeadings = ("Heading 1", "Heading 2")
def printMsg():
print("Ok")
toolbar = ttk.Frame(root)
treeframe = ttk.Frame(root)
toolbar.pack(side="top", fill="x")
treeframe.pack(side="bottom", fill="both", expand=True)
label1 = tk.Label(toolbar, text="Label here")
button1 = tk.Button(toolbar, text="Yes", width=2, command=printMsg)
button2 = tk.Button(toolbar, text="No", width=2, command=printMsg)
label1.pack(side="left")
button1.pack(side="left")
button2.pack(side="left")
treeview1 = ttk.Treeview(treeframe, columns=columnHeadings, show='headings')
treeview1.pack(side="top", fill="both", expand=True)
root.mainloop()
In the following, the buttons labelled 'ONE', 'TWO', and 'THR' do not get evenly spaced out. It seems to me that the root of the problem is that Tk is assuming a default minimum width for any column containing part of a widget that spans multiple columns. However, this behaviour appears to be undocumented, so I am unsure how to accommodate for or adjust it in order to get the columns to be of equal width - including the two columns spanned by the text widget and the single column not spanned by the text widget - and thus space out the buttons evenly. I could kludge it by trial and error, i.e. padding out the latter column until it matches the former two, but that seems a poor solution.
Edit: Following discussion below with #jwillis0720, I've added an extra column (3) and button ('FIV') to make the problem clearer. This question is about how to get columns the same width when some of those columns are spanned by multi-column widgets and others are not.
import Tkinter
master = Tkinter.Tk()
Tkinter.Button(master, text='ONE').grid(row=0, column=0)
Tkinter.Button(master, text='TWO').grid(row=0, column=1)
Tkinter.Button(master, text='THR').grid(row=0, column=2)
Tkinter.Button(master, text='FOU').grid(row=1, column=2)
Tkinter.Button(master, text='FIV').grid(row=0, column=3) # added as per above edit
Tkinter.Text(master).grid(row=1, column=0, columnspan=2)
master.mainloop()
Please note that using grid_columnconfigure with uniform does not solve this problem. Inserting the following lines (see answer to similar question here: How to create equal-width grid columns with Tkinter?) simply makes the columns stretchy; they remain unevenly sized:
master.grid_columnconfigure(0, weight=1, uniform='a')
master.grid_columnconfigure(1, weight=1, uniform='a')
master.grid_columnconfigure(2, weight=1, uniform='a')
master.grid_columnconfigure(3, weight=1, uniform='a') # added as per above edit
I think you might want to use the sticky option.
sticky= Defines how to expand the widget if the resulting cell is
larger than the widget itself. This can be any combination of the
constants S, N, E, and W, or NW, NE, SW, and SE.
For example, W (west) means that the widget should be aligned to the
left cell border. W+E means that the widget should be stretched
horizontally to fill the whole cell. W+E+N+S means that the widget
should be expanded in both directions. Default is to center the widget
in the cell.
import Tkinter
master = Tkinter.Tk()
Tkinter.Button(master, text='ONE').grid(row=0, column=0, sticky='NW')
Tkinter.Button(master, text='TWO').grid(row=0, column=1, sticky='NW')
Tkinter.Button(master, text='THR').grid(row=0, column=2, sticky='NW')
Tkinter.Button(master, text='FOU').grid(row=1, column=2)
Tkinter.Text(master).grid(row=1, column=0, columnspan=2)
master.mainloop()
Edit
What does it look like. Mine looks like this evenly spaced except the text widget takes up two columns as specified.
old post I know, but I also struggled to get the columns and rows to maintain a common width/height so I thought I would share my solution
newish to python and tkinter, so if there are any mistakes please let me know
I created a grid manager, this allowed the main window and any frame to be setup with evenly spaced columns and rows, it's not 100% but for what I was using it for it worked well, it was especially useful during the building phase
one downside when creating a frame is the maximum number of rows/columns of the frame must be equal to or less than the number or rows/columns it is spanning, otherwise it goes a bit weird (nut sure why)
hope this helps
import tkinter
class grid_manager:
def __init__(self, Frame, colour = "gray94"):
self.Frame = Frame
self.Colour = colour
def set_grid(self, numofRows, numofColumns, borderwidth = 1):
self.numofRows = numofRows
self.numofColumns = numofColumns
self.borderwidth = borderwidth
for i in range(numofRows):
for j in range(numofColumns):
canvas = tkinter.Canvas(self.Frame)
canvas.config(relief="raised", borderwidth=self.borderwidth) #comment out to hide grid layout
canvas.grid(row=i, column=j)
canvas.config(background=self.Colour)
self.Frame.columnconfigure(j, weight=1)
self.Frame.rowconfigure(i, weight=1)
mainwindow = tkinter.Tk()
mainwindow.title("Test")
mainwindow.geometry("640x480-8-200")
mainGrid = grid_manager(mainwindow)
mainGrid.set_grid(10, 10)
header_Frame = tkinter.Frame(mainwindow)
header_Frame.grid(row=0, column=0, columnspan=10, sticky="nsew")
headerGrid = grid_manager(header_Frame)
headerGrid.set_grid(numofRows=1, numofColumns=10, borderwidth=5)
footerFrame = tkinter.Frame(mainwindow)
footerFrame.grid(row=9, column=0, columnspan=10, sticky="nsew")
footerGrid = grid_manager(footerFrame, "red")
footerGrid.set_grid(numofRows=1, numofColumns=10, borderwidth=5)
rightFrame = tkinter.Frame(mainwindow)
rightFrame.grid(row=1, column=5, rowspan=5, columnspan=5, sticky="nsew")
rightGrid = grid_manager(rightFrame, "blue")
rightGrid.set_grid(numofRows=5, numofColumns=5, borderwidth=2)
leftFrame = tkinter.Frame(mainwindow)
leftFrame.grid(row=3, column=0, rowspan=5, columnspan=4, sticky="nsew")
leftGrid = grid_manager(leftFrame, "yellow")
leftGrid.set_grid(numofRows=5, numofColumns=4, borderwidth=2)
mainwindow.mainloop()
enter image description here
import tkinter
master = tkinter.Tk()
tkinter.Button(master, text='ONE ').grid(row=0, column=3, sticky='NW')
tkinter.Button(master, text='TWO ').grid(row=1, column=3, sticky='NW')
tkinter.Button(master, text='THR ').grid(row=2, column=3, sticky='NW')
tkinter.Button(master, text='FOU ').grid(row=3, column=3, sticky='NW')
tkinter.Text(master).grid(column=30, columnspan=10)