2
votes

In python (2.7.12) with tkinter I try to achieve the following: I want to dynamically hide and unhide certain text labels. This I try to do using pack_forget and then later doing a pack again. The labels I am about to hide and unhide are in a fixed place (relative position) among other labels. When the labels are hidden, they shall consume no space.

For example, think about a label with the text "not" appearing or disappearing as part of a sentence. The other parts of the sentence would also be formed with labels. Thus, I can not simply use pack_forget followed by pack on that "not" label: The pack would not place the label at its previous position, but at the end of the packing list.

Thus, to ensure the label keeps its relative position (and to avoid re-packing everything again) I do the following: I create a frame, pack it at the location where my label shall be appearing/disappearing, and put the label inside. Then, I hide/unhide the label, but not the frame.

My expectation is, that the frame's size should adjust itself depending on whether the label within is hidden or not. Certainly, when the label is visible, the frame should be large enough to contain it. But, after calling pack_forget on the label, the frame's size should shrink to zero again.

However, this does not work: Initially, the frame actually has zero size. But, after the text label within has been visible once, the frame keeps its size. After calling pack_forget on the label within the frame, the text of the label actually disappears, but the frame does not shrink to zero.

What am I doing wrong?

Here is a complete code example (adapted from an example by Tony Veijalainen). On my system at least it shows the behaviour, that the text actually disappears, but the frame keeps its width (and height), which should not be the case.

from Tkinter import *

def toggle():
    if label.winfo_ismapped():
        button['text']='unmap'
        label.pack_forget()
    else:
        button['text']='map'
        label.pack()

root = Tk()
button = Button(text="Push me", command=toggle)
button.pack()
labelFrame = Frame()
labelFrame.pack()
label = Label(labelFrame, text="Hello Big Big World")
label.pack()
root.wait_window()

If in the example you remove labelFrame and create label directly as a child of root, everything works fine: The hidden label really consumes zero space.

3

3 Answers

3
votes

When you call pack_forget on the last widget within a container, the container sees that it has no children and thus gives up responsibility for managing the size of the container.

A simple hack is to pack a 1x1 widget in the container. Doing so will trigger pack to adjust the container to fit, causing it to shrink.

pack has options that allow you to place it before or after other widgets, so you could always keep track of the widget after the one you are making invisible so that you can later restore it to the correct place.

You could also switch to using grid instead of pack, since grid can remember the relative position of each widget.

0
votes

say we got two frames and a root like this

frame2 | frame 1 | root

If you want to make this hidden

self.frame2.pack(side='left', fill=tk.Y)

To make the other widgets resize do this (the lstb is in frame1)

self.frame1.pack(side='left', fill=tk.Y)
self.lstb.pack(fill=tk.Y, expand=1)

In the root I also have a Text

self.text.pack(fill=tk.Y, expand=1)

When I hide frame1, the other 2 (fram1 with listbox and root with Text) resize themselves. The only problem with frames is that you cannot make them visible again with pack(). You can do make the other widgets visible again, but if you pack_forget them, the other widgets won't resize. I haven't still found a solution to bring back frames to visibility.

0
votes

Update: there ARE before and after pack() options after all! I'm not sure at what version of Tkinter they became available, but they're at least in Py3.6 and later. I didn't see this on effbot.org, but I dug deeper based on Bryan Oakley's answer.

Original: It's disappointing that there's no before or after option for the pack() method. But if you have your heart set on using the pack manager instead of the grid manager, you can pack_forget() the desired widgets as needed. And when it's time to put them back where they were, you can work around the pack limitation by simply forgetting the rest of the widgets and then repacking all of them in the desired order. It may not be ideal, but it would get the job done.