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:
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 have a simple label and entry field that would:
1) Create a static label and clear the entry field after confirmation button click
2) Clear static label after reset button click
Is there any way to overwrite the entry field with a static label of the user input on the confirmation click instead of creating a new static label? And overwriting the static label with an empty entry field on the reset click?
Thank you for the help in advance.
from tkinter import *
root = Tk()
frame1 = Frame(root)
frame1.pack()
def reset():
set_cname.destroy()
cbtn['state'] = NORMAL
def confirm():
global set_cname
text1="Customer Name: " + entry1.get()
set_cname = Label(frame1, text=text1)
set_cname.grid(row=3, column=0, columnspan=1)
entry1.delete(0, 'end')
cbtn['state'] = DISABLED
cname = Label(frame1, text="Customer Name: ").grid(padx=5, pady=5, columnspan=2, sticky=W)
entry1 = Entry(frame1)
entry1.grid(row=0, column=2, padx=5)
cbtn = Button(frame1, text="Confirm", command=confirm, width=20)
cbtn.grid(row=1, column=4, padx=5, pady=5)
rbtn = Button(frame1, text="Reset Names", command=reset, width=20)
rbtn.grid(row=2, column=4, padx=5, pady=5)
root.mainloop()
You can replace the Entry with a Label by first creating both and then using pack() to switch between them. The trick is to not let their different sizes affect the application layout, which can be accomplished by disabling size propagation.
In my example I create a new frame (entry_frame) with a fixed size and then disable size propagation (.pack_propagate(False)). Then I use this new frame to contain the Entry/Label. Im giving the entry_frame the bg color khaki to let you see exactly where it is.
I fiddled a bit with the column numbers also.
from tkinter import *
root = Tk()
frame1 = Frame(root)
frame1.pack()
def reset():
text_label.pack_forget()
entry1.pack()
cbtn['state'] = NORMAL
def confirm():
global set_cname
entry1.pack_forget()
text_label.config(text=entry1.get())
text_label.pack(side='left')
entry1.delete(0, 'end')
cbtn['state'] = DISABLED
cname = Label(frame1, text="Customer Name: ")
cname.grid(row=0, column=0, padx=5, pady=5, sticky=W)
entry_frame = Frame(frame1, width=130, height=20, bg='khaki')
entry_frame.grid(row=0, column=1, padx=5, pady=5, sticky='nsew')
entry_frame.pack_propagate(False) # Disable size propagation
entry1 = Entry(entry_frame) # Customer name entry
entry1.pack()
text_label = Label(entry_frame) # Label to hold customer name
cbtn = Button(frame1, text="Confirm", command=confirm, width=20)
cbtn.grid(row=1, column=2, padx=5, pady=5)
rbtn = Button(frame1, text="Reset Names", command=reset, width=20)
rbtn.grid(row=2, column=2, padx=5, pady=5)
root.mainloop()
Be aware that this solution will be sensitive to font size changes.
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()
I'm having an alignment issue with radio buttons. I want three columns of form elements. For some reason, when I add radio buttons to the form, they appear to take up space for a new column on the left. I was hoping for a simple grid layout with each cell having equal size. That doesn't appear to be the case. Any suggestions would be greatly appreciated!
Here is part of my code:
self._mode_state = StringVar()
self._mode_radio_timelapse = Radiobutton(self, text="Timelapse", command=self._transition(), value=self._timelapse_mode, variable=self._mode_state)
self._mode_radio_continuous = Radiobutton(self, text="Continuous", command=self._transition(), value=self._continuous_mode, variable=self._mode_state)
self._mode_radio_ramphold = Radiobutton(self, text="Ramp and Hold", command=self._transition(), value=self._ramp_hold_mode, variable=self._mode_state)
self._mode_radio_timelapse.grid(row=0, column=0, pady=10)
self._mode_radio_continuous.grid(row=0, column=1, pady=10)
self._mode_radio_ramphold.grid(row=0, column=2, pady=10)
image_set_label = Label(text="Image Set Type: ")
image_set_label.grid(row=1, column=0, pady=10)
self._image_set_type = Entry()
self._image_set_type.insert(0, "Ramp")
self._image_set_type.grid(row=1, column=1, pady=10, columnspan=2)
The widgets are not all on the same grid. The radio buttons are specifically set with a parent of self, but your Label and Entry widgets are not created with any parent so the parent defaults to the root object.
Here's the fix:
image_set_label = Label(self, text="Image Set Type: ") # made self parent
image_set_label.grid(row=1, column=0, pady=10)
self._image_set_type = Entry(self) # made self parent
self._image_set_type.insert(0, "Ramp")
self._image_set_type.grid(row=1, column=1, pady=10, columnspan=2)
This is my code so far:
master = Tk()
Label(master, text="Input:").grid(row=0)
e1 = Entry(master, width = 100)
e1.grid(row=0, column=1)
Button(master, text='Q', command=q_pressed).grid(row=3, column=2,
sticky=W, padx = 4, pady=4)
Button(master, text='C', command =c_input).grid(row=3, column=1,
sticky=W, padx = 0, pady=4)
Button(master, text='Confirm', command=parse_input).grid(row=3, column=0,
sticky=W, padx = 4, pady=4)
I am trying to make it so that "Q" button is next to the "C" button (like how "C" is next to the "Confirm" button) but it instead places it where the Entry widget ends.
I understand this is a grid management issue. How do I set up a layout so row = 0, has text and entry widget and row=3 has its own independent columns (0,1,2) for the buttons?
Your problem is that the Entry e1 is very long and contains 100 characters, but it spans only one column. You can use columnspan to define the number of used columns for e1 in the grid command. Following example with columnspan = 30 works for me. Decide yourself, what would be a reasonable value for columnspan.
from Tkinter import *
master = Tk()
Label(master, text="Input:").grid(row=0)
e1 = Entry(master, width = 100)
e1.grid(row=0, column=1, columnspan=30)
Button(master, text='Q').grid(row=3, column=2)
Button(master, text='C').grid(row=3, column=1)
Button(master, text='Confirm').grid(row=3, column=0)
mainloop()
There is possibly more than one way to perform the layout you want, I choose to present a clean one. Your Entry widget occupies a large horizontal space in the second column of the first row, thus placing widgets below it in the next row is going to cause you trouble. Since you know you will have 3 buttons below it, you could make this Entry span two columns (columnspan=2 while gridding it) and play with that while gridding the next row. But I prefer a different approach, which also produces a better layout overall. What you want is to grid a Frame in the second row that takes two columns (since in the row above it you have two widgets). Then you grid your buttons on this new Frame.
This turns your code into:
import Tkinter
master = Tkinter.Tk()
Tkinter.Label(master, text="Input:").grid(row=0, column=0)
Tkinter.Entry(master, width = 100).grid(row=0, column=1)
frame = Tkinter.Frame()
frame.grid(row=1, column=0, columnspan=2, sticky='w')
Tkinter.Button(frame, text='Confirm').grid(row=0, column=0, sticky='w')
Tkinter.Button(frame, text='C').grid(row=0, column=1, sticky='w')
Tkinter.Button(frame, text='Q').grid(row=0, column=2, sticky='w')
master.mainloop()
enter code here{ master = Tk()
Label(master, text="Input:" ).grid(row=0)
e1 = Entry(master, width=100)
e1.grid(row=0, column=1, columnspan=10)
Button(master, text='Q', width=5, command=q_pressed).grid(row=3, column=1, padx=0, ipadx=5, pady=4, sticky=E)
Button(master, text='C', width=5, command =c_input).grid(row=3, column=1, padx=0, ipadx=5, pady=4, sticky=W)
Button(master, text='Confirm', width=10, command=parse_input).grid(row=3, column=0, padx=0, pady=4, sticky=W)
mainloop()}
Since you wanted 100 char for entry string I based my cell/column width on 10 for simple math division. Set my columnspan to 10. I got rid of padx since it pertains to border width and used ipadx which pertains to inner text width. Made entry set to 10 columns wide of 10 char. Used "input" text as 1 column. This will allow me to use 10 char or one column for text "confirm" and I can break column 1 in half by making button "c" & "q" 5 char wide using width=5. Then by using justfy put "c" West and "q" East.
There are many ways this is just easiest in my mind.