This is the code that's giving me trouble.
f = Frame(root, width=1000, bg="blue")
f.pack(fill=X, expand=True)
l = Label(f, text="hi", width=10, bg="red", fg="white")
l.pack()
If I comment out the lines with the Label, the Frame displays with the right width. However, adding the Label seems to shrink the Frame down to the Label's size. Is there a way to prevent that from happening?
By default, both pack and grid shrink or grow a widget to fit its contents, which is what you want 99.9% of the time. The term that describes this feature is geometry propagation. There is a command to turn geometry propagation on or off when using pack (pack_propagate) and grid (grid_propagate).
Since you are using pack the syntax would be:
f.pack_propagate(0)
or maybe root.pack_propagate(0), depending on which widgets you actually want to affect. However, because you haven't given the frame height, its default height is one pixel so you still may not see the interior widgets. To get the full effect of what you want, you need to give the containing frame both a width and a height.
That being said, the vast majority of the time you should let Tkinter compute the size. When you turn geometry propagation off your GUI won't respond well to changes in resolution, changes in fonts, etc. Tkinter's geometry managers (pack, place and grid) are remarkably powerful. You should learn to take advantage of that power by using the right tool for the job.
Related
My code is the following:
import tkinter as tk
#setting up window.
root = tk.Tk()
root.title("CSV Maker")
root.geometry("600x300")
#setting up frames.
leftFrame = tk.Frame(root, bg="red", width=300, height=300)
rightFrame = tk.Frame(root, bg="blue", width=300, height=300)
#placing frames on window.
leftFrame.grid(row=0, column=0)
rightFrame.grid(row=0, column=1)
#setting up labels.
inputPathLabel = tk.Label(leftFrame, text="Input File Path:")
#placing labels on frames.
inputPathLabel.grid(row=0, column=0)
root.mainloop()
When I remove the label I get the following:
Without label
However when I leave the code as it is below (with a label), I get a completely different result. It seems as if the frame was resized to another size than the one that I selected and the color is gone. Why is this?
With label
That is simply how tkinter was designed to work. When you use pack or grid, frames (or any other widget) will shrink or expand to try to fit all of its contents.
99.9% of the time, this is the behavior you want. Tkinter is really good at making GUIs the appropriate size.
From the official documentation for grid:
The grid geometry manager normally computes how large a master must be to just exactly meet the needs of its slaves, and it sets the requested width and height of the master to these dimensions. This causes geometry information to propagate up through a window hierarchy to a top-level window so that the entire sub-tree sizes itself to fit the needs of the leaf windows. However, the grid propagate command may be used to turn off propagation for one or more masters. If propagation is disabled then grid will not set the requested width and height of the master window. This may be useful if, for example, you wish for a master window to have a fixed size that you specify.
From the documentation for pack:
The packer normally computes how large a master must be to just exactly meet the needs of its slaves, and it sets the requested width and height of the master to these dimensions. This causes geometry information to propagate up through a window hierarchy to a top-level window so that the entire sub-tree sizes itself to fit the needs of the leaf windows. However, the pack propagate command may be used to turn off propagation for one or more masters. If propagation is disabled then the packer will not set the requested width and height of the packer. This may be useful if, for example, you wish for a master window to have a fixed size that you specify.
Notice that place doesn't have the same behavior. From the place documentation:
Unlike many other geometry managers (such as the packer) the placer does not make any attempt to manipulate the geometry of the master windows or the parents of slave windows (i.e. it does not set their requested sizes). To control the sizes of these windows, make them windows like frames and canvases that provide configuration options for this purpose.
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'm in a situation where I must use .pack() or .grid() alone to achieve specified widget sizes when they are laid out next to each other.
I used to mix .pack() with .grid() based on the knowledge gained in this post:
In what cases Tkinter's grid() cannot be mixed with pack()?
i.e., having frames packed next to each other, but inside each frame, I have grids. It worked fine, until I upgraded Tcl/TK backend to 8.6.8 in official Python 3.7.2, when I start seeing interpreter woes:
_tkinter.TclError: cannot use geometry manager grid inside . which already has slaves managed by pack
I then switched everything to .pack(), but now I have trouble to bring back my column designs where some widgets are wider than others.
Should I make everything .grid() to achieve this or are there secret ingredients in .pack() that I didn't see in docs?
Ideally I expect to achieve the following
In a row of widgets, widgets are next to each other and fill the space as much as possible, similar to .pack(side='left', fill='x', expand=True)
In the same row, I can still specify one widget to be wider than the rest, so that when setting everything to expand=True, the relative sizes are still in control.
Example
On macOS, the following code gives me wider entry and narrower slider, while I'd like the other way around, with just .pack().
import tkinter as tk
import tkinter.ttk as ttk
root = tk.Tk()
mainframe = tk.Frame(root, bg='blue')
mainframe.pack()
row1 = tk.Frame(mainframe, bg='red')
entry = ttk.Entry(row1, text='Search...')
slider = ttk.Scale(row1)
btn1 = ttk.Button(row1, text='Reset')
btn2 = ttk.Button(row1, text='Help')
entry.pack(side='left', fill='x', expand=True)
slider.pack(side='left', fill='x', expand=True)
btn1.pack(side='left', fill='x', expand=True)
btn2.pack(side='left', fill='x', expand=True)
row1.pack()
root.mainloop()
I must stress that I know how to use .grid() with my "rows" using .grid_columnconfigure(col, weight), which worked. It's just my working code (more complex than the above example, of course) simply broke after upgrading to TK 8.6, which implies that as my project gets bigger, mixed managers would be increasingly hard to maintain. So I decided to pick a camp between .pack() and .grid(). If one can achieve everything the other can at a cost, that'd be great.
With pack, it is not possible to have some widgets expand more than others. Either they expand or they don't. If you want to maintain relative proportions, you must either force each widget to a specific size and only have the one widget expand, or you need to use something other than pack.
Since it's unclear precisely what you want to achieve, and you claim to have had a working solution with grid, I recommend sticking with grid. It appears that you're already using pack for the root window. A simple solution is to create a frame that you can add to the root with pack, and then put the widgets inside the frame with grid.
Personally, I would do everything in .grid() because it is just easier to maintain and much simpler once things get complicated than .pack() is. I used pack a while back and it took me ages to get stuff done because things ended up where I didn't want them.
More on grid column sizing: http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/grid-config.html
and
Tkinter configure columnwidth
P.S. if you are into making GUI there is a library called Kivy which is great for this sort of stuff, just check it out (https://kivy.org/#home) if you're interested otherwise ya, use grid.
What is the primary difference between these two functions?
I'm currently working on a program that contains a canvas and a label, the latter is behaving as a button. Now the canvas was placed using .pack() while I used .place() for the label. Additionally the program reads and uses the location of mouse clicks to perform certain actions.
There is currently a bug that causes the program to trigger an unexpected action upon clicking the label, to my surprise upon clicking the label the x and y coordinate of the mouse click are not in reference to the window (as I expected it to be) but rather to the top-left coordinates of the label.
Is there a way to set it so that the label does not modify the x and y coordinate of the mouse click within it?
I'm relatively new to python and this is the first projects I've made using tkinter. If needed I can provide the code upon request.
EDIT: Entire program can be found here. Labels are added starting at line 87:
newgame_Label = Label(parent, anchor=NW, font="Calibri",
text="New Game")
newgame_Label.bind("<Button 1>", self.newgame_Click)
newgame_Label.place(x=450, y=300)
reset_Label = Label(parent, anchor=NW, font="Calibri",
text="Reset Board")
reset_Label.bind("<Button 1>", self.reset_Click)
reset_Label.place(x=450, y=400)
The function initBoard calls the .pack() method on the canvas. The program is a clone of Light's Out, thought it would be a good way of learning python and tkinter.
Alternately I can just not use a label and simply use the create_text method available to a canvas widget, but this would require some workaround to determine if it has been clicked. Mostly interested in finding out why is it that .place() for a Label Widget behaves the way it does event though they share the same frame.
The primary difference is that place lets you specify absolute coordinates, and pack uses a box model which allows you to place widgets along the sides of the box.
place is not used that often in real world applications. It's great for a very small class of problems, but pack and grid are more useful. Place requires that you spend a lot more effort making the GUI resize properly.
You can use pack() and place() together :
r = Tk()
label = Label(r, text = any text)
label.pack()
label.place(x = x, y = y)
Remember !
You must use pack before place
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.