Tkinter Frame Resize one axis - python

I have a tkinter frame that I want to automatically resize on the Y axis but remain a constant width as I add label widgets to it.
To keep the X-size constant I am using grid_propogate(False) but that keeps the whole thing a constant size.
How can I set the frame to resize in this manner?
Thanks

Ok, figured it out
Made a larger frame that encompassed the space that my frame could fill if maxed out and used pack(fill=X) and pack_propagate(False) to make the inner frame conform to the X dimension of the outer frame while not changing it. I then could add lines to the innerframe as needed with it maintaining it's X size:
OuterFrame = Frame(root, height=500, width=400)
InnerFrame = Frame(OuterFrame, borderwidth=3, relief=RAISED)
InnerFrame.pack(side=TOP, fill=X)
# stuff that goes in the y-resizing InnerFrame
OuterFrame.pack_propogate(False)
OuterFrame.pack()

This seems like a very ugly solution to me, (hopefully someone will come along with something better):
import Tkinter as tk
root=tk.Tk()
f=tk.Frame(root,width=100,height=300)
f.grid_propagate(False)
f.grid(row=0,column=0)
def resize(evt):
f.update_idletasks()
height=f.winfo_reqheight()
f.grid_propagate(False)
if(evt.width!=100) or (evt.height!=height):
f.configure(width=100,height=height)
print "HERE", evt.width,height
f.bind('<Configure>',resize)
#Just some stupid (ugly) code to update the size of the widget at runtime.
def add_label():
f.grid_propagate(True)
lbl=tk.Label(f,text=' %d Hello!'%(add_label.row))
lbl.grid(column=0,row=add_label.row)
add_label.row+=1
add_label.row=0
b=tk.Button(root,text="Add label",command=add_label)
b.grid(row=1,column=0)
root.mainloop()

Related

Tkinter widget displacement with pack()

I want to pack two buttons (left and right) and a label (in the middle) in a frame. I want the label to fill the remaining space on the frame to both sides, but the widgets get displaced vertically with this code. What's the best way to do this? The widgets don't necessarily have to be packed on a frame but I want them to align horizontally while the text size of the label can change, but the buttons need to stay in place on the far left and right side. enter image description here
import tkinter as tk
root = tk.Tk()
root.geometry('600x800')
root.configure(background='#141414')
frm = tk.Frame(root)
frm.place(x=0, y=0, width=300, height=30)
btn1 = tk.Button(frm, text='button1')
lbl = tk.Label(frm, text='Lalalalalala')
btn2 = tk.Button(frm, text='button2')
btn1.pack(side='left')
lbl.pack(fill='x')
btn2.pack(side='right')
tk.mainloop()
You can solve this problem a couple of ways. One solution is to pack the label to one side or the other rather than the top.
btn1.pack(side='left')
lbl.pack(side='left', fill='x', expand=True)
btn2.pack(side='right')
Another is to pack the buttons first, and then pack the label. With pack the order matters.
btn1.pack(side='left')
btn2.pack(side='right')
lbl.pack(fill='x', expand=True)
For an illustrated explanation of how pack works see this answer to the question Tkinter pack method confusion

Python -> Tkinker -> TopLevel - auto size but with minimum width

I have a TopLevel(elements with Tkinker libiary) that has dynamically generated and added new items. The window adapts perfectly to the elements in height and width and I do not want to set a constant. However, the width is always a bit too narrow, so the elements fit, but the window looks bad.
Is it possible to set a minimum width or add permanent margins on the sides?
(Python 3)
The simplest solution is to put all of your widgets in a frame, then the frame inside the toplevel with padding around the edges.
The following example creates several labels inside the frame, and the frame is placed in the root window with a margin of 100 pixels all around it.
This example puts everything in the root window, but the same technique works with a Toplevel or any other widget.
import tkinter as tk
root = tk.Tk()
frame = tk.Frame(root, bd=1, relief="raised")
frame.pack(padx=100, pady=100)
for i in range(20):
label = tk.Label(frame, text=f"Widget #{i+1}")
label.pack()
root.mainloop()

Tkinter scrollbar isn't reacting to the size of my canvas

