I'm making a GUI and I'm stuck trying to resize a listbox. The listbox is supposed to expand to fill the frame but instead, the frame shrinks to fit the listbox.
Thanks, in advance, for any help.
I have tried many variations of the code, but none seem to work, so I simplified the code (it still does't work) to put it on here.
import tkinter as tk
w = tk.Tk() # New window
f = tk.Frame(w, width=300, height=500, bg='red') # New frame with specific size
f.grid_propagate(0)
f.grid(row=0, column=0)
lb = tk.Listbox(f, bg='blue') # New listbox
lb.pack(fill=tk.BOTH, expand=True)
I ran these sequentially in IDLE and the frame appears (in red) at the correct size, however, when I pack the listbox, the whole window shrinks to the size of the listbox (and turns completely blue, which is expected).
In my experience, turning off geometry propagation is almost never the right solution. With a couple of decades of using tk and tkinter I've done that only two or three times for very specific edge cases.
Tkinter is very good at making the widget the best size based on the set of widgets you're using. Turning off propagation means you are responsible for doing all calculations to get the window to look right, and your calculations may not be correct when your program runs on a machine with different fonts or a different resolution. Tkinter can handle all of that for you.
Unfortunately, with such a small code example and without knowing your end goal it's hard to solve your layout problems. If your goal is to have a window that is 300x500, the best solution is to make the window that size and then have the frame fill the window, which is easier to do with pack than grid:
import tkinter as tk
w = tk.Tk() # New window
w.geometry("300x500")
f = tk.Frame(w, bg='red') # New frame with specific size
f.pack(fill="both", expand=True)
lb = tk.Listbox(f, bg='blue') # New listbox
lb.pack(fill=tk.BOTH, expand=True)
w.mainloop()
Thank you so much to Tls Chris for your answer and explanation. What I didn't realize is that grid_propagate() and pack_propagate() also affect a widget's children.
Therefore, with the help of Tls Chris, I have fixed my code and it now expands the listbox and doesn't shrink the frame.
Fixed code:
import tkinter as tk
w = tk.Tk() # New window
f = tk.Frame(w, width=300, height=500, bg='red') # New frame
f.grid_propagate(False) # Stopping things (that use grid) resizing the frame
f.pack_propagate(False) # Stopping things (that use pack) resizing the frame
f.grid(row=0, column=0)
lb = tk.Listbox(f, bg='blue') # New listbox
lb.pack(fill=tk.BOTH, expand=True) # Packing the listbox and making it expand and fill the frame
Edit: I think you actually want the listbox to expand to fit the frame. Amended the code to do that as an option
I haven't used pack much but I suspect that grid_propagate changes the behaviour of the grid geometry manager but not of the pack manager.
The below lets app() run with or without propagate set. It uses the grid geometry manager throughout.
import tkinter as tk
def app(propagate = False, expand = False ):
w = tk.Tk() # New window
tk.Label( w, text = 'Propagate: {} \nExpand: {}'.format(propagate, expand) ).grid()
f = tk.Frame(w, width=300, height=500, bg='red') # New frame with specific size
f.grid_propagate(propagate)
f.grid( row=1, column=0 )
lb = tk.Listbox(f, bg='blue') # New listbox
if expand:
f.columnconfigure(0, weight = 1 )
f.rowconfigure(0, weight = 1 )
# lb.pack(fill=tk.BOTH, expand=True)
lb.grid( row=0, column=0, sticky = 'nsew' )
# My guess is that grid_propagate has changed the behaviour of grid, not of pack.
lb.insert(tk.END, 'Test 1', 'Test 2', 'Test 3')
w.mainloop()
This changes the listbox geometry manager to grid. Run app() as below.
app(True, True) # propagate and Expand
app(False, True) # no propagate but expand
app(True, False) # propagate without expand
app() # no propagate or expand
Related
I created a main root with two frames.
-One frame is for program toolbar.
-Other frame is for canvas where data will be displayed and a scrollbar widget.
-Inside of the canvas is a third smaller frame which will be used for scrolling trough data.
However, when I try to define new widgets and place them on that third smaller frame, nothing happens. I'm defining new widgets inside of a function call of a button command. I have also tried declaring everything as global variables but without luck.
Hint: I tried placing the code from the function to the top level of the code and it works. Also, if I try to mount these widgets on the toolbar frame it also works. It seems that the only thing I can't do is to mount these new widgets on the small frame that is inside the canvas.
I used a simple for loop to create labels just for testing.
Could anyone tell what I am doing wrong?
from tkinter import *
from tkinter import ttk
#Creating main window
root = Tk()
root.resizable(width=False, height=False)
#Defining Background
toolbar = Frame(root, width=613, height=114)
toolbar.grid(row=0, column=0)
background_frame = Frame(root, width=615, height=560)
background_frame.grid(row=1, column=0)
background = Canvas(background_frame, width=615, height=560)
background.pack(side=LEFT, fill=BOTH, expand=1)
scroll_bar = ttk.Scrollbar(background_frame, orient=VERTICAL, command=background.yview)
scroll_bar.pack(side=RIGHT, fill=Y)
background.configure(yscrollcommand=scroll_bar.set)
background.bind('<Configure>', lambda e:background.configure(scrollregion = background.bbox('all')))
second_frame = Frame(background)
background.create_window(150,100, window=second_frame, anchor='nw')
def confirm1():
for x in range(100):
Label(second_frame, text = x ).grid(row=x, column=1)
show_labels = Button(toolbar, text= "Show labels", fg="black", command=confirm1)
show_labels.grid(row=0, column=2)
root.mainloop()
Picture of the app so far
I surely can't reproduce the issue with your current code, but looking at the previous edit it is pretty clear what your problem is.
(taken from your previous edit)
def confirm1():
global background_image1
background.delete('all') # <--- this line of code
for x in range(100):
Label(second_frame, text = x ).grid(row=x, column=1)
Here you delete all your items from your canvas:
background.delete('all')
hence no item appears.
You should instead delete only those items that you want to remove by passing the id or tags to delete method. You can delete multiple items together at once by giving the same tags.
Another option would be to recreate the frame item again on canvas using create_window (Do note: your frame is not deleted/destroyed, it's only removed from canvas)
The size of Tkinter windows can be controlled via the following methods:
.minsize()
.maxsize()
.resizable()
Are there equivalent ways to control the size of Tkinter or ttk Frames?
#Bryan: I changed your frame1.pack code to the following:
frame1.pack(fill='both', expand=True)
frame1.bind( '<Configure>', maxsize )
And I added this event handler:
# attempt to prevent frame from growing past a certain size
def maxsize( event=None ):
print frame1.winfo_width()
if frame1.winfo_width() > 200:
print 'frame1 wider than 200 pixels'
frame1.pack_propagate(0)
frame1.config( width=200 )
return 'break'
The above event handler detects that a frame's width is too big, but is unable to prevent the increase in size from happening. Is this a limitation of Tkinter or have I misunderstood your explanation?
There is no single magic function to force a frame to a minimum or fixed size. However, you can certainly force the size of a frame by giving the frame a width and height. You then have to do potentially two more things: when you put this window in a container you need to make sure the geometry manager doesn't shrink or expand the window. Two, if the frame is a container for other widget, turn grid or pack propagation off so that the frame doesn't shrink or expand to fit its own contents.
Note, however, that this won't prevent you from resizing a window to be smaller than an internal frame. In that case the frame will just be clipped.
import Tkinter as tk
root = tk.Tk()
frame1 = tk.Frame(root, width=100, height=100, background="bisque")
frame2 = tk.Frame(root, width=50, height = 50, background="#b22222")
frame1.pack(fill=None, expand=False)
frame2.place(relx=.5, rely=.5, anchor="c")
root.mainloop()
A workaround - at least for the minimum size: You can use grid to manage the frames contained in root and make them follow the grid size by setting sticky='nsew'. Then you can use root.grid_rowconfigure and root.grid_columnconfigure to set values for minsize like so:
from tkinter import Frame, Tk
class MyApp():
def __init__(self):
self.root = Tk()
self.my_frame_red = Frame(self.root, bg='red')
self.my_frame_red.grid(row=0, column=0, sticky='nsew')
self.my_frame_blue = Frame(self.root, bg='blue')
self.my_frame_blue.grid(row=0, column=1, sticky='nsew')
self.root.grid_rowconfigure(0, minsize=200, weight=1)
self.root.grid_columnconfigure(0, minsize=200, weight=1)
self.root.grid_columnconfigure(1, weight=1)
self.root.mainloop()
if __name__ == '__main__':
app = MyApp()
But as Brian wrote (in 2010 :D) you can still resize the window to be smaller than the frame if you don't limit its minsize.
I'm working on a GUI with tkinter and i have a problem.
When i add a scrollbar to my app, the frame on my canvas overlaps the outlines (see image)
Here is the code:
from tkinter import *
window = Tk()
window.geometry("400x225")
scrollbar1 = Scrollbar(window, orient=VERTICAL)
canvas1 = Canvas(window, bg="#003333", yscrollcommand=scrollbar1.set)
frame1 = Frame(canvas1, bg="#003333")
scrollbar1.config(command=canvas1.yview)
scrollbar1.pack(side=RIGHT, fill=Y)
canvas1.pack(fill=BOTH, expand=True)
canvas1.create_window((0, 0), window=frame1, anchor="nw")
for x in range(20):
string = "line " + str(x)
label1 = Label(frame1, fg="white", bg="#003333", text=string, font=("Calibri Bold", 14))
label1.pack(pady=5)
window.update()
canvas1.config(scrollregion=canvas1.bbox("all"))
window.mainloop()
I don't know if it's possible but i want the frame to fit within the canvas and keeping the outlines as well.
I hope you get my problem and can probably help me out! Thanks in advance.
The highlightthickness
Specifies a non-negative value indicating the width of the highlight rectangle to draw around the outside of the widget when it has the input focus.
So, this is not really the "border" that you want. It is a part of the drawing space within the canvas, when you use window_create to draw a window, the parent of that window is the canvas, which begins before the highlight and so the window slides over it.
A solution, as also suggested by #martineau would be to make this 0 by specifying highlightthickness=0 and as you suggested that you need the "border" around the whole thing, you can either create a container frame and specify the bd parameter, or just set the bd of the window window.config(bd=2).
I am trying this so I can place buttons in the medFrame, but they appear in the topFrame. When using topFrame, the button is against the top of the screen, which looks bad, so I figured this could be solved by using a third frame.
from tkinter import *
root = Tk()
root.title('BulletHead')
root.attributes('-fullscreen', True)
root.resizable(width = NO, height=NO)
topFrame=Frame(root)
topFrame.pack(side=TOP)
medFrame=Frame(root)
medFrame.pack()
botFrame = Frame(root)
botFrame.pack(side=BOTTOM)
botonJugar = Button(medFrame, text = 'Jugar')
botonJugar.bind("<Button-1>",jugar)
botonJugar.pack()
botonTabla = Button(medFrame, text = 'Tabla de puntajes')
botonTabla.bind("<Button-1>",tabla)
botonTabla.pack()
root.mainloop()
The reason the widgets appear at the top is because topFrame is empty. Since it has no height or width, and no children, it is only one pixel in size. If you give it a width and height or put some widgets in it, you'll see that the button is in fact in the middle frame.
Here is the image I get when I give the frames an artificial width and height, and force the frames to fill in the "x" direction (and also shrink the window down for illustrative purposes):
Here is what I changed (note that I also added color to make the frames easier to see):
topFrame = Frame(root, bg="pink", height=100)
medFrame = Frame(root, bg="bisque")
botFrame = Frame(root, bg="yellow", height=100)
topFrame.pack(side=TOP, fill="x")
medFrame.pack(fill="x")
botFrame.pack(side=BOTTOM, fill="x")
If you want the medium frame to take up all of the extra space, add the expand option and have it fill in both directions:
medFrame.pack(fill="both", expand=True)
Once you add widgets to the top and bottom frames, they will shrink in height to fit their contents, making it really easy top create tool bars and status bars.
The elements have successfully been added to the middle frame however it appears as though they are in the top frame because the topFrame and botFrame have no dimensions so they don't appear (unless you can perceive one pixel). To get some spacing for the middle frame you want to supply some dimensions to the other frames. try this to give the middle frame some spacing:
topFrame=Frame(root, height=200, width=200)
botFrame = Frame(root, height=200, width=200)
An alternative to this is to check out some other options for using the pack geometry manager with only one frame to get the desired results. Particularly the expand= option is helpful for centering objects in a window.
from tkinter import *
root = Tk()
root.title('BulletHead')
root.attributes('-fullscreen', True)
root.resizable(width = NO, height=NO)
medFrame=Frame(root)
medFrame.pack(expand=True)
botonJugar = Button(medFrame, text = 'Jugar')
botonJugar.bind("<Button-1>",jugar)
botonJugar.pack()
botonTabla = Button(medFrame, text = 'Tabla de puntajes')
botonTabla.bind("<Button-1>",tabla)
botonTabla.pack()
root.mainloop()
I'm programming a little game with tkinter and briefly, I'm stuck.
I have a kind od starting menu, in which are two buttons and one label.
If I just create the frame everything is fine, it has the size 500x500 pixels
I want the background not to change when I create the buttons and the labe, but it adapts the size whatever I do. Here is my code:
import tkinter as tk
def startgame():
pass
mw = tk.Tk() #Here I tried (1)
mw.title('The game')
back = tk.Frame(master=mw, width=500, height=500, bg='black')
back.pack()
go = tk.Button(master=back, text='Start Game', bg='black', fg='red',
command=lambda:startgame()).pack()
close = tk.Button(master=back, text='Quit', bg='black', fg='red',
command=lambda:quit()).pack()
info = tk.Label(master=back, text='Made by me!', bg='red',
fg='black').pack()
mw.mainloop()
I've searched around on stackoverflow and didn't get anything useful!
I've found just one question a bit similar to mine but the answer didn't work. I tried this:
(1) mw.resizable(width=False, height=False)
I can't imagine what is the problem, I'm really desperate.
You turn off pack_propagate by setting pack_propagate(0)
Turning off pack_propagate here basically says don't let the widgets inside the frame control it's size. So you've set it's width and height to be 500. Turning off propagate stills allows it to be this size without the widgets changing the size of the frame to fill their respective width / heights which is what would happen normally
To turn off resizing the root window, you can set root.resizable(0, 0), where resizing is allowed in the x and y directions respectively.
To set a maxsize to window, as noted in the other answer you can set the maxsize attribute or minsize although you could just set the geometry of the root window and then turn off resizing. A bit more flexible imo.
Whenever you set grid or pack on a widget it will return None. So, if you want to be able to keep a reference to the widget object you shouldn't be setting a variabe to a widget where you're calling grid or pack on it. You should instead set the variable to be the widget Widget(master, ....) and then call pack or grid on the widget instead.
import tkinter as tk
def startgame():
pass
mw = tk.Tk()
#If you have a large number of widgets, like it looks like you will for your
#game you can specify the attributes for all widgets simply like this.
mw.option_add("*Button.Background", "black")
mw.option_add("*Button.Foreground", "red")
mw.title('The game')
#You can set the geometry attribute to change the root windows size
mw.geometry("500x500") #You want the size of the app to be 500x500
mw.resizable(0, 0) #Don't allow resizing in the x or y direction
back = tk.Frame(master=mw,bg='black')
back.pack_propagate(0) #Don't allow the widgets inside to determine the frame's width / height
back.pack(fill=tk.BOTH, expand=1) #Expand the frame to fill the root window
#Changed variables so you don't have these set to None from .pack()
go = tk.Button(master=back, text='Start Game', command=startgame)
go.pack()
close = tk.Button(master=back, text='Quit', command=mw.destroy)
close.pack()
info = tk.Label(master=back, text='Made by me!', bg='red', fg='black')
info.pack()
mw.mainloop()
If you want a window as a whole to have a specific size, you can just give it the size you want with the geometry command. That's really all you need to do.
For example:
mw.geometry("500x500")
Though, you'll also want to make sure that the widgets inside the window resize properly, so change how you add the frame to this:
back.pack(fill="both", expand=True)
Try parent_window.maxsize(x,x); to set the maximum size. It shouldn't get larger even if you set the background, etc.
Edit: use parent_window.minsize(x,x) also to set it to a constant size!
Here is the most simple way.
import tkinter as tk
root = tk.Tk()
root.geometry('200x200')
root.resizable(width=0, height=0)
root.mainloop()
I don't think there is anything to specify.
It's pretty straight forward.
There are 2 solutions for your problem:
Either you set a fixed size of the Tkinter window; mw.geometry('500x500')
OR
Make the Frame adjust to the size of the window automatically;back.place(x = 0, y = 0, relwidth = 1, relheight = 1)
*The second option should be used in place of back.pack()
from tkinter import *
root = Tk()
width = root.winfo_screenwidth() #get your Windows width size
height = root.winfo_screenheight() #get your Windows height size
root.geometry("%dx%d" % (width, height))
root.resizable(False,False)
root.mainloop()
#full size Tkinter