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