I'm trying to implement a scrollbar that will scroll through a frame in a canvas window. The canvas, frame, and frame elements are all functioning well as far as I can see. I have no issues with adding or removing elements from the frame, and they show up as I want them to. The only issue is with the scrollbar.
The scrollbar doesn't work at all. It shows up exactly where it should, but it doesn't react at all to the canvas or frame elements. If I resize the window and squish down the canvas, the scrollbar does nothing — it just stays empty as though there's nothing for it to scroll through.
I recognize there are a million other questions about scrollbars in canvases, but I've carefully read through as many of them as I could and didn't find any sort of solution to my problem. So with that, I ask: any idea where I'm going wrong?
Any feedback or suggestions for possible fixes would be greatly appreciated.
lbl_frame = Canvas(menu, bg=root_bg, width=(menu.winfo_width() - 35), height=canvas_size, highlightthickness=0)
f = Frame(lbl_frame, bg=root_bg) # Frame that goes in the canvas
scroll = Scrollbar(menu, bg=root_bg, orient='vertical', width=17) # Scrollbar
scroll.grid(row=3, column=2, sticky='ns')
scroll.config(command=lbl_frame.yview) # Scrollbar's command reads information about the height of the canvas
lbl_frame.create_window(0, 0, window=f, anchor="nw") # Creating a canvas window for the frame
lbl_frame.config(yscrollcommand=scroll.set)
def canvas_resize(event):
global min_size # Currently set to 95
canvas_size = menu.winfo_height() - 170 # Sum of all other elements
if canvas_size < min_size:
canvas_size = min_size # Canvas size must be >= 100
lbl_frame.configure(height=canvas_size) # Scale canvas size with window
lbl_frame.configure(scrollregion=lbl_frame.bbox("all"))
# Set scrollregion to the total region of all elements in the frame
menu.bind("<Configure>", lambda e: canvas_resize(e))
Note that elements are added to the frame shortly after this. Because the process I add them by is very long and contains a lot of stuff that isn't relevant to my problem, plus I don't have any issues with adding these elements, I've decided not to include that part.
I figured it out. It was with how I was adding the elements. I added them to the canvas, not the frame. So the frame was left empty.

Python Tkinter Grid Manager is Ignoring Sizing of Previous Items and Wrapping Frames

I was thinking the Grid manager was pretty straightforward, but I'm stuck. I basically have three frames, starting from the main window and each frame using the previous as its master.
I'm trying to create 12 blocks inside the second frame, but when I try to create the blocks it wraps around its master frame and adds extra space at the end.
I also don't understand why I can change the hScroll width to also change the size of its master frame, example being set to 1000, but changing the width of its master frame, even increasing size does nothing.
I'm trying to learn how to use Tkinter and am looking for where I am messing up conceptually here, not so much just a code fix.
# Main frame
worksheetFrame = tk.Frame(tkRoot, width=2000, height=800, background='BLUE')
worksheetFrame.grid(row=0, column=0, padx=(100, 0), pady=(60, 0))
# Horizontal scrollbar
hScroll = tk.Frame(worksheetFrame, width=1000, height=20, background='RED')
hScroll.grid(row=1, column=0)
# Month frames
for i in range(12):
monthFrame = tk.Frame(worksheetFrame, width=200, height=400, background='YELLOW')
monthFrame.config(borderwidth = 2, relief=tk.GROOVE)
monthFrame.grid(row=0, column=i, sticky=tk.W)
# Vertical scrollbars
vScroll = tk.Frame(monthFrame, width=20, height=400, background='GREEN')
vScroll.grid(row=0, column=i)
I'm trying to create 12 blocks inside the second frame, but when I try to create the blocks it wraps around its master frame and adds extra space at the end.
Nothing is wrapping. You have one green frame in column 0, and then the other 11 frames are in columns 1-11.
I think the thing you are missing is that you're trying to put one of the narrow green frames in column 0 of worksheetFrame, which is the same column that has the horizontal scrollbar. You've forced the scrollbar to be 1000 pixels wide which causes column 0 to be 1000 pixels wide. When you put a 20 pixel wide frame inside a 1000 pixel column, there's going to be a lot of unused spaced, which is what you're seeing.
I also don't understand why I can change the hScroll width to also change the size of its master frame, example being set to 1000, but changing the width of its master frame, even increasing size does nothing.
That is again due to the fact that grid and pack by default grow or shrink a container to fit its contents. Since you add a child to worksheetFrame using grid, that frame will ignore its requested width and shrink to fit its children.
If you want a single horizontal frame that spans the width of worksheetFrame, you need to use columnspan so that it spans all of the columns:
hScroll.grid(row=1, column=0, columnspan=12)

Why is inner padding only applied on the right?

