Is there any better method to manage place in Tkinter in Python? - python

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

Related

Python Tkinter - full width frames not working

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?

Scrollbar in Tkinter

I tried many things to do 2 things :
center my content (horizontal)
have a scrollbar right and bottom
Here my code :
# encoding: utf8
from tkinter import *
from tkinter import ttk
class Program:
def __init__(self):
# Tk.__init__(self)
# Fill the content of the window
self.window = Tk()
self.window.geometry("1080x720")
self.createFrameWithScrollbar()
self.content()
def createFrameWithScrollbar(self):
# Create a Main Frame
self.mainFrame = Frame(self.window)
self.mainFrame.pack(fill=BOTH, expand=True)
# Create a canvas
self.canvas = Canvas(self.mainFrame)
self.canvas.pack(side=LEFT, fill=BOTH, expand=True)
# Add a scrobar
yScrollbar = ttk.Scrollbar(self.mainFrame, orient=VERTICAL, command=self.canvas.yview)
yScrollbar.pack(side=RIGHT, fill=Y)
xScrollbar = ttk.Scrollbar(self.mainFrame, orient=HORIZONTAL, command=self.canvas.xview)
xScrollbar.pack(side=BOTTOM, fill=X)
# Configure the canvas
self.canvas.configure(yscrollcommand=yScrollbar.set)
self.canvas.configure(xscrollcommand=xScrollbar.set)
self.canvas.bind('<Configure>', lambda e: self.canvas.configure(scrollregion = self.canvas.bbox("all")))
self.frame = Frame(self.canvas)
self.canvas.create_window((0,0), window=self.frame, anchor="nw")
self.currentFrame = Frame(self.frame)
self.currentFrame.configure(bg="red")
self.currentFrame.pack(fill=BOTH, expand=1)
def content(self):
label_title = Label(self.currentFrame, text="Title")
label_title.grid(column=0, row=0, sticky=NSEW)
label_description = Label(self.currentFrame, text="Title")
label_description.grid(column=0, row=1, sticky=NSEW)
label_version = Label(self.currentFrame, text="0.2 beta [Novembre 2020]")
label_version.grid(column=0, row=2, sticky=NSEW)
# Lauch the program
app = Program()
app.window.mainloop()
So the result is the following :
So any suggestion ?
The bottom sidebar is so small ! I don't know why.
Are regarding the text, it's not center at all ;(
I want to have it center and changing position if I rezize the window
Thanks a lot
pack works by allocating space along an entire empty side of the master, so the order of when you call pack matters. When you pack the canvas before packing the scrollbar, the canvas takes up the entire left side from top to bottom.
The simple solution is to pack the scrollbars before you pack the canvas.
yScrollbar.pack(side=RIGHT, fill=Y)
xScrollbar.pack(side=BOTTOM, fill=X)
self.canvas.pack(side=LEFT, fill=BOTH, expand=True)
To make the UI look a bit more professional, grid is usually a better choice when laying out scrollbars. With pack, either one edge of the horizontal scrollbar will be below the vertical scrollbar, or the edge of the vertical scrollbar will be directly beside the horizontal one. That, or you have to add a little bit of padding so the scrollbars don't seem to overlap.
When I have a scrollable widget, I almost always put it and the one or two scrollbars together in a frame using grid. Then, I can treat them all as if they were a single widget when adding them to the rest of the UI.
In a comment you ask about how to do it in a grid. You simply need to place the widgets in the appropriate rows and columns, and make sure that the row and column with the scrollable widget has a non-zero weight so any extra space is allocated to it.
self.mainFrame.grid_rowconfigure(0, weight=1)
self.mainFrame.grid_columnconfigure(0, weight=1)
yScrollbar.grid(row=0, column=1, sticky="ns")
xScrollbar.grid(row=1, column=0, sticky="ew")
self.canvas.grid(row=0, column=0, sticky="nsew")

How do I align something to the bottom left in Tkinter using either .grid() or .pack()?

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:

Tkinter grid spacing problems

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()

How to make Tkinter columns of equal width when widgets span multiple columns?

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)

Categories