Reputation: 1155
I have this GUI program, and one of the Frames has an OptionMenu in it. Whenever I select something in the OptionMenu, it resizes the parent frame, even though it has more than enough room to fit in the current panel. It's hard to describe in words, so here's an image of what I mean:
You can see that the purple, blue, and red frames have expanded with the size increase of the OptionMenu.
The window is split into two Frames, the left and right, with a Grid Layout, with a weight of 3 and 2, respectively. Inside of the two frames is a Pack layout of each colored panel, set to fill X. Inside of Purple, I have set this optionMenu, and packed it to the left of the panel.
When it changes its contents, it resizes the entire right frame, ignoring the grid weights and throwing off the balance of the GUI. If I set a fixed width for the OptionMenu, it doesn't resize, but still grows the frame out of alignment with the GridLayout. How can I get the frames to not resize based off of the width of this one element, and just place the element inside of the frame, which has more than enough room to handle it, even at it's widest?
Here's a boiled-down version of the GUI code I'm using, the full code is a bit too long since each panel is broken into classes for future functionality:
root = Tk()
root.geometry('640x480')
#Begin defining left frame areas
leftFrame = Frame(root)
grayPanel = Frame(leftFrame,bg="gray")
whitePanel = Frame(leftFrame,bg="white", height=50)
grayPanel.pack(expand=True,fill=BOTH)
whitePanel.pack(fill=X)
#Begin defining right frame areas
rightFrame = Frame(root)
purplePanel = Frame(rightFrame,bg="purple", height=50)
bluePanel = Frame(rightFrame,bg="blue")
redPanel = Frame(rightFrame,bg="red", height=100)
purplePanel.pack(fill=X)
bluePanel.pack(expand=True,fill=BOTH)
redPanel.pack(fill=X)
#create the options dropdown for purple
currentOption = StringVar(purplePanel)
currentOption.set("None")
optList = ["None","Some incredibly long string that breaks everything"]
options = OptionMenu(purplePanel,currentOption,*optList)
options.pack(side=LEFT)
leftFrame.grid(row=0,column=0,sticky=N+S+E+W)
rightFrame.grid(row=0,column=1,sticky=N+S+E+W)
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=3)
root.grid_columnconfigure(1, weight=2)
root.mainloop()
Upvotes: 2
Views: 1920
Reputation: 385890
The root of the problem lies in the fact you don't give any widgets a size, and all the frames are empty. The following description may sound confusing, but it's actually pretty logical, deterministic, and consistent with the documented behavior.
Because none of the frames have an explicit width, they default to a width of 0 (zero). The optionmenu has the behavior that it adapts its size to be large enough to show the value (except on the Mac, I think). It is the only widget with a non-zero requested (or "natural") size.
The grid is responsible for the left and right sides. The left size is of zero width, and the right side is the width of the option menu. For the sake of this discussion let's say that it is 140 pixels wide just to make math easy; the actual size may be bigger or smaller depending on your fonts, and we're ignoring borders to keep the math simple. Therefore, grid thinks the left wants to be zero, and the right wants to be 140. That is the only information it starts with when it tries to make everything fit.
You force the window to be 640 pixels wide but its children are only require 140 pixels, thus it has 500 extra pixels. You use column weights of three for the left and two for the right, so for every five pixels of extra space, three will go to the left and two will go to the right. That means that the left column gets 300 pixels of the extra space and the right column will get 200 pixels of the extra space. That makes the left column 300 pixels (0+300) and the right 340 (140+200). That's why they appear roughly the same size at startup.
Now, you change the value of the optionmenu, causing it to grow. Let's say the new width of the optionmenu is 440 pixels. The left side still wants to be zero, but now the right side wants to be 440. That means there are only 200 pixels of extra space: 120 for the left and 80 for the right. Remember, the left column's natural size is zero, so zero plus 120 means the left column will be 120 pixels wide. On the right, the natural size is 440. 440 plus the extra 80 means the right side will now be 520 pixels wide.
That is why, when there's a long option menu, the left side shrinks and the right side has extra space. You requested the left be zero, so it only gets to use the extra space after all of the other widgets with an actual size are laid out. The right side has a non-zero natural size, but the grid algorithm has space left over that it has to allocate to the right.
You didn't ask, but I'm going to assume a follow-up question will be "how do I make the two columns the same size?" The answer is to use the uniform
option with grid. Tell grid you want the two columns to be of a uniform size like this:
root.grid_columnconfigure(0, weight=3, uniform="column")
root.grid_columnconfigure(1, weight=2, uniform="column")
uniform
takes any string as an argument. All columns (or rows) with the same value are considered a "uniform group".
Note that with uniform
, grid still honors the weight. it's just that with uniform
the columns are guaranteed to strictly follow the column weights. In your case that means you have a 3:2 ratio between the left and right wide
Tkinter is a wrapper around a tcl interpreter with the tk toolkit. While the syntax is different than python, it's easy to translate the tcl documentation into python. Here are links to the descriptions of how grid
and pack
work:
Upvotes: 2