MCVE
import Tkinter as tk
import ttk
root = tk.Tk()
root.minsize(200, 100)
inner_frame = ttk.LabelFrame(root, text='inner_frame')
inner_frame.grid(row=0, column=0)
button = ttk.Button(inner_frame, text='this is a button')
button.grid(row=0, column=0)
# this does not work as expected
inner_frame.grid_configure(ipadx=20)
root.mainloop()
Output
Question
Why is the inner padding for inner_frame only applied on the right? How do I apply it on both sides?
Interestingly, and I don't know if this is a bug, your problem is solved by making the column containing the button expand past its minimum horizontal size.
The minimum horizontal size of column 0 in the inner frame is the horizontal size of its content, which is the button.
If you add a inner_frame.columnconfigure(0, weight=1), then the internal padding works as expected:
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
inner_frame = ttk.LabelFrame(root_frame, text="inner_frame")
inner_frame.grid(row=0, column=0)
inner_frame.columnconfigure(0, weight=1)
button = ttk.Button(inner_frame, text="this is a button")
button.grid(row=0, column=0)
# this works expected:
inner_frame.grid_configure(ipadx=20)
root.mainloop()
Note that grid_configure changes column 0 by default, so here we're adding internal padding to the cells in column 0.
I do not know exactly why this happens. It is not mentioned in the Tcl grid docs (http://tcl.tk/man/tcl8.5/TkCmd/grid.htm#M13).
Interestingly, this link does mention some special case around size of the frame using internal padding (low down the page, in the "Internal Padding" section): https://tkdocs.com/tutorial/grid.html
The difference can be subtle. Let's say you have a frame that's 20x20, and specify normal (external) padding of 5 pixels on each side. The frame will request a 20x20 rectangle (its natural size) from the geometry manager. Normally, that's what it will be granted, so it'll get a 20x20 rectangle for the frame, surrounded by a 5-pixel border.
With internal padding, the geometry manager will effectively add the extra padding to the widget when figuring out its natural size, as if the widget has requested a 30x30 rectangle. If the frame is centered, or attached to a single side or corner (using "sticky"), you'll end up with a 20x20 frame with extra space around it. If however the frame is set to stretch (i.e. a "sticky" value of "we", "ns", or "nwes") it will fill the extra space, resulting in a 30x30 frame, with no border.
But it is confusingly written, and I can't fully understand what they mean (or even if they are correct in modern Python). If someone knows what that paragraph above means, by all means comment below and let us know!
Adding internal padding to the frame as a whole
You can add internal padding by using styles or passing it directly as an argument to the frame's constructor:
ttk.LabelFrame(root, text="inner", padding=(20, 0))
The padding value there can take either:
One value, for padding on all sides
Two values, for x and y (in that order)
Four values, for padding starting at the left and going clockwise.
In your case, ipadx isn't failing. It's working as designed, it's just that the way it works isn't very intuitive, especially when you apply it to a frame.
To better visualize what is happening, lets apply the ipadx value to the button rather than the frame. That way we can see the padding relative to the label on the button.
For example, add two buttons instead of one. Give one an ipadx of 20, and give the other an ipadx of 0.
button1 = ttk.Button(inner_frame, text='this is a button')
button2 = ttk.Button(inner_frame, text='this is a button')
button1.grid(row=0, column=0, ipadx=20)
button2.grid(row=1, column=0, ipadx=0)
Notice that the button with ipadx=20 is wider, and the extra space is inside the button rather than as a margin surrounding the button.
The same thing is happening with inner_frame: when it is added to its parent, the extra space is being added inside the frame, effectively making inner_frame wider. You can't see it because it's added to the empty space already inside the frame.
Here's the important part: if you add a widget to inner_frame, grid doesn't know anything about the ipadx values applied to inner_frame -- that ipadx option only applies to inner_frame and its parent, not its children. At the point of adding widgets inside of inner_frame, grid only knows that inner_frame is X pixels wide.
To illustrate, we can add a label to the button, similar to how your original code adds a button to the frame. (note: we'll turn geometry propagation off so that it doesn't cause the button to shrink).
button1.grid_propagate(False)
label = ttk.Label(button1, text="x")
label.grid(row=0, column=0)
You should see a window that looks something like this:
See how the "x" is at the far left edge of the button? That is because it doesn't know anything about the ipadx value applied to its parent. All it knows is that the button is X pixels widget, and that it's supposed to be on the left edge of the button.
That is what's happening with you original frame and button - the button is being added inside the frame, making use of all of the space inside the frame.
So far I have figured out that using padding=... in the LabelFrame constructor produces the correct result.
If you delete the line
inner_frame.grid_configure(ipadx=20)
and use
inner_frame = ttk.LabelFrame(root, text='inner_frame', padding=[20, 0])
or alternatively
inner_frame['padding'] = [20, 0]
the result looks like this:
I have no clue why using ipadx through grid_configure does not work as expected.

Categories