I have a frame that holds buttons and it's packed in a LabelFrame with grid geometry manager.
When I remove this frame with grid_forget, the LabelFrame still has the same size.
With other words
it doesn't shrink.
Here is the code, when you press the button all the buttons are removed
but the size remains.
I expected that the grid geometry manager deals automatically with resizing when widgets are removed.
import tkinter as tk
class Collapsible():
def __init__(self, master):
self.master = master
self.dynamic_widgets()
self.fill_lb()
def dynamic_widgets(self):
"""create widgets"""
#frame that holds labelwidgets
self.fr_collapse = tk.Frame(self.master, bg="orange")
#title for label frame----------------------------------------------------------------
self.bt_title = tk.Button(self.fr_collapse, text="o",
highlightthickness = 0, bd = 0,
relief="flat", bg="orange", fg="red")
self.bt_title.grid(row=0, column=0)
self.label_title = tk.Label(self.fr_collapse, text="Name", bg="orange")
self.label_title.grid(row=0, column=1)
#-------------------------------------------------------------------------------------
self.label_frame = tk.LabelFrame(self.master,
bg="orange", labelwidget=self.fr_collapse)
self.label_frame.grid(sticky="wesn", ipady=(10))
#frame for buttons
self.frame_forget = tk.Frame(self.label_frame, bg="orange")
self.frame_forget.grid()
#set command
self.bt_title.configure(command=lambda x=self.frame_forget, y=self.bt_title: self.hide(x, y))
def fill_lb(self):
"fill label frame with dumb buttons"""
b = tk.Button(self.frame_forget, text="Example button 1", bg="orange", relief="flat")
b.grid()
b2 = tk.Button(self.frame_forget, text="Example button 2", bg="orange", relief="flat")
b2.grid()
def hide(self, frame, button):
"""switch value: hide frame based on text configuration"""
bt_text = button.configure("text")
if bt_text[-1] == "o":
frame.grid_remove()
button.configure(text="-")
else:
frame.grid()
button.configure(text="o")
if __name__ == "__main__":
root = tk.Tk()
col = Collapsible(root)
root.configure(bg="orange")
root.mainloop()
What I tried so far:
I thought that maybe I need to grid frame that holds buttons after deleting them. Does'n work because this will
grid my hidden buttons again which is logically.
I thought that maybe I need to grid the LableFrame again. No changes in size either
I thought that maybe I should put a dumb frame like a placeholder with minimal width and height values.
and grid it as child in my frame_forget frame with the hope that it will shrink. But still nothing.
None of those thoughts brought me a solution and the question remains
When I run my script it looks like this:
Then when I press flat button in the left corner 'o', I get this:
I wish it would collapse like this one:
Related
Python beginner. I placed a scrollbar widget in window and that works, but no matter what I do I can't get the scrollbox widget to change size. Could go with a larger scrollbox or for it to resize when the window resizes, but can't figure out how to force either to happen. Tried lots of different solutions, but feels like the grid and canvas are defaulting to a size and can't figure out how to change that. Help would be appreciated. Code is below:
import tkinter as tk
from tkinter import ttk
import os
import subprocess
class Scrollable(tk.Frame):
"""
Make a frame scrollable with scrollbar on the right.
After adding or removing widgets to the scrollable frame,
call the update() method to refresh the scrollable area.
"""
def __init__(self, frame, width=16):
scrollbar = tk.Scrollbar(frame, width=width)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y, expand=True)
self.canvas = tk.Canvas(frame, yscrollcommand=scrollbar.set)
self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.config(command=self.canvas.yview)
self.canvas.bind('<Configure>', self.__fill_canvas)
# base class initialization
tk.Frame.__init__(self, frame)
# assign this obj (the inner frame) to the windows item of the canvas
self.windows_item = self.canvas.create_window(0,0, window=self, anchor=tk.NW)
def __fill_canvas(self, event):
"Enlarge the windows item to the canvas width"
canvas_width = event.width
self.canvas.itemconfig(self.windows_item, width = canvas_width)
def update(self):
"Update the canvas and the scrollregion"
self.update_idletasks()
self.canvas.config(scrollregion=self.canvas.bbox(self.windows_item))
root = tk.Tk()
root.title("application")
root.geometry('750x800')
dbEnvs = ['a','b']
x = 1
header = ttk.Frame(root)
body = ttk.Frame(root)
footer = ttk.Frame(root)
header.pack(side = "top")
body.pack()
footer.pack(side = "top")
#setup Environment selection
envLabel = tk.Label(header, text="Environment:")
envLabel.grid(row=0,column=0,sticky='nw')
dbselection = tk.StringVar()
scrollable_body = Scrollable(body, width=20)
x = 1
for row in range(50):
checkboxVar = tk.IntVar()
checkbox = ttk.Checkbutton(scrollable_body, text=row, variable=checkboxVar)
checkbox.var = checkboxVar # SAVE VARIABLE
checkbox.grid(row=x, column=1, sticky='w')
x += 1
scrollable_body.update()
#setup buttons on the bottom
pullBtn = tk.Button(footer, text='Pull')
pullBtn.grid(row=x, column=2, sticky='ew')
buildBtn = tk.Button(footer, text='Build')
buildBtn.grid(row=x, column=3, sticky='ew')
compBtn = tk.Button(footer, text='Compare')
compBtn.grid(row=x, column=4, sticky='ew')
root.mainloop()
have tried anchoring, changing the window base size and multiple other things (8 or 19 different items, plus reading lots of posts), but they normally involve packing and since I used grids that normally and ends with more frustration and nothing changed.
If you want the whole scrollbox to expand to fill the body frame, you must instruct pack to do that using the expand and fill options:
body.pack(side="top", fill="both", expand=True)
Another problem is that you're setting expand to True for the scrollbar. That's probably not something you want to do since it means the skinny scrollbar will be allocated more space than is needed. So, remove that attribute or set it to False.
scrollbar.pack(side=tk.RIGHT, fill=tk.Y, expand=False)
tip: when debugging layout problems, the problems are easier to visualize when you temporarily give each widget a unique color. For example, set the canvas to one color, body to another, the instance of Scrollable to another, etc. This will let you see which parts are visible, which are growing or shrinking, which are inside the borders of others, etc.
I'm trying to center text in the frame using LabelFrame function. My code is below.
import tkinter as tk
window = tk.Tk()
for i in range(9):
for j in range(9):
frame = tk.LabelFrame(
master=window,
relief=tk.RAISED,
borderwidth=5,
width=50,
height=50,
text=i+j,
labelanchor = 'n'
)
frame.grid(row=i, column=j)
window.geometry("500x500")
window.mainloop()
Argument labelanchor that specify position gives only options on the edge of the frame. Is there any simple way to center text inside of the frame using LabelFrame?
Is there any simple way to center text inside of the frame using LabelFrame?
The text of the labelframe can only appear along the edges of the frame. If you wish for text to appear inside the frame, you must create a Label and add it to the frame.
Using a frame and a label
If you wish to put other widgets in the frame and don't want this text to interfere with the other widgets, this is a perfect opportunity to use place.
The following example adds the label "Hello, world" to appear in the center of the widget. A button is placed in the frame just to show that its placement is not affected by the label, or vise versa. The screenshots show what the frame looks like naturally and when the window is resized.
import tkinter as tk
root = tk.Tk()
lf = tk.Frame(root, bd=2, relief="groove")
lf_label = tk.Label(lf, text="Hello, world")
lf_label.place(relx=.5, rely=.5, anchor="c")
lf.pack(padx=20, pady=20, fill="both", expand=True)
b = tk.Button(lf, text="Click me")
b.pack(padx=10, pady=10)
root.mainloop()
Using only a label
You can add widgets inside any other widget. So, instead of a frame you could just use a label. By default, the text will be centered, and just like with the previous example it won't affect the layout of other widgets.
I haven't included screenshots because the results are identical to the previous example.
import tkinter as tk
root = tk.Tk()
lf = tk.Label(text="Hello, world", bd=2, relief="groove")
lf.pack(padx=20, pady=20, fill="both", expand=True)
b = tk.Button(lf, text="Click me")
b.grid(row=0, column=0)
root.mainloop()
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 past pack().
Re-run frm.pack(): but this ruins the order of my main widgets.
.geometry(''): This only works on the root window - doesn't exist for Frames.
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.
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()
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
I am working on this table in tkinter made from a bunch of treeveiw widgets. The idea is to get a table where I can add lines, select lines and edit them. In the code below you can add lines to the table by pushing the button. I now want to control the height of each row by configuring the style. However, when I use style the alignment of the treeview widgets is messed up, see attached picture. Any suggestions how to fix this?
EDIT: The problem is the added space between the widgets.
The code for the table is:
from tkinter import *
from tkinter import ttk
class MyApp(Tk):
def __init__(self):
super(MyApp, self).__init__()
self.geometry('950x500+100+100')
self.NewTree = []
label = Label(self,text='Table with some data', font=("Arial Bold", 25))
label.pack()
self.addLine()
master_frame = Frame(self, bd=3, relief=RIDGE)
master_frame.pack(side=BOTTOM)
# Create a frame for the canvas and scrollbar(s).
frame2 = Frame(master_frame)
frame2.pack(side = BOTTOM)
# Add a canvas in that frame.
self.canvas = Canvas(frame2)
self.canvas.grid(row=0, column=0)
# Create a vertical scrollbar linked to the canvas.
vsbar = Scrollbar(frame2, orient=VERTICAL, command=self.canvas.yview)
vsbar.grid(row=0, column=1, sticky=NS)
self.canvas.configure(yscrollcommand=vsbar.set)
# Create a frame on the canvas to contain the buttons.
self.table_frame = Frame(self.canvas)
# Create canvas window to hold the buttons_frame.
self.canvas.create_window((0,0), window=self.table_frame, anchor=NW)
def addLine(self):
#Make button for adding step
bt = Button(self,text='Add Line',command=lambda: self.addLineMethod())
bt.config(width=9, height=1)
bt.pack()
def addLineMethod(self):
lineNumber = int(len(self.NewTree)/5)
for index in range(5):
s = ttk.Style()
s.configure('MyStyle.Treeview', rowheight=25)
self.NewTree.append(ttk.Treeview(self.table_frame, height=1,show="tree",columns=("0"),style='MyStyle.Treeview'))
#Works fine when using this line instead of those above
#self.NewTree.append(ttk.Treeview(self.table_frame, height=1,show="tree",columns=("0")))
self.NewTree[index+5*lineNumber].grid(row=lineNumber, column=index+1)
self.NewTree[index+5*lineNumber]['show'] = ''
item = str(index+5*lineNumber)
self.NewTree[index+5*lineNumber].column("0", width=180, anchor="w")
self.NewTree[index+5*lineNumber].insert("", "end",item,text=item,values=('"Text text text"'))
self.table_frame.update_idletasks() # Needed to make bbox info available.
bbox = self.canvas.bbox(ALL) # Get bounding box of canvas with Buttons.
# Define the scrollable region as entire canvas with only the desired
# number of rows and columns displayed.
self.canvas.configure(scrollregion=bbox, width=925, height=200)
app = MyApp()
app.mainloop()
Her is a picture of the table with some lines.
Put the style configuration in the __init__() function and the effect will go away. I'm not clear as to why this works.
def __init__(self):
...
s = ttk.Style()
s.configure('MyStyle.Treeview', rowheight=20)
I want to get a table, but when I realized, that there's no table in python, I decided to fix it with two listboxes.
The point is, that I don't want a border between them.
So my Question is: How to remove the border from the Tk Listbox in Python?
Even if it'll become white I had a solution...
You want to set the borderwidth to zero, but you also want to set highlightthickness to zero. Highlightthickness represents a rectangle that surrounds the whole widget when it has keyboard focus. Finally, when you use pack or grid, make sure you don't add any padding between them.
If you want to complete the illusion that the two widgets are one, put them in a frame and give the frame a borderwidth and relief.
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent, borderwidth=1, relief="sunken")
lb1 = tk.Listbox(self, borderwidth=0, highlightthickness=0)
lb2 = tk.Listbox(self, borderwidth=0, highlightthickness=0)
lb1.pack(side="left", fill="both", expand=True)
lb2.pack(side="left", fill="both", expand=True)
lb1.insert(0, "left")
lb2.insert(0, "right")
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True, padx=8, pady=8)
root.mainloop()
I think the best you can achieve would be this:
import tkinter as tk
root = tk.Tk()
wrapper = tk.Frame(root, bd=2, relief="sunken")
L1 = tk.Listbox(wrapper, bd=0)
L2 = tk.Listbox(wrapper, bd=0)
L1.grid(column = 1, row = 1)
L2.grid(column = 2, row = 1)
wrapper.pack()
root.mainloop()
note setting the border of each listbox to 0, (bd=0) and to give the overall widget a similar look to the original listbox i've wrapped it in a frame and given that the same border style as the default listbox.
also worth nothing that you need to get the bindings right to make them scroll as expected, just binding to the scroll wheel and scroll bar is insufficient as the lists can be moved with the arrow keys when an item is highlighted, like in the second example on this page:
scrolling multiple listboxes together
by Santiclause
Speicfy borderwidth as 0 when you create a listbox.
For example:
from Tkinter import * # from tkinter import * (Python 3.x)
root = Tk()
lb = Listbox(borderwidth=0) # <---
lb.pack()
root.mainloop()