Related
I've been trying to align the entries and buttons on this password manager I built for a while now but haven't been able to find a solution that works.
I tried changing the width, columnspan, and coordinates but it doesn't seem to work.
I want the password entry to be aligned just like the other two but with a lower width so that the generate button does not go over. I also want the add button to be aligned equally with the row and column.
window = Tk()
window.title("Password Manager")
window.config(padx=50, pady=50)
canvas = Canvas(width=200, height=200)
my_pass_img = PhotoImage(file="logo.png")
canvas.create_image(100, 100, image=my_pass_img)
canvas.grid(column=1, row=0)
web_label = Label(text="Website:", fg="black")
web_label.grid(row=1, column=0)
user_label = Label(text="Email/Username:", fg="black")
user_label.grid(row=2, column=0)
pass_label = Label(text="Password:", fg="black")
pass_label.grid(row=3, column=0)
web_entry = Entry(width=35)
web_entry.grid(row=1, column=1, columnspan=2)
web_entry.focus()
user_entry = Entry(width=35)
user_entry.grid(row=2, column=1, columnspan=2)
user_entry.insert(0, "-#gmail.com")
pass_entry = Entry(width=30)
pass_entry.grid(row=3, column=1)
generate_button = Button(text="Generate Password", fg="black", command=generate_password)
generate_button.grid(row=3, column=2)
add_button = Button(width=36, text="Add", fg="black", command=save)
add_button.grid(row=4, column=1, columnspan=2)
window.mainloop()
Grid
When using grid to setup your widgets, the entire window is divided into individual cells based on the number of columns and rows you've specified. Although you can control the individual sizes of widgets, the overall size it can take will be limited by your choice of column- and rowwidth, as well as column- and rowspan.
Sticky
You can use the sticky attribute in grid to set the side of the column you want your widgets to 'stick' to.
The stickiness is set by choosing one of the 8 directions on a compass:
N (north): stick to top of cell
S (south): bottom
E (east): right
W (west): left
NW, NE, SW, SE: top-left, top-right, bottom-left, bottom-right corners respectively.
There are also two bonus stickiness options:
NS: stretch your widget from top to bottom of the sell, but keep it centered horizontally
EW: stretch from left to right of the cell, but centered vertically.
Padding
To increase legibility, you can add padding above and below, and to the sides, of your widgets using the padx and pady attributes (measured in pixels).
So if you set stickyness to "W" with a horizontal padding padx=5, the widget position is offset from the cell boundary by 5 pixels.
Combining it in your example
To align the entries, set their sticky attribute to "W", or tk.W, to algin on left side of cell.
If you want to align labels on the right set sticky="E".
You can reduce the columnspan too of the entries, if you don't need them to be extra large. The resulting changes to grid are thus:
canvas.grid(column=1, row=0)
# Label widgets: set sticky to 'East'
web_label.grid(row=1, column=0, sticky='E')
user_label.grid(row=2, column=0, sticky='E')
pass_label.grid(row=3, column=0, sticky='E')
# Entry widgets
web_entry.grid(row=1, column=1, columnspan=1, sticky='WE')
user_entry.grid(row=2, column=1, columnspan=1, sticky=tk.EW) # Note flag is available as tkinter attribute
# Password entry: align on left-hand side
pass_entry.grid(row=3, column=1, sticky='W')
# Align button to right side of 3rd column
generate_button.grid(row=3, column=2,sticky='E')
# Can either align in middle column...
add_button.grid(row=4, column=1, columnspan=1, sticky="WE")
#... or in middle of page
add_button.grid(row=4, column=0, columnspan=3, sticky="WE")
Bonus
Apply padding (or sticky) to all widgets in a frame or window:
for child in window.winfo_children():
child.grid_configure(padx=3, pady=3)
# child.grid_configure(sticky='W')
You can use sticky option to put the widget in a grid cell at "n" (north), "s" (south), "e" (east) and "w" (west). If not specify, it is by default is "" which will put the widget at the center of the cell.
For your case, add sticky="w" to all the labels and entries, and sticky="e" to the "Add" button:
from tkinter import *
def generate_password():
pass
def save():
pass
window = Tk()
window.title("Password Manager")
window.config(padx=50, pady=50)
canvas = Canvas(window, width=200, height=200)
my_pass_img = PhotoImage(file="logo.png")
canvas.create_image(100, 100, image=my_pass_img)
canvas.grid(column=1, row=0)
web_label = Label(window, text="Website:", fg="black")
web_label.grid(row=1, column=0, sticky="w")
user_label = Label(window, text="Email/Username:", fg="black")
user_label.grid(row=2, column=0, sticky="w")
pass_label = Label(window, text="Password:", fg="black")
pass_label.grid(row=3, column=0, sticky="w")
web_entry = Entry(window, width=35)
web_entry.grid(row=1, column=1, columnspan=2, sticky="w")
web_entry.focus()
user_entry = Entry(window, width=35)
user_entry.grid(row=2, column=1, columnspan=2, sticky="w")
user_entry.insert(0, "-#gmail.com")
pass_entry = Entry(window, width=30)
pass_entry.grid(row=3, column=1, sticky="w")
generate_button = Button(window, text="Generate Password", fg="black", command=generate_password)
generate_button.grid(row=3, column=2)
add_button = Button(window, width=36, text="Add", fg="black", command=save)
add_button.grid(row=4, column=1, columnspan=2, sticky="e")
window.mainloop()
Result:
You can play around with different values or combinations of them of the sticky option to see the different effects.
Note also that it is better to specify the parent of those widgets.
I tried to follow the lesson on databases in this youtube video from freecodecamp. However, when I wrote my code and executed it, an entry box and a label are stuck on top of my button to submit a record. I don't know how to fix this issue.
Using rowconfigure didn't help either and the label is still stuck on top of the submit button.
Also, for every label and button that I add, even if the button's row is smaller than the label's row, the label is displayed before the button in the GUI.
label and entry box on top of button
I have removed all the database elements from my code as they are not relevant to the problem I am facing. Here is my code:
from tkinter import *
window = Tk()
window.geometry("400x400")
# Create function to Delete a record
def delete():
pass
# create submit function
def submit():
pass
# create query function
def query():
pass
f_name = Entry(window, width=30)
f_name.grid(row=0, column=1, pady=(10, 0))
l_name = Entry(window, width=30)
l_name.grid(row=1, column=1)
address = Entry(window, width=30)
address.grid(row=2, column=1)
city = Entry(window, width=30)
city.grid(row=3, column=1)
province = Entry(window, width=30)
province.grid(row=4, column=1)
p_code = Entry(window, width=30)
p_code.grid(row=5, column=1)
# Create text box label
f_name_label = Label(window, text="First Name")
f_name_label.grid(row=0, column=0, pady=(10, 0))
l_name_label = Label(window, text="Last Name")
l_name_label.grid(row=1, column=0)
address_label = Label(window, text="Street")
address_label.grid(row=2, column=0)
city_label = Label(window, text="City")
city_label.grid(row=3, column=0)
province_label = Label(window, text="Province")
province_label.grid(row=4, column=0)
p_code_label = Label(window, text="Postal Code")
p_code_label.grid(row=5, column=0)
# Create submit button
submit_btn = Button(window, text="Add record to database", command=submit)
submit_btn.grid(rows=6, column=0, columnspan=2, pady=10, padx=10, ipadx=100)
# create a query button
query_btn = Button(window, text="Show Records", command=query)
query_btn.grid(rows=7, column=0, columnspan=2, pady=10, padx=10, ipadx=125)
delete_box = Label(window, text="ID Number")
delete_box.grid(row=8, column=0)
delete_box = Entry(window, width=30)
delete_box.grid(row=8, column=1)
# create a delete button
delete_btn = Button(window, text="Delete Record", command=delete)
delete_btn.grid(rows=9, column=0, columnspan=2, pady=10, padx=10, ipadx=125)
window.mainloop()
What mistake have I made?
Edit: Thanks for the responses. It turns out that it was just a typo (rows instead of row) that caused the problem. But now I am curious to know how did my previous code give that particular output? Shouldn't it have thrown an error saying that rows is an invalid keyword argument? Moreover, when the row parameter was not even given for submit_btn, how was it correctly placed below "Postal Code"? Why exactly was there an overlap of the label, entry and button?
The reason for your output is really interesting and involves some minute details in tkinter.
Key Observations:
After experimenting with your code, I made the following observations:
rows is equivalent to rowspan and columns is equivalent to columnspan
When row is not given in the .grid statement, it is taken to be <index-of-last-occupied-row> + 1. On the other hand, the default value of column is fixed at 0.
Explanation for the Undesired Output:
Let's first figure out which is the last occupied row after p_code_label is added.
p_code_label = Label(window, text="Postal Code")
p_code_label.grid(row=5, column=0)
p_code_label is present in row[5] and it is the last occupied row.
Next, the buttons are displayed using grid as explained below:
# Create submit button
submit_btn = Button(window, text="Add record to database", command=submit)
submit_btn.grid(rows=6, column=0, columnspan=2, pady=10, padx=10, ipadx=100)
In the above grid statement, row has not been given. Therefore, submit_btn is added to row[5+1=6] and spans 6 rows (due to rows=6).
Now, the last occupied row = 6 + (6-1) = 11.
# create a query button
query_btn = Button(window, text="Show Records", command=query)
query_btn.grid(rows=7, column=0, columnspan=2, pady=10, padx=10, ipadx=125)
As row is once again not given above, query_btn is added to row[11+1=12] and spans 7 rows.
Now, the last occupied row = 12 + (7-1) = 18.
Note that even though submit_btn and query_btn span multiple rows, they appear to be spanning only 1 row because none of the rows have been assigned any weights.
delete_box = Label(window, text="ID Number")
delete_box.grid(row=8, column=0)
delete_box = Entry(window, width=30)
delete_box.grid(row=8, column=1)
Unlike the previous two widgets, the above two widgets have been given a row. Thus, they occupy row[8] and are displayed on top of the submit_btn. The button is almost completely covered vertically even though the above two widgets are present in only one of the rows spanned by submit_btn. This is because the rows do not have any weights.
If all the rows spanned by submit_btn are assigned weights and the button is gridded using sticky=NSEW, the output would be as given below:
(The vertical centre of 6 rows would be in between two rows. That's the reason why Add record to database is displayed between row[8] and row[9].)
By now, the reason for the overlap of the label, entry and button should be clear.
For the sake of completion, I will also explain how is delete_btn added.
# create a delete button
delete_btn = Button(window, text="Delete Record", command=delete)
delete_btn.grid(rows=9, column=0, columnspan=2, pady=10, padx=10, ipadx=125)
The above widget is added to row[18+1=19] and spans 9 rows.
Thus, the last occupied row = 19 + (9 - 1) = 27.
I did not make any minor changes except to rearrange in order(Label, Entry and Button) for row index (6,7,8,9).
Also, it's bad for putting Entry widget any where before mainloop().
Always start after row=5. Then you can put Entry widget there.
I rearrange code to make readability widgets order..
Code:
delete_box = Label(window, text="ID Number")
delete_box.grid(row=6, column=0)
delete_box = Entry(window, width=30)
delete_box.grid(row=6, column=1)
# Create submit button
submit_btn = Button(window, text="Add record to database", command=submit)
submit_btn.grid(row=7, column=0, columnspan=2, pady=10, padx=10, ipadx=100)
# create a query button
query_btn = Button(window, text="Show Records", command=query)
query_btn.grid(row=8, column=0, columnspan=2, pady=10, padx=10, ipadx=125)
# create a delete button
delete_btn = Button(window, text="Delete Record", command=delete)
delete_btn.grid(row=9, column=0, columnspan=2, pady=10, padx=10, ipadx=125)
window.mainloop()
Screenshot:
I am trying to make my own gui with Python. I am using Tkinter and eventhough I made the buttons show text, the text won´t show up.
import classes
root = Tk()
root.geometry('395x400')
e = Entry(root, width=60, borderwidth=5)
e.grid(row=0, column=0, columnspan=3, padx=10, pady=10)
#buttons
button_add_student=Button(root, name="add student", padx=90, pady=20, command=)
button_show_student=Button(root, name="enter", padx=90, pady=20, )
button_add_student.grid(row=1, column=0)
button_show_student.grid(row=1, column=1)
root.mainloop() ```
They don't show text but you haven't given them any text to show. If you want a button to have a textual label then you need to provide a value to the text attribute:
button_add_student=Button(root, text="add student", ...)
button_show_student=Button(root, text="enter", ...)
You were using the name attribute, which specifies the internal name for the widget. The name is only visible if you print out the string representation of the widget (eg: print(str(button_add_student))).
I am currently trying to make a game in Tkinter which uses multiple different windows.
However, as I am trying to create the layout of a secondary window, I can't seem to get my Return to Menu button underneath the list box, and aligned to the left. I have tried it using .pack() and .grid() methods, but they don't make sense to me.
I've tried using .pack():
header = Frame(wn).pack()
title = Label(header, text='Single-Player',font=('Arial bold',20),bg=bgcolor).pack(anchor='center')
footer = Frame(wn).pack(side=BOTTOM)
return_to_menu = Button(footer, text='Return to Main Menu',font=('Arial',16),bg=bgcolor,command=lambda: menu()).pack(side=BOTTOM,padx=20,pady=250)
# body frame (left side)
bodyL = Frame(wn).pack(side=LEFT)
#output box
output = Listbox(bodyL, width=50, font=("Arial", 20)).pack(side=LEFT,padx=15)`
And I've tried using .grid():
header = Frame(wn).grid(sticky=N)
title = Label(header, text='Single-Player',font=('Arial bold',20),bg=bgcolor).grid(sticky=N+E+W,row=0,column=0)
footer = Frame(wn).grid(sticky=S)
return_to_menu = Button(footer, text='Return to Main Menu',font=('Arial',16),bg=bgcolor,command=lambda: menu()).grid(sticky=S,padx=20,row=0,column=0)
# body frame (left side)
bodyL = Frame(wn).grid(sticky=W)
#output box
output = Listbox(bodyL, width=50, font=("Arial", 20)).grid(sticky=W,padx=15, )`
However using .grid() doesn't align my title to the center of the screen anymore.
Is there a way to center it more efficiently - I didn't like using padx=450 to get it centered.
What happens with the button and the list box, is the button appears to the left of the list box, instead of on the bottom. I have attempted several other methods, such as incrementing row numbers, but my script doesn't appear to do what I anticipated.
Here is an example of how you can set up the weight of specific columns and row to get widgets to stick to a specific location on the screen.
With the use of grid() we need to use columnconfigure() and rowconfigure() to make this work.
These 2 methods are used to define at what ratio the column or row will expand in relation to the columns or rows around it as the container grows or shrinks.
See below example and let me know if you have any questions:
import tkinter as tk
root = tk.Tk()
for i in range(3):
root.columnconfigure(i, weight=1)
root.rowconfigure(1, weight=1)
tk.Label(root, text='Top left').grid(row=0, column=0, sticky='w')
tk.Label(root, text='Top center').grid(row=0, column=1)
tk.Label(root, text='Top right').grid(row=0, column=2, sticky='e')
tk.Label(root, text='center').grid(row=1, column=1)
tk.Label(root, text='Bottom left').grid(row=2, column=0, sticky='w')
tk.Label(root, text='Bottom center').grid(row=2, column=1)
tk.Label(root, text='Bottom right').grid(row=2, column=2, sticky='e')
root.mainloop()
Example:
Here is another example but this time I have a title label outside of a frame so that we can make it easier to manage the title being centered and then working with all the other content of the frame is separate from the title label.
import tkinter as tk
root = tk.Tk()
root.columnconfigure(0, weight=1)
root.rowconfigure(1, weight=1)
frame = tk.Frame(root)
frame.grid(row=1, column=0, sticky='nsew')
for i in range(3):
frame.columnconfigure(i, weight=1)
frame.rowconfigure(1, weight=1)
tk.Label(root, text='Title centered').grid(row=0, column=0)
tk.Label(frame, text='Top left').grid(row=0, column=0, sticky='w')
tk.Label(frame, text='Top center').grid(row=0, column=1)
tk.Label(frame, text='Top right').grid(row=0, column=2, sticky='e')
tk.Label(frame, text='Center').grid(row=1, column=1)
tk.Label(frame, text='Bottom left').grid(row=2, column=0, sticky='w')
tk.Label(frame, text='Bottom center').grid(row=2, column=1)
tk.Label(frame, text='Bottom right').grid(row=2, column=2, sticky='e')
tk.Label(root, text='Footer centered').grid(row=2, column=0)
root.mainloop()
Example:
To address your problem in the comments you cannot use grid() or any other geometry manager for that matter on the same line you create your container. This will always cause the variable for that frame to return None as the geometry managers return None when called.
See this image as to what happens when you use grid() on the same line you create your container.
Now if you delete the grid() part on the row that your container is created and then write it on the next line as the commented out section of the above images shows it will work as expected. See below image of proper use of grid() for containers.
To address your 2nd question in the comments you can add this line to provide a button at the bottom left.
tk.Button(root, text='Bottom left button').grid(row=3, column=0, sticky='w')
Example:
import tkinter as tk
root = tk.Tk()
buttonOK = tk.Button(root, text='B1')
MCC = tk.Button(root, text='B2')
TID = tk.Button(root, text='B3')
CURRENCY = tk.Button(root, text='B4')
COUNTRY = tk.Button(root, text='B5')
RESPONSE = tk.Button(root, text='B6')
B1.grid(row=3, column=0, sticky=tk.E+tk.W)
B2.grid(row=3, column=1, sticky=tk.E+tk.W)
B3.grid(row=3, column=2, sticky=tk.E+tk.W)
B4.grid(row=4, column=0, sticky=tk.E+tk.W)
B5.grid(row=4, column=1, sticky=tk.E+tk.W)
B6.grid(row=4, column=2, sticky=tk.E+tk.W)
label1 = tk.Entry(root, bd =8)
label1.grid(row=2, column=0, rowspan=1, columnspan=3, sticky=tk.E+tk.W)
label=tk.Text(root,background="yellow")
label.insert(index=0.0, chars="Enter values below\nand click search.\n")
label.grid(row=0, column=0,rowspan=1, columnspan=3, sticky=tk.E+tk.W)
root.mainloop()
I am trying to build a GUI in Python using Tkinter but the space for the inserted text label as "Enter values below\nand click search.\n" occupies about 6 blank rows. Please help me remove it. My current result using the code above is the left one, I want to have the right one image.
When you create the text widget, specify the number of lines you want it to display, for example:
label=tk.Text(root,background="yellow", height=3)
Failing to specify means it will default to 24, hence why it is so large in your program.
Ignoring the grid() mistake in your code.
You could correct the sizing issue by providing a weight and starting geometry size.
UPDATE:
If you provide weights to the proper rows and columns give your Text widget a default height of say 3 and tell the grid() on your Text widget to sticky="nsew" you can have your program start out the size you want and be able to resize evenly if you want to.
Take a look at the below code:
import tkinter as tk
root = tk.Tk()
# we want all 3 columns to resize evenly for the buttons so we provide
# a weight of 1 to each. We also want the first row where the text box is
# to resize so there is not unwanted behavior when resizing, so we set its weight to 1.
# keep in mind a weight of zero (default) will tell tkinter to not resize that row or column.
root.columnconfigure(0, weight=1)
root.columnconfigure(1, weight=1)
root.columnconfigure(2, weight=1)
root.rowconfigure(0, weight=1)
buttonOK = tk.Button(root, text='B1')
MCC = tk.Button(root, text='B2')
TID = tk.Button(root, text='B3')
CURRENCY = tk.Button(root, text='B4')
COUNTRY = tk.Button(root, text='B5')
RESPONSE = tk.Button(root, text='B6')
# corrected the variables being assigned a grid location
buttonOK.grid(row=3, column=0, sticky=tk.E+tk.W)
MCC.grid(row=3, column=1, sticky=tk.E+tk.W)
TID.grid(row=3, column=2, sticky=tk.E+tk.W)
CURRENCY.grid(row=4, column=0, sticky=tk.E+tk.W)
COUNTRY.grid(row=4, column=1, sticky=tk.E+tk.W)
RESPONSE.grid(row=4, column=2, sticky=tk.E+tk.W)
label1 = tk.Entry(root, bd =8)
label1.grid(row=2, column=0, rowspan=1, columnspan=3, sticky=tk.E+tk.W)
# added a height of 3 to the Text widget. to reduce its starting height
label=tk.Text(root,background="yellow", height=3)
label.insert(index=0.0, chars="Enter values below\nand click search.\n")
# added stick="nsew" so the text box will resize with the available space in the window.
label.grid(row=0, column=0,rowspan=1, columnspan=3, sticky="nsew")
root.mainloop()