Difference between .pack() and .place() and using them together? - python

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

Related

Defined frame size suppressed when using pack() geometry manager for a label

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.

tkinter with Tcl/TK 8.6: Achieve "column widths" using pack()

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.

Python: Resizing widgets when window is resized (Using PLACE manager)

After lots of reading, I could not find solution for my problem.
I have made a quiz program using Tkinter and Python. I also used pack geometry manager everywhere. Window is not resizable, it's set to 960x540, and all widgets are precisely set in window using X and Y coordinates. Now, I'd like to make full screen option. But, when I turn it full screen, all widgets are moved in upper left corner (because they are set to X and Y coordinates using place manager). Any idea how could widgets 'stretch' when I turn window into full screen?
I know this could be accomplished using grid managed, but I would like to use pack manager instead.
I didn't post any code, because I don't think it would help. Please correct me if I'm wrong!
PS: Sorry for my weird English, and thank you a lot!
If you don't want to use grid you're going to need to use the place manager. A lot of people recommend against it because it's more complex, but I like the control it gives you over your GUI.
For example you can have a label that always stays in a relative position and has a relative width and height (in relation to the size of the screen)
newLabel = tk.Label(root)
newLabel.place(relwidth = 0.5, relheight = 0.2, relx = 0.25, rely = 0.4)
This creates a label that is always half the width of the root size, 20% of the root height, and is always centered in the screen.
These are two excellent tutorials on pack and place, and more importantly they are a great reference for the options that pack and place offer (scroll to the bottom of the page to see all the options and their descriptions). You may be able to get pack to do what you want, but I stick with place.
http://effbot.org/tkinterbook/pack.htm
http://effbot.org/tkinterbook/place.htm

setting width and height of window ignored [duplicate]

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.

Python Tkinter - How to move a textbox by pixels

What the title says.
I'm having a problem moving the textbox from a side to side.
The code's long and it's about 200+ lines so I wont post it here.
Anybody has an idea?
You have various options for this, depending on what you mean by "text box," and whether you want to move it "by pixels" or "from a side to [another] side."
If you just want to display text, you can use a Label widget. If you want a text box where the user can enter text, try an Entry widget. If you want to move your widget from one area of the screen to another, you can use the grid geometry manager and simply use grid_forget to "unplace" your widget then grid (with different options than you originally used, of course) to put it somewhere else.
If you just have text and you'd like to move it pixel by pixel, you could create a Canvas and then use that widget's create_text method to create some text in a specific place on the Canvas. You can the use the Canvas widget's itemconfig method to move the text to a new location.
If you need something more complex than text, like an Entry widget, and you want to move it pixel by pixel, do the same as above but use the create_window method instead.
See Canvas, grid, Label, Entry, and these SO questions about create_window.

Categories