Tkinter Widget color does not change - python

When I put a widget inside the frame, the color of the frame vanishes. If it was 'black' before, then after putting a widget(label) inside the frame, the color again becomes white.
Here's my code:
from tkinter import *
root = Tk()
root.geometry("700x600")
f = Frame(root, height = 400, width = 400, bg = 'black')
f.pack()
id = Label(f, text = "Email:", fg = 'blue', font = ('Kristen ITC', 18))
id.pack()

Your Frame is resizing itself to the Label. You need to set...:
...
f.pack_propagate(False)
f.pack()
...
In order for the Frame to maintain its own dimension without affected by the its children widgets.

By default, widgets shrink or expand to fit their contents. When you add the button, the frame shrinks down to fit.
It appears you want the frame to take up just a part of the root window. Instead of explicitly giving the frame a width and height, it's usually better to let tkinter do that for you. Fill the frame with whatever widgets you want, and let tkinter decide how big the frame should be. Then, use appropriate options for grid or pack to arrange them logically. When tkinter is allowed to make widgets the right size, you'll end up with a much more responsive UI.
For example, if you set the fill and expand options when you call pack on the frame, it will not shrink to fit. If you later need to add more widgets, you won't have to modify other parts of your code to make them fit.
f.pack(fill="both", expad=True)
You can also turn off this "shrink to fit" feature by calling f.pack_propagate(False), but that is rarely the right solution because it forces you to calculate sizes, and your calculations may be wrong if you run the program on a system with different fonts or different resolutions.

Related

Tkinter window resize to fit content without touching

I have created an application that required to use the place() manager. I tried using pack() and grid() but after many tries and lots of effort it did not work for my goals. Since I use relx= and rely= almost all the time and I want to put my app on multiple OS's I need to have the window resize to fit all the widgets without them touching.
Is there a way to do this? Since many OS's and their updates change rendering the sizes of widgets changes greatly and I don't want the user to have to resize the window all the time. I want it to just fit as tightly as possible or get the minimum width and height to I can add some buffers. Is this possible? If not, is there a way to fix my app without having to rewrite everything?
Note:
I found something similar: Get tkinter widget size in pixels, but I couldn't properly get it to work.
I figured it out. You can use the widget.winfo_height()/widget.winfo_width() functions to get the minimum pixel sizes. Since the window.geometry() function requires pixel sizes you can make two lists: one for width, and one for height. By adding the minimum amount of widgets you can get the desired size.
Code:
height_widget_list = [main_label, main_notebook, the_terminal, quit_button, settings_button]
width_height_list = [commands_label, main_notebook, quit_button]
widget_list = [main_label, main_notebook, the_terminal, quit_button, settings_button, commands_label]
# Widgets must be updated for height and width to be taken
for widget in widget_list:
widget.update()
height_required_list = []
width_required_list = []
# Collects data
for widget in height_widget_list:
height_required_list.append(int(widget.winfo_height()))
for widget in width_height_list:
width_required_list.append(int(widget.winfo_width()))
# Get height requirement
minimum_height = 0
for height in height_required_list:
minimum_height += height
# Get width requirement
minimum_width = 0
for width in width_required_list:
minimum_width += width
# Make window correct size make window require the correct sizing
window.geometry("{}x{}".format(minimum_width, minimum_height))
window.resizable(False, False)

Tkinter geometry method to only set width or height

The following will set a tkinter window width and height.
root.geometry("500x500")
Is it possible to only set width or height?
Where can I find a full list of geometry method variations, for setting only select parameters?
I would like to be able to set select parameters, of the window size and/or position, and let the rest be under tkinter dynamic control.
When looking at the tcl/tk documentation, we can see that we do not have to provide a full "widthxheight±x±y" string to the .geometry() method:
with only "widthxheight", the size of the window will be changed but not its position on the screen.
with only "±x±y", the position will be changed but not the size.
However, it is not possible to set separately the width and height of the window with this method. Nevertheless, you can retrieve the dimension you don't want to change and use it in .geometry() with something like
def set_height(window, height):
window.geometry("{}x{}".format(window.winfo_width(), height))
def set_width(window, height):
window.geometry("{}x{}".format(width, window.winfo_height()))
Note: Using root.config(width/height=...) only works for me if the window has never been resized with the mouse or using .geometry()
Use root.config(width=100) or root.config(height=100)
If you define a frame within your window you can set width and height of this frame separately like this:
myframe = tk.Frame(width=200)
This may create the appearance you wish to achieve.
An example program demonstrates how to use this trick:
import tkinter as tk
window = tk.Tk()
window.title('Frame Demo')
frame = tk.Frame(width=300)
frame_2 = tk.Frame()
label = tk.Label(master=frame_2, text="Frame_2")
label.pack()
frame.pack()
frame_2.pack()
window.mainloop()
The frame by the name frame is used only to widen the window and is invisible. No GUI elements should be placed in it. Its only function is to give the main window the needed size of 300, in this example. frame_2 is the canvas, so to say, for the GUI elements.

