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

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()

Related

How do I stop text from moving when I resize the screen in tkinter

I am creating a registration form, and I have coded labels to show next to the text box for someone's username and password. This is the code I am using to place the text boxes and labels:
usernamebx.place(relx=0.5, rely=0.5, width=225, height=25,
anchor= CENTER)
userbx_label.place(relx=0.1, rely=0.5, anchor=CENTER)
passwbx.place(relx=0.5, rely=0.6, width=225, height=25, anchor = CENTER)
passwbx_label.place(relx=0.1, rely=0.6, anchor=CENTER)
The code for usernamebx and passwbx means that the text boxes don't move when I resize the tkinter window. However, I have done the same with the labels for each but it doesn't work. Any help?
The code for usernamebx and passwbx means that the text boxes don't move when I resize the tkinter window.
Actually, they do move! If you put a widget at relx 0.5 in a window that is 200 pixels wide, that means the center of the widget will be 100 pixels from the left edge of the window. When you grow the window to 400 pixels wide, the center of the widget now will be 200 pixels from the left edge. It moved 100 pixels. You don't see it because it's symmetrical so it stays in the center.
The same happens with a widget that is at 0.1. on a 200 pixel wide window it's going to be 20 pixels from the left edge. When you make the window 400 pixels widget it's going to be 40 pixels from the edge.
This is the nature of relative coordinates -- they will always change when the window is resized.
It's hard to see what your actual requirement is, though I'm guessing you want the username label+entry and password label+entry to be co-aligned in the center of the window.
If that's the case, one simple solution is to put those widgets in a frame. Use grid internally since it appears that you are in fact creating a grid. Then, you can place the frame in the window as a separate step.
Here's an example of the technique. For illustrative purposes the frame has a visible border, but that's not strictly necessary. You can remove the border to make it blend in with the background.
This example uses place to put the frame in the center, though you can also use pack.
import tkinter as tk
root = tk.Tk()
root.geometry("400x400")
inner_frame = tk.Frame(root, bd=2, relief="groove")
usernamebx = tk.Entry(inner_frame)
userbx_label = tk.Label(inner_frame, text="Username:")
passwbx = tk.Entry(inner_frame)
passwbx_label = tk.Label(inner_frame, text="Password:")
inner_frame.grid_columnconfigure(0, weight=1)
userbx_label.grid(row=0, column=0, sticky="e")
usernamebx.grid(row=0,column=1, sticky="ew")
passwbx_label.grid(row=1, column=0, sticky="e")
passwbx.grid(row=1, column=1, sticky="ew")
inner_frame.place(relx=.5, rely=.5, anchor="center")
root.mainloop()
If you want to use pack rather than place, have the packer expand the allocated space to be the whole window, and the frame will automatically be centered. In this case the window will shrink to fit the frame plus the padding.
inner_frame.pack(side="top", expand=True, padx=10, pady=10)

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

size responsive widgets in a tkinter canvas

When I configure a tkinter window's rows and columns with weight=1 and then place a canvas over it spanning all the rows and the columns ,the canvas becomes size responsive:
win=Tk()
win.geometry("1200x675")
rows=0
while rows<200:
win.rowconfigure(rows,weight=1)
win.columnconfigure(rows,weight=1)
rows+=1
canvas=Canvas(win,bg='white',width=1200,height=675)
canvas.grid(sticky='nesw',rowspan=200,columnspan=200)
But the widgets placed inside the canvas at specific coordinate remains of the same size and at the same position,for example a button:
but=Button(text="Calculate")
canvas.create_window(100,100,window=but)
So,please can anyone help as to how to make the widgets also size responsive.
You don't need to make that many grid cells. One cell with
weight=1 will expand the canvas with size change.
There is no general method to scale contained widgets, you'll
have to write that yourself.
Hook window size change with <Configure> and then loop through
widgets on canvas form canvas.find_withtag('all'). See example below:
from tkinter import *
win = Tk()
win.geometry("300x200+800+50")
win.rowconfigure(0, weight=1)
win.columnconfigure(0, weight=1)
canvas=Canvas(win, bg='white')
canvas.grid(sticky='nesw')
def size(event):
width, heigth = canvas.winfo_width(), canvas.winfo_height()
print('Canvas size:', width, 'x', heigth)
items = canvas.find_withtag('all') # Find all widgets on canvas
for item in items:
pass # Change widget size here, one widget at a time
win.bind('<Configure>', size) # Hook window size changes
win.mainloop()

tkinter - create label widget (containing text) with size defined by pixel dimensions

I am trying to make a tkinter label containing text with width and height defined by pixel dimensions. It is made clear that a label widget containing text's size is defined in terms of its text size in the docs:
If the label displays text, the size is given in text units. If the label displays an image, the size is given in pixels (or screen units). If the size is set to 0, or omitted, it is calculated based on the label contents.
I have tried using this information to achieve what I am trying to do. I want to create labels of fixed width 700 and variable height as I am creating a timetable application. Here's an example of what I have tried so far:
import tkinter as tk
root = tk.Tk()
root.geometry("800x600")
height = 20
label = tk.Label(root,width=700,bg="white",text="test",borderwidth=0,font=("Calibri",height))
label.place(x=10,y=10)
root.mainloop()
This almost achieves what I want in terms of height, but I would expect the height to be 1 pixel when height = 1, but it is actually 5 pixels (I've got very good eyesight!). As for width, its completely off the screen as its 700 times the width of a character.
Using individual frames as each "label" and then creating label widgets as children of these frames does not work either, as the frame just gets resized to fit the label. My question is: is there a way to create a text-containing label sized by pixel dimensions?
I have found a solution in my case. I used a frame, disabled its pack_propagate and made a label the child of this frame. I hope others will find it useful. This question helped me a lot.
import tkinter as tk
root = tk.Tk()
root.geometry("800x600")
height = 20
label_frame = tk.Frame(root,width=700,height=height,bg="white")
label_frame.pack_propagate(0) # Stops child widgets of label_frame from resizing it
tk.Label(label_frame,bg="white",fg="black",text="test",font=("Calibri",15)).pack()
label_frame.place(x=10,y=10)
root.mainloop()
doing a timetable is something that lends itself to using the grid packing method, which has a grid_columnconfigure and a grid_rowconfigure method which can be used to set the minimum and maximum size of a row or column, and the rate at which they expand:
import tkinter as tk
root = tk.Tk()
tk.Label(root, text="Test").grid(column=1, row=1, sticky="nesw")
root.grid_columnconfigure(1, minsize=700)
root.mainloop()

Tkinter Frame Resize one axis

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()

Categories