4

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?

AlwaysLearning2
  • 144
  • 1
  • 1
  • 6
  • If you really want precise control over control sizes and locations, consider `place` rather than `grid` or `pack`. You could also try setting widths for specific controls if the default flow doesn't fit your needs. But it's hard to help solve a problem about spacing without any images! – jonrsharpe Nov 12 '15 at 17:43
  • This doesn't answer your question, but you should not `grid` a widget and assign it to a variable on the same line. See [Tkinter: AttributeError: NoneType object has no attribute get](http://stackoverflow.com/q/1101750/953482) for more information. – Kevin Nov 12 '15 at 18:05

1 Answers1

6

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()
Engineer
  • 8,529
  • 7
  • 65
  • 105
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • Excellent details, thank you! What would happen if scrollbars were used with treeview? Could pack() still be used? – AlwaysLearning2 Nov 13 '15 at 15:46
  • @AlwaysLearning2: you can use `pack` with scrollbars, but `grid` is the better choice. It's hard to describe in a tiny comment box, but `pack` makes it hard to get the scrollbars to meet properly in the corner. – Bryan Oakley Nov 13 '15 at 16:14