Is There a Way to Always have a Place Widget Relative to Another Widget(Such as Pack)

So, I'm using the place method to have a widget overlap other widgets, but its position is relative (with winfo) to a widget that uses pack. When the parent frame is resized, the pack position will change, but the place position will not.
This is my code:
from tkinter import *
root = Tk()
root.geometry("200x300")
search = Entry(root)
search.pack()
search.update()
x = search.winfo_x()
y = search.winfo_y()
width = search.winfo_width()
height = search.winfo_height()
frame = LabelFrame(root, width=width, height=200)
frame.place(x=x, y=y+height)
root.mainloop()
The LabelFrame stays in its x and y position when the window is resized. The Entry widget will be used as a search bar and I want autocompletion under it. There will be widgets under the entry widget and the autocompletion will only appear when you are typing (That's not what I'm looking for though. Its just more exposition if you need it). So, is there a way to have the place widget always be relative to the pack widget. If you have any answers, thank you:)
If your goal is to put one widget relative to another, place lets you do that. It's great for things like tooltips or other transient widgets that don't otherwise fit into a normal layout.
The easiest way to do that is to make the widget a child of the controlling widget. For example, for your frame to be placed relative to the search box you can make it a child of the search box. If it's inconvenient to do that, you can use the in_ parameter to tell place which widget is the other widget.
For example, to place your labelframe immediately below the search box and with the same width as the search box you might do it something like this:
frame.place(
in_=search,
bordermode="outside",
anchor="nw",
relx=0,
rely=1.0,
y=5,
relwidth=1.0
)
This is what the options mean:
in_=search: place the frame relative to the search box
bordermode="outside": relative measurements are from the outside of the border (default is "inside")
anchor="nw": place the widget so that the northwest corner of the frame is at the computed coordinate
relx=0: place the anchor point 0% from the left edge of the search box
rely=1.0: place the frame at 100% of the height of the search box
y=5: add 5 pixels to the computed position so it floats just a little below the window
relwidth=1.0: make the width of the frame 100% the width of the search box.
Obviously you don't have to use y=5, I just added it to illustrate the additive behavior of using rely and y.

Tkinter: How to make a fixed canvas size with scroll bars that resize to the window

