Tkinter Grid: How to position widgets so they are not stuck together

You need to use grid_columnconfigure to give the columns in the middle some weight. With more weight than the other columns, they will expand and contract to fill any extra space you have. However, in your case there’s really no need to use grid. Everything in your GUI is aligned along particular sides for which pack is a more natural fit.

I’ll show how to use both pack and grid, starting with pack since it’s the easiest. Even if you insist on using grid, read through the next section to understand how to break one big layout problem into many smaller layout problems.

Divide and conquer

The best way to do layout in Tkinter is “divide and conquer”. Start with the outermost widgets and get them exactly the way you want. Then, tackle each of these one at a time.

In your case you have one outermost widget – the main container. Since it is the only widget in the window, pack is the simplest way to get it to fill the whole container. You can use grid as well, but it requires a little extra work:

self.main_container = Frame(parent. background="bisque")
self.main_container.pack(side="top", fill="both", expand=True)

It helps to temporarily give your frames distinctive colors so you can visualize them as you develop. Add just the above code to your __init__, run your app, then resize the window to see that the main frame properly grows and shrinks.

Next, you have two frames — top_frame and bottom_frame. By their names and judging by how you’ve attempted to use grid, I assume they should fill the GUI in the x direction. Also, I’m guessing the top frame is some sort of toolbar, and the bottom frame is the real “main” part of your GUI. Thus, let’s make the bottom widget take up all the extra space.

Since these are stacked on top of each other, pack is again the natural choice. Add the following code — and only the following code — to make sure these areas occupy the parts of the window that you expect, and that they have the proper resize behavior.

    self.top_frame = Frame(self.main_container, background="green")
    self.bottom_frame = Frame(self.main_container, background="yellow")
    self.top_frame.pack(side="top", fill="x", expand=False)
    self.bottom_frame.pack(side="bottom", fill="both", expand=True)

Next comes the frames for the labels. Again, these occupy space along the edges of their container so pack makes the most sense. And again, add just the following bit of code, run your program, and make sure things are still resizing properly and filling the right parts of the window:

    self.top_left = Frame(self.top_frame, background="pink")
    self.top_right = Frame(self.top_frame, background="blue")
    self.top_left.pack(side="left", fill="x", expand=True)
    self.top_right.pack(side="right", fill="x", expand=True)

Next, you have your “corner” labels. Again, since the container is but a single row of widgets, pack makes it easy. Since you want the labels in the corners, we’ll set the sticky attribute a little different for each:

    self.top_left_label = Label(self.top_left, text="Top Left")
    self.top_right_label = Label(self.top_right, text="Top Right")
    self.top_left_label.pack(side="left")
    self.top_right_label.pack(side="right")

Finally, you have the text widget. It fills the entire bottom frame, so once again pack is our friend:

    self.text_box = Text(self.bottom_frame, height=5, width=40, background="gray")
    self.text_box.pack(side="top", fill="both", expand=True)

pack or grid?

You used grid for your original code and asked how to fix it. Why did I use pack for my examples?

When you use pack, all of the configuration options can be wrapped up in a single call. With grid along with putting the widgets in their containers you must also take care to give your various columns and rows “weight” so that they resize properly. When you are simply stacking widgets on top of each other or aligning them in a row, pack is much easier to use.

In my GUIs, I almost always use a combination of grid and pack. They are both powerful, and excel at different things. The only thing to remember — and this is crucial — is that you can’t use them in the same parent. Using your code as an example, you can’t use pack for the top_left frame and grid for the top_right frame, since they both share the same parent. You can, however, mix them within the same application.

Once more, using grid

Ok, so maybe you really want to use grid: maybe this is a school assignment, or maybe you just want to focus on one geometry manager at a time. That’s cool. Here’s how I would do it. Again, you must divide and conquer:

Start with the main frame. Replace the one statement where we pack the main container with the following lines. Notice that we have to configure the rows and columns on the parent, not the frame that we created.

    self.main_container.grid(row=0, column=0, sticky="nsew")
    self.myParent.grid_rowconfigure(0, weight=1)
    self.myParent.grid_columnconfigure(0, weight=1)

Ok, now for the top and bottom frames. Remove the pack, and add the following lines. You still only have a single column, but this time there are a couple of rows. Notice which row gets a weight of 1:

    self.top_frame.grid(row=0, column=0, sticky="ew")
    self.bottom_frame.grid(row=1, column=0,sticky="nsew")
    self.main_container.grid_rowconfigure(1, weight=1)
    self.main_container.grid_columnconfigure(0, weight=1)

The corner frames — at last, a container with more than one column! Let’s create a third column in the middle to take up all the slack. Replace the pack statements with the following, paying close attention to what is given “weight”:

    self.top_left.grid(row=0, column=0, sticky="w")
    self.top_right.grid(row=0, column=2, sticky="e")
    self.top_frame.grid_columnconfigure(1, weight=1)

Next, the labels in their frames. Since we don’t need them to expand, we can keep the default weight of zero. You may think it odd that both labels are in column zero. Remember that they are in different parents, and they are the only widgets in their parents so there’s only one column in each:

    self.top_left_label.grid(row=0, column=0, sticky="w")
    self.top_right_label.grid(row=0, column=0, sticky="e")

Finally we have the text widget which is the only widget in the bottom frame. Replace the final pack statements with the following:

    self.text_box.grid(row=0, column=0, sticky="nsew")
    self.bottom_frame.grid_rowconfigure(0, weight=1)
    self.bottom_frame.grid_columnconfigure(0, weight=1)

Conclusion

Laying out widgets is easy, but you have to be systematic about it. Think about what you are doing, organize your widgets into groups, and then tackle the groups one at a time. When you do that, it should become apparent which geometry manager you should use.

As you can see, grid requires a few more lines of code but it’s the right choice when you really do have a grid. In your case you had already sub-divided your GUI into sections, and so even though the end result was a grid, each section was either packed on top or below another, or to the left or right edges. In these cases, pack is a bit easier to use since you don’t have to worry about row and column weights.

Leave a Comment