I am using the grid manager and have two frames side by side, and five columns with 1 button in each below the two frames in a second row, evenly spaced. All use "sticky" NSEW since I want them to scale proportionally if I enlarge the window.
When I add a text entry widget to the right frame, it distorts the buttons below them so they are larger than those to the left. I can't figure out how to prevent this distortion, or put another way, how to keep each column the same size.
Is there a reason why the text entry widget is not respective the row/col/weighting? Thanks in advance!
You can try to use columnspan or rowspan. This should allow the buttons to be unaffected by the text widget.
for example:
root=Tk()
button1=Button(root,text="button1")
button1.grid(row=1,column=0)
textbox=Text(root)
textbox.grid(row=1,column=1,rowspan=2)
In each frame you should set the "propagate" status to false, this will keep the frame from resizing based on what is inside. So if the frame uses grid() set grid_propagate(False) and so on.
I think this is due to the fact you've given the entry widget a specific width (or are accepting the default). Since the widget wants to be a particular size, it will cause the column to grow in order to fit the requested size of its children.
One solution is to set the size of the entry widget to 1. Then, because of the sticky settings for E and W, it will expand to exactly fit the column.
Related
I am trying to append an image to a frame I have. However, I have many frames within each other so I don't know what the dimensions of this frame are.
Is there any function or anything that will output the dimensions of the frame like "100x500", for example?
Thanks
Every widget has the methods winfo_width and winfo_height which return the current dimensions of the widget. You can also use winfo_reqwidth and winfo_reqheight to get the requested width and height. The two can be different if the size of the widget changes due to how it is managed (eg: when using sticky with grid, or fill with pack.
If the window has not yet actually been rendered, the width and height returned by winfo_width and winfo_height will be 1.
I want to eliminate strange extra space that seems to resist any size tweaking in my layout when using grid() alone, but calling in pack() sometimes make things worse: The GUI simply disappear entirely!
I read a few eye-opening layout answers from #Bryan Oakley such as:
When to use pack or grid layouts in tkinter?
and
Tkinter: grid or pack inside a grid?
but when I get down to write my own stuff, I still often have troubles.
My understanding:
I must have a Frame to fill the root window, otherwise there'd be no hope to fill the extra space in the window, however I tweak widgets alone.
For all the child widgets sitting inside a common parent Frame, I must use either pack() or grid() but not both.
When using grid() in a Frame, it's mandatory to specify Frame.grid_rowconfigure() and .grid_columnconfigure() with non-zero weight arguments. Otherwise, nothing would show up.
It's thus possible to have the main Frame using pack(), but its immediate child Frames all using grid(); Inside each of these child Frames on the grid, we could then pack() their own child widgets. In other words, we could interleave grid() and pack() by "regions" or container hierarchy levels, but never mix them in the same container: The only restriction.
By a careful weight design, I could fill a horizontal space in a parent Frame with a child Frame full of widgets laid out horizontally, e.g., all widgets use grid(sticky='nsew'), and the child Frame uses pack(side='top', fill='both', expand=True).
If my understanding was correct, then I could never figure out why #5 couldn't work for me, e.g., there is always unused extra space towards the right end of my horizontal child Frame inside the main Frame of the root window.
UPDATE 2
I figured it out. #5 didn't work for me because I forgot to specify .grid_columnconfigure(0, weight=1) in the main Frame before using grid(). My bad! Case closed.
UPDATE
I'm on macOS High Sierra, running python 3.6.4 Homebrew.
In what cases Tkinter's grid() cannot be mixed with pack()?
In all cases, you cannot use both grid and pack for widgets that have a common master. Within a master, all direct children must use the same geometry manager. Within an application as a whole, you can mix pack and grid all you want as long as you follow that one rule that you can't use them both for widgets that have the same parent.
I must have a Frame to fill the root window, otherwise there'd be no hope to fill the extra space in the window, however I tweak widgets alone.
This is not correct. You can easily fill all of the space in the root window without using a frame.
For all the child widgets sitting inside a common parent Frame, I must use either pack() or grid() but not both.
That is correct. The third option is to use place, though it's rarely the best choice.
When using grid() in a Frame, it's mandatory to specify Frame.grid_rowconfigure() and .grid_columnconfigure() with non-zero weight arguments. Otherwise, nothing would show up.
That is not true -- configuring rows and columns to have a non-zero weight isn't mandatory. It's usually a best practice, but it's not required in order for widgets to show up. The weight only applies to how grid manages extra space. Any widgets with a non-zero size should appear whether you use weights or not.
It's thus possible to have the main Frame using pack(), but its immediate child Frames all using grid()
Correct.
By a careful weight design, I could fill a horizontal space in a parent Frame with a child Frame full of widgets laid out horizontally, e.g., all widgets use grid(sticky='nsew'), and the child Frame uses pack(side='top', fill='both', expand=True).
That is correct.
I've always found the pack() geometry manager quite ambiguous in terms of how it acts when widgets are added.
Here I have a simple code for creating a new frame within a much bigger parent frame. The frame size has been set to 300x300. The problem is that if I create a label with the pack() geometry manager within this frame, it will suppress the original frame size. Basically the frame will become as big as is the label.
If I use the place() geometry manager, then there is no problem and the frame stays at the original 300x300 size.
The question is - why does packing a label within the frame affects its size? And then what is the best way to avoid this problem and have everything fixed at the size as they are set?
class MainRightFrame(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.place(x=600, y=0)
self.config(height=300, width=300, bg='green')
label = Label(self, text='Left Frame')
label.place(x=10, y=10) # OPTION 1
# label.pack() # OPTION 2
why does packing a label within the frame affects its size?
Because that is how the packer is designed to work. It will shrink or grow to fit its contents, which is what you want 99.99% of the time.
For the canonical documentation for how pack works, see the official tcl/tk documentation here:
The packer algorithm
And then what is the best way to avoid this problem and have everything fixed at the size as they are set?
The best wait to avoid this "problem" is to use place. However, the way both pack and grid works makes it much easier then using place to create a responsive UI that can handle changes in font size, resolution, and the user manually resizing the window.
In over a couple decades of writing GUIs with python/tkinter and tcl/tk, I have never used place except for extremely special circumstances. Its simply too difficult to use for must common layouts.
If you absolutely insist on using pack or grid without this "shrink to fit" behavior, you can pass a false value to the pack_propagate or grid_propagate method of the containing frame (eg: self.pack_propagate(False)). In my experience this is very rarely the right solution.
To fix this, add the following line after the line beginning: self.config(...:
self.pack_propagate(0)
See here for a documentary explaining this.
As #KārlisRieksts noted, this approach does not work however if the frame (or other parent widget) is packed with place() geometry manager. The child widgets will then affect the size of the parent.
I have a layout question for python with qt5.
So, there is a main window with a normal vertical layout with 2 widgets.
The first one is a widget with a scrollarea which has a minimumSize & maximumSize set.
The second one is another widget with yet another scrollarea widget.
My goal is this. I load some content in the first widget and depending on the content it should either shrink or grow to the set min/max sizes and if it gets bigger than maxsize the scrollarea should take effect.
The second widget should always take all the rest of the available space.
I've tried all kinds of sizePolicy combinations but can't get it to quite to work. The second widget has a sizePolicy of Prefered/Prefered with vertical stretch set to 1, so it takes all the available space for itself.
The 1st widget has a miniumSize of 100 and a maximumSize of 250. So ideally it should shrink to something between 100-250 if the content is less and it should grow to 250 and activate the scrollbar if the content is getting bigger.
If I update the content of widget one (i.e. by clicking on a button) it should 1. resize/shrink/grow so the content fits, but not smaller than 100 and not larger than 250. If I have less content I should not be able to make widget one reszie to 250 - it shoudl always just take the exact needed height.
Any idea on how to do that?
example layout
Your scrollarea isn't going to change size, because it's a scrollarea. It doesn't need to size itself to its content, because the size of the contents of a scrollarea is basically unbounded. You can just scroll more. If you want to change the size of the first scrollarea, you're going to have to set its height manually.
I'd like a to make a scrolled Tkinter textbox that fills the maximum alloted space. I have it working kind of...
For some reason when I stretch the window the text widget is fine; However, the scroll bar gets a ton of padding on the x axis.
The second problem is when I shrink the window the scrollbar goes of the screen.
Anyone know the solutions to these two programs?
snippet:
self.Fr = Tkinter.Frame(self, width=self.Wi, height=self.He)
self.Fr.pack(side='right', fill='both', expand='yes')
self.Te = Tkinter.Text(self.Fr, font=self.Fo, fg=self.FG, bg=self.BG,
selectforeground=self.SFG,
selectbackground=self.SBG,
insertbackground=self.IBG, wrap='word',
undo=True, maxundo=100)
#self.Te.grid(column=0, row=0, sticky='NSEW')
self.Te.pack(side='left', fill='both', expand='yes')
self.Sc = Tkinter.Scrollbar(self.Fr, elementborderwidth=1)
#self.Sc.grid(column=1, row=0, sticky='NSEW')
self.Sc.pack(side='right', fill='both', expand='yes')
self.Te.configure(yscrollcommand=self.Sc.set)
self.Sc.configure(command=self.Te.yview)
Your scrollbar gets all the padding because you use fill='both'. Even though it's a vertical scrollbar you asked it to take up extra space along the x axis, which results in the padding since the scrollbar itself won't stretch to make a wide scrollbar. You want vertical scrollbars to only fill in the Y direction and horizontal ones to fill in the X direction.
As to the scrollbar going off screen, that's a little complex to explain but it has a simple solution.
The problem is this: if you shrink a window managed by pack to a point where it's smaller than that required by the widgets inside, it starts clipping widgets. The way this works is it processes the widgets in order, laying out the window and then allocating any left-over space for any remaining widgets. This means that if a widget early in the order takes up all the remaining visual space, any later widgets will not appear.
The "order" mentioned above is the order of the packing list. Specifically, the order in which items were packed. So, if you pack the text widget and then the scrollbar Tk will first lay out the text widget, and any remaining space will be allocated to the scrollbar. IF you had packed the scrollbar first, it would get laid out and any remaining space would be given to the text widget.
It all sounds very complex, but the cool thing is that if you pack things in the proper order it all just works.
The general rule of thumb, then, is to make sure the last widget you pack is the one with expand set to true. This is your "elastic" widget. That way all the fixed-size widgets will take up whatever space they need first, and your "elastic" widget will take up all that is left.
There is another solution which is to give your text widget a requested width and height of one. With that, when the packer initially allocates space it will allocate only a small amount of space. Thus, when the window shrinks the text widget will shrink until it gets down to that tiny size. This isn't very practical though, since one of the great features of pack is that you can give all widgets their natural size (or they assume their natural size based on their content in the case of buttons and labels) and the packer does all the work. If you set the width and height to one, your initial window (unless explicitly set to a larger size) will be rather small.
This behavior is all documented in the man page for pack, though you have to read it really closely to fully grasp this behavior.