I am attempting to create fixed-size canvas widget with scroll bars. The canvas in question could be quite large, and will almost definitely be much larger than the frame containing it. I would like to keep the canvas at its fixed size, but be able to resize the window containing it. The issue I am having is I don't know how to bind the scroll bars to the edge of the window.
I have tried both .pack and .grid. The obvious issue with .grid is that it will simply place the scroll bars next to the canvas. Unfortunately, the canvas must have a fixed size that will always be larger than the window. Whenever I .pack, the canvas appears to resize with the window, even when I explicitly disable expand and set fill to None.
I have made set the background to black for the purpose of clearly seeing the canvas area.
import tkinter as tk
from tkinter import *
class DialogueCreation(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.xbar = tk.Scrollbar(parent, orient=HORIZONTAL)
self.xbar.pack(side=BOTTOM, fill=X)
self.ybar = tk.Scrollbar(parent)
self.ybar.pack(side=RIGHT, fill=Y)
self.item_canvas = tk.Canvas(parent, width=5000, height=5000, xscrollcommand=self.xbar.set, yscrollcommand=self.ybar.set)
self.item_canvas.pack(side=LEFT, expand=FALSE, fill=None)
self.item_canvas.configure(background='black')
if __name__ == '__main__':
root = tk.Tk()
DialogueCreation(root)
root.title("Editor")
root.mainloop()
The canvas is a massive 5000x5000, so I should definitely be able to scroll when the window is small. I want the scrollbars to remain flush with the edges of the window, without resizing my canvas. The scrollbars remain dormant no matter how large or small the window is. I'm assuming the canvas is resizing with the window, which is definitely not the desired result.
Eventually this canvas will have several images displayed on it, and the location of those images must not change on the canvas. I do not believe the issue is with how I bound the scrollbars (I checked several other posts on this website to make sure), but it would not be uncharacteristic if I missed something obvious like that.
When you say you want to create a fixed size canvas, I'm assuming that you mean you want the drawable area to be a fixed size, rather than have a fixed size for the viewable portion of the canvas.
To do that, you need to set the scrollregion attribute to the drawable area. You use the width and height attributes to set the size of the visible portion of the canvas.
Also, hooking up scrollbars is a two way street: you've got to configure the canvas to update the scrollbars, and configure the scrollbars to scroll the canvas.
Note: you made DialogCreation a Frame, yet you put all of the widgets directly in the parent. That's very unusual, and not the best way to do it. I recommend inheriting from Frame like you do, but then all of the widgets should go in self rather than parent.
When you do it this way, you need to make sure you call pack on the instance of DialogCreation, eg:
dr = DialogueCreation(root)
dr.pack(fill="both", expand=True)
Using pack
With pack, you should set the expand option to True if you want the visible portion to grow or shrink when the user resizes the window.
My personal experience is that code is easier to understand and easier to maintain if you separate widget creation from widget layout. The following code shows how I would rewrite your code using pack. Notice the additional lines for configuring xbar and ybar, as well as setting the scrollregion.
class DialogueCreation(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.xbar = tk.Scrollbar(self, orient=HORIZONTAL)
self.ybar = tk.Scrollbar(self)
self.item_canvas = tk.Canvas(self, width=400, height=400,
xscrollcommand=self.xbar.set,
yscrollcommand=self.ybar.set)
self.xbar.configure(command=self.item_canvas.xview)
self.ybar.configure(command=self.item_canvas.yview)
self.item_canvas.configure(scrollregion=(0,0,4999,4999))
self.item_canvas.configure(background='black')
self.xbar.pack(side=BOTTOM, fill=X)
self.ybar.pack(side=RIGHT, fill=Y)
self.item_canvas.pack(side=LEFT, expand=TRUE, fill=BOTH)
Using grid
The obvious issue with .grid is that it will simply place the scroll bars next to the canvas.
I don't see that as obvious at all. grid has no such restriction. You can put the scrollbar anywhere you want.
The important thing to remember with grid is that rows and columns do not automatically grow or shrink when the window as a whole changes size. You have to explicitly tell tkinter which rows and columns to grow and shrink.
To achieve the same effect as with using pack, you need to configure row zero, column zero to be given all extra space. You do that by giving them a weight that is greater than zero.
To use grid instead of pack, replace the last three lines of the above example with the following six lines:
self.item_canvas.grid(row=0, column=0, sticky="nsew")
self.xbar.grid(row=1, column=0, sticky="ew")
self.ybar.grid(row=0, column=1, sticky="ns")
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)

pack_propagate(0) equivalent for Grid and place

I am trying to place a button on a frame for an application that am working on...
But the frame disappears when I use pack or grid
from Tkinter import *
root=Tk()
width = root.winfo_screenwidth()
height = root.winfo_screenheight()
root.geometry(str(width)+'x'+str(height))
frame=Frame(root,width=500, height=500, bg='black')
but1=Button(frame,text='qwe')
but1.grid()
frame.grid()
root.mainloop()
And if I use place, even worse, both of them disappear...I dont see the Frame and the Button
from Tkinter import *
root=Tk()
width = root.winfo_screenwidth()
height = root.winfo_screenheight()
root.geometry(str(width)+'x'+str(height))
frame=Frame(root,width=500, height=500, bg='black')
but1=Button(frame,text='qwe')
but1.place()
frame.place()
root.mainloop()
but when I use pack_propagate(0), I can see them both...
from Tkinter import *
root=Tk()
width = root.winfo_screenwidth()
height = root.winfo_screenheight()
root.geometry(str(width)+'x'+str(height))
frame=Frame(root,width=500, height=500, bg='black')
but1=Button(frame,text='qwe')
but1.pack()
frame.pack_propagate(0)
frame.pack()
root.mainloop()
My Questions are,
What does pack_propagate(0) mean ?
Why is the frame behaving weird with and without pack_propagate(0) ?
What is the equivalent of pack_propagate(0) for GRID and PLACE ?
What does pack_propagate(0) mean ?
pack_propagate(0) tells tkinter to let the parent control its own size, rather than letting it's size be determined by the children of the widget when using pack to manage the children.
Why is the frame behaving weird with and without pack_propagate(0)
I don't see any weird behavior. You'll have to be more precise about what you mean by "weird". It's behaving as documented. place will not cause the children to determine the size of the parent. If you don't give the frame a size, it's size will be 1x1 (ie: a single pixel). And, if the frame is invisible, any widgets inside the frame will also be invisible.
When you use pack or grid, the frame will try to shrink to fit its children. It doesn't disappear, you just can see it because its children are on top of it. If you add some padding, you'll see the frame.
For example, in the first block, change where you call grid on but1 to be like the following code and your black frame will appear:
but1.grid(padx=20, pady=20)
This is one of the reasons why place is rarely the right choice. Both pack and grid do a really great job of making sure all widgets are just the right size based on what is inside them.
What is the equivalent of pack_propagate(0) for GRID and PLACE
For grid it's grid_propagate. There's no equivalent for place because that's the defined behavior of place -- it never affects the size of the containing widget.
For the vast majority of cases, you should not use pack_propagate(0), grid_propagate(0), or place. When you use grid and pack properly, your gui will be the right size on every platform you run it on, no matter what the resolution and no matter what fonts the user has loaded.

Categories