问题
Most of the topics I came across deals with how to not shrink the Frame
with contents, but I'm interested in shrinking it back after the destruction of said contents. Here's an example:
import tkinter as tk
root = tk.Tk()
lbl1 = tk.Label(root, text='Hello!')
lbl1.pack()
frm = tk.Frame(root, bg='black')
frm.pack()
lbl3 = tk.Label(root, text='Bye!')
lbl3.pack()
lbl2 = tk.Label(frm, text='My name is Foo')
lbl2.pack()
So far I should see this in my window:
Hello!
My name is Foo
Bye!
That's great, but I want to keep the middle layer interchangeable and hidden based on needs. So if I destroy the lbl2
inside:
lbl2.destroy()
I want to see:
Hello!
Bye!
But what I see instead:
Hello!
███████
Bye!
I want to shrink frm
back to basically non-existence because I want to keep the order of my main widgets intact. Ideally, I want to run frm.pack(fill=tk.BOTH, expand=True)
so that my widgets inside can scale accordingly. However if this interferes with the shrinking, I can live without fill/expand
.
I've tried the following:
pack_propagate(0)
: This actually doesn't expand the frame at all pastpack()
.- Re-run
frm.pack()
: but this ruins the order of my main widgets. .geometry('')
: This only works on theroot
window - doesn't exist forFrame
s.frm.config(height=0)
: Oddly, this doesn't seem to change anything at all.frm.pack_forget()
: From this answer, however it doesn't bring it back.
The only option it leaves me is using a grid
manager, which works I suppose, but not exactly what I'm looking for... so I'm interested to know if there's another way to achieve this.
回答1:
Question: Shrink a
Frame
after removing the last widget?
Bind to the <'Expose'>
event and .configure(height=1)
if no children.
Reference:
- Expose
An Expose event is generated whenever all or part of a widget should be redrawn
import tkinter as tk
class App(tk.Tk):
def __init__(self):
super().__init__()
tk.Label(self, text='Hello!').pack()
self.frm = frm = tk.Frame(self, bg='black')
frm.pack()
tk.Label(self, text='Bye!').pack()
tk.Label(frm, text='My name is Foo').pack()
self.menubar = tk.Menu()
self.config(menu=self.menubar)
self.menubar.add_command(label='delete', command=self.do_destroy)
self.menubar.add_command(label='add', command=self.do_add)
frm.bind('<Expose>', self.on_expose)
def do_add(self):
tk.Label(self.frm, text='My name is Foo').pack()
def do_destroy(self):
w = self.frm
if w.children:
child = list(w.children).pop(0)
w.children[child].destroy()
def on_expose(self, event):
w = event.widget
if not w.children:
w.configure(height=1)
if __name__ == "__main__":
App().mainloop()
Question: Re-run
frm.pack()
: but this ruins the order of my main widgets.frm.pack_forget()
, however it doesn't bring it back.
Pack
has the options before=
and after
. This allows to pack a widget relative to other widgets.
Reference:
- -before
Use its master as the master for the slaves, and insert the slaves just before other in the packing order.
Example using before=
and self.lbl3
as anchor. The Frame
are removed using .pack_forget()
if no children and get repacked at the same place in the packing order.
Note: I show only the relevant parts!
class App(tk.Tk):
def __init__(self):
...
self.frm = frm = tk.Frame(self, bg='black')
frm.pack()
self.lbl3 = tk.Label(self, text='Bye!')
self.lbl3.pack()
...
def on_add(self):
try:
self.frm.pack_info()
except:
self.frm.pack(before=self.lbl3, fill=tk.BOTH, expand=True)
tk.Label(self.frm, text='My name is Foo').pack()
def on_expose(self, event):
w = event.widget
if not w.children:
w.pack_forget()
Tested with Python: 3.5 - 'TclVersion': 8.6 'TkVersion': 8.6
回答2:
When you destroy the last widget within a frame, the frame size is no longer managed by pack
or grid
. Therefore, neither pack
nor grid
knows it is supposed to shrink the frame.
A simple workaround is to add a small 1 pixel by 1 pixel window in the frame so that pack
still thinks it is responsible for the size of the frame.
Here's an example based off of the code in the question:
import tkinter as tk
root = tk.Tk()
lbl1 = tk.Label(root, text='Hello!')
lbl1.pack()
frm = tk.Frame(root, bg='black')
frm.pack()
lbl3 = tk.Label(root, text='Bye!')
lbl3.pack()
lbl2 = tk.Label(frm, text='My name is Foo')
lbl2.pack()
def delete_the_label():
lbl2.destroy()
if len(frm.winfo_children()) == 0:
tmp = tk.Frame(frm, width=1, height=1, borderwidth=0, highlightthickness=0)
tmp.pack()
root.update_idletasks()
tmp.destroy()
button = tk.Button(root, text="Delete the label", command=delete_the_label)
button.pack()
root.mainloop()
来源:https://stackoverflow.com/questions/59584847/how-to-shrink-a-frame-in-tkinter-after-removing-contents