I'd like to create three text areas in a tkinter window and make them dinamically resizable. I thought that one solution was to pass the width and height parameters in pixels (such as height=int(win_height/2)), but as I read it isn't possible, in fact the width and height parameters in a tk.Text widget are calculated by characters for each line and column. I've also tried to pass the width and height parameters in percentages (such as height=50%) but it returns me a syntax error.
I've been trying to find out a solution for this problem in the net, and the best code I've found is this:
import tkinter as tk
root = tk.Tk()
root.geometry("500x500")
# Text Box
first_textbox = tk.Text(root, width=25, height=10, bg='yellow')
second_textbox = tk.Text(root, width=25, height=10, bg='blue')
third_textbox = tk.Text(root, width=50, height=20, bg='red')
# Packing
first_textbox.grid(column=1, row=1)
second_textbox.grid(column=1, row=2)
third_textbox.grid(column=2, row=1, rowspan=2)
root.mainloop()
By running this code I obtain a window with three different text areas which aren't dinamically resizabled and which take more space than the actual window width. I hope you can help me.
Sorry for any English mistake, it is my second lenguage
grid has several documented parameters to help you do what you want. You simply need to use them.
By default, grid won't give widgets any extra space -- they take up only the space they need and no more. If you widgets to be allocated extra space, you have to explicitly arrange for that.
For example, if you want all widgets to grow and shrink equally, you need to configure the rows and columns to have an equal weight greater than zero. That will tell grid how to allocate any extra space when the window is bigger than the size requested by all of the widgets.
For example:
root.grid_rowconfigure((1,2), weight=1)
root.grid_columnconfigure((1,2), weight=1)
That just tells grid what to do with extra space. If instead, you want two or more rows or columns to have exactly the same size, you can use the uniform option to tell grid that you want the rows or columns to have a uniform (identical) size.
For example, if you want both columns 1 and 2 to have the same width, you can give each column the same value for the uniform option. Note: the value passed to uniform can be anything you want. The important thing is that they are configured to have the same value.
root.grid_columnconfigure((1, 2), uniform="equal")
That alone won't solve the problem. You also must tell grid that you want the widgets to fill the space given to them. You do that with the sticky parameter, which tells grid to "stick" the widget to one or more sides of the allocated space.
To get the widgets to fill all allocated space you can give the string "nsew" which stands for "north, south, east, and west" which represent the four sides of the given space.
first_textbox = tk.Text(root, width=25, height=10, bg='yellow')
second_textbox = tk.Text(root, width=25, height=10, bg='blue')
third_textbox = tk.Text(root, width=50, height=20, bg='red')
Related
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)
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)
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.
I want to create a desktop-application using tkinter. When placing text (of big size) in Labels, I always get a large vertical padding. Can I anyhow get rid of this additional space? I would like to place the text right on the bottom of the label.
I have already tried to set pady as well as the text anchor.
self.lbl_temp = Label(self.layout, text='20°C', font=('Calibri', 140), bg='green', fg='white', anchor=S)
self.lbl_temp.grid(row=0, column=1, sticky=S)
Here is an image of how it looks:
I would like to remove the green space below (and on top of) the text.
Removing the space above and below the text cannot be done with a Label because the height corresponds to an integer number of lines whose height is determined by the font size. This line height reserves space for letters going below the baseline, like 'g', but since you don't use such letters, you have a lot of empty space below your text (I don't have as much extra space a the top on my computer though).
To remove this space, you can use a Canvas instead of a Label and resize it to be smaller.
import tkinter as tk
root = tk.Tk()
canvas = tk.Canvas(root, bg='green')
canvas.grid()
txtid = canvas.create_text(0, -15, text='20°C', fill='white', font=('Calibri', 140), anchor='nw')
# I used a negative y coordinate to reduce the top space since the `Canvas`
# is displaying only the positive y coordinates
bbox = canvas.bbox(txtid) # get text bounding box
canvas.configure(width=bbox[2], height=bbox[3] - 40) # reduce the height to cut the extra bottom space
root.mainloop()
I want to create a GUI with tkinter in python using grid-Method and grid_columnconfigure/grid_rowconfigure.
Unfortunately, this is not working inside a Frame.
How can I get this work?
from tkinter import *
master = Tk()
master.state('zoomed')
f = Frame(master, width=800, height=400)
Label1 = Label(f, text='Label 1')
Label2 = Label(f, text='Label 2')
f.grid_columnconfigure(0, weight=1)
f.grid_columnconfigure(2, weight=1)
f.grid_columnconfigure(4, weight=1)
Label1.grid(row=0, column=1)
Label2.grid(row=0, column=3)
f.pack()
master.mainloop()
ADDITIONAL QUESTION:
I got great answers, all is working fine with pack-Manager.
But how could I do this if using grid-Manager?
The grid_columnconfigure is working fine. The problem is that your frame will by default set its size to the smallest possible size to fit the labels. Since empty columns don't have a size, the frame will be just wide enough to hold the two labels.
This will be easy to visualize if you give frame a distinctive color during development. It also sometimes helps to give the frame a visual border so you can see its boundaries.
While I don't know what your ultimate goal is, you can see the spaces between the column if you have the frame fill the entire window:
f.pack(fill="both", expand=True)
If you want to use grid instead of pack, you have to do a bit more work. In short, put the frame in row 0 column 0, and give that row and column a non-zero weight so that grid will give all unused space to that row and column.
f.grid(row=0, column=0, sticky="nsew")
master.grid_rowconfigure(0, weight=1)
master.grid_columnconfigure(0, weight=1)
If you want to force the window to be a specific size, you can use the geometry method of the master window:
master.geometry("800x400")