This question already has answers here:
Using buttons in Tkinter to navigate to different pages of the application?
(3 answers)
Closed 2 years ago.
So for my project when I click a button - the page must navigate to another. And when I do this each time it creates a different window (hence resulting in a lot of windows) or keeps stacking on top of each other
Currently I have a admin/student choice page. And if I click on the student page it will run the student log in function and this applies the same for the admin.
I want to keep the background of the windows consistent as well - but I am unable to do so and on top of that I have no clue on how to navigate via the pages - I have looked at a few videos but I did not really understand it.
contains 2 buttons admin and student which should navigate to different "windows" when clicked
def mainPage():
""""Starting Page - which navigates to the approporate login, when user logs it navigates back to this window"""
root = Tk()
root.geometry('1024x600')
load = PIL.Image.open('Image//book.jpg')
render = ImageTk.PhotoImage(load)
img =Label(root, image = render)
img.place(x=0, y=0)
my_font = font.Font(size = 15)
buttonFrame = Frame(root, width = 600, height=300, bg="grey")
buttonFrame.grid(row = 0, column = 0, padx= 10, pady=2, sticky="")
label = Label(buttonFrame, text = "Pick a Log-in", bg = "white", font=my_font).grid(row = 1, column=1, sticky="", padx=10, pady=10)
button = Button(buttonFrame, text = "Admin",command = adminLogin, width=30, height = 5, font=my_font).grid(row=2, column=1, sticky = "", padx= 10, pady=10)
button2 = Button(buttonFrame, text = "Student", command = studentLogin, width = 30, height=5, font=my_font).grid(row = 3, column=1, sticky="", padx = 10, pady=10)
root.mainloop()
def studentLogin():
root = Tk()
root.geometry('1024x600')
load = PIL.Image.open('Image//book.jpg')
render = ImageTk.PhotoImage(load)
img =Label(root, image = render)
img.place(x=0, y=0)
my_font = font.Font(size = 15)
buttonFrame = Frame(root, width = 600, height=600, bg="grey")
buttonFrame.grid(row = 0, column = 0, padx= 10, pady=2, sticky="")
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
label = Label (buttonFrame, text="Welcome", font=('Helvetica', 18, 'bold'), bg="white").grid(row = 1, column = 1,sticky="", padx=10, pady=10)
label = Label(buttonFrame, text="Username", font = my_font).grid(row=2, column=1, padx=10, pady=10)
usernameEntry = Entry(buttonFrame).grid(row=2, column=2, padx=10, pady=10)
root.mainloop()
Maybe try with PySimpleGUI :)
It has a convenient window.Hide() or window.UnHide() for these cases. Its also a great library for developping GUIs, good documentation, great support, and easy to pick up.
For tkinter, you could try with
root.withdraw()
and
root.update()
root.deiconify()
Related
This is a part of code from my school project.
from tkinter import *
from tkinter.font import Font
class student_window():
def __init__(self, master):
self.student_win = master
#window = Toplevel(self.master)
self.student_win.geometry("1280x720")
self.header1Font = Font(family='Helvetica', size=20)
self.optionFont = Font(family='Sans Serrif', size=20)
self.student_win.focus()
self.show_window()
def show_window(self):
print("ookk")
self.student_win.title("Student Window")
self.option_frame = Frame(self.student_win, width=200, height=720)
lbl_header = Label(self.option_frame,text="EXAMINATION", font=self.header1Font, fg='white', bg='#172D44').grid(row=0,column=0, sticky=NSEW)
lbl_welcome = Label(self.option_frame, text="Welcome,", fg='#E9F1F7', bg='#2A3F54').grid(row=1,column=0)
lbl_username = Label(self.option_frame, text="Username", fg='white', bg='#2A3F54').grid(row=2,column=0)
lbl_header2 = Label(self.option_frame, text="STUDENT CORNER", fg='white', bg='#2A3F54').grid(row=3, column=0)
self.btn_tests = Button(self.option_frame, text="Attempt Exam", fg='#E9F1F7', bg='#35495D', relief=FLAT)
self.btn_tests.grid(row=4,column=0, sticky=NSEW)
self.btn_attempts = Button(self.option_frame, text="Attempts", fg='#E9F1F7', bg='#2A3F54', relief=FLAT)
self.btn_attempts.grid(row=5, column=0, sticky=NSEW)
self.btn_result = Button(self.option_frame, text="Result", fg='#E9F1F7', bg='#2A3F54', relief=FLAT)
self.btn_result.grid(row=6, column=0, sticky=NSEW)
self.btn_goBack = Button(self.option_frame, text="Go Back", fg='#E9F1F7', bg='#2A3F54', relief=FLAT)
self.btn_goBack.grid(row=7, column=0, sticky=NSEW)
self.option_frame.configure(bg='#2A3F54')
self.option_frame.grid(row=0, column=0)
self.option_frame.grid_propagate(0)
self.main_frame = Frame(self.student_win, width=880, height=720)
self.main_result_frame = Frame(self.main_frame)
self.main_result_frame.grid(row=0,column=0)
self.attempts_frame = Frame(self.main_frame)
self.attempts_frame.grid(row=0, column=0)
self.test_frame = Frame(self.main_frame)
lbl_test = Label(self.test_frame, text="In test frame").pack()
self.test_frame.grid(row=0,column=0)
self.main_frame.grid(row=0,column=1)
self.main_frame.grid_propagate(0)
self.info_frame = Frame(self.student_win, width=200, height=720)
self.btn_username = Button(self.info_frame, text="Username", relief=FLAT)
self.btn_username.grid(row=0,column=0)
self.userInfo_frame = Frame(self.info_frame)
self.info_frame.grid(row=0, column=2)
self.info_frame.grid_propagate(0)
root = Tk()
student_window(root)
root.mainloop()
And it looks something like this.
The Student Panel for my project
The whole window is divided into three frames and want to expand each label and button of the left frame(self.option_frame) to fill it horizontally. I tried doing sticky=EW and sticky=NSEW but still some space is left. How do I fix that?
You need to call self.option_frame.columnconfigure(0, weight=1) to make column 0 to use all the available horizontal space.
I was just trying some things and what I have found to be working is to make the label width bigger than than the frame then anchoring the text to the left.
I am currently working on Tkinter codes. I happen to need to create new windows exactly similar to root but my codes do not perfectly work well. The title does not appear on the new windows. This is an excerpt:
from tkinter import *
#New Window
def New_page():
window()
#Main Window
def window():
window = Tk()
window.resizable(0,0)
window.configure(background='grey')
window.state('zoomed')
window.geometry("2000x4000")
#Title Frame
TITLE_FRAME = Frame(window, relief = GROOVE, bg = "black", width=2000, height=160).grid(row=0, column=0, columnspan = 150, rowspan = 30, sticky=E+W)
Label(TITLE_FRAME, text= 'THIS IS THE TITLE PART', fg='sky blue', bg='black', font='none 40 bold',
borderwidth=5).grid(row=0,column=10)
#New Window Button
ENTRY_FRAME = Frame(window, bg='sky blue', relief = SUNKEN)
ENTRY_FRAME.grid(row=40, column=0, columnspan=20, padx=15, pady=15)
Label(ENTRY_FRAME, text= 'SELECT THE APPROPRIATE DETAILS:',
bg = 'sky blue', fg='black', font='none 10 bold', borderwidth=5).grid(row=0, column=0, columnspan=20)
NEW_WINDOW = Button(ENTRY_FRAME, text="NEW WINDOW", font='None 8 bold', width=30, command=New_page, fg= 'black', bg='white')
NEW_WINDOW.grid(row = 3, column = 0, columnspan = 3, padx = 10, pady = 10)
window.mainloop()
#Calling the Tkinter function
window()
Like in the comments, Toplevel is the way to go for this one. What I changed:
Moved making the window object globally
Renamed the function to make it makeWindow(master)
makeWindow(master) now takes in a master widget. This will make
all of the widgets made there be part of the master window.
New_page was modified to make a new Toplevel() widget
TITLE_FRAME is now made first and then grid is called on it
(EDIT) These edits fix the problems with the original window closing the program.
We want to remap the closing behaviour to act how we want. This is done with window.protocol("WM_DELETE_WINDOW",callback). We must define the callback function, in this case, deleteWindow(win).
What delete window does is take a window, and if it is the root window it hides it. Otherwise, it deletes the window. I used window.withdraw() in my code, but there's probably a better way to do it.
The way it knows if it should close the program is by keeping track of the number of active windows in the activeWindows variable. When a window is created, the number increases, when delete it decreases. If the number of active windows is 0, we can delete the main window to close the program cleanly.
The way we bind the deleteWindow(win) callback is through an anonymous function. Normally, the protocol mentioned above does not give any arguments, but we want to know which window called the function. To do this, whenever we bind the destruction of the window, we define a anonymous function using lambda that calls deleteWindow.
.
from tkinter import *
window = Tk()
window.resizable(0,0)
window.configure(background='grey')
window.state('zoomed')
window.geometry("2000x4000")
activeWindows = 1
def deleteWindow(win):
if win == window:
window.withdraw()
else:
win.destroy()
global activeWindows
activeWindows-=1
if activeWindows <= 0:
window.destroy()
#New Window
def New_page():
global activeWindows
activeWindows+=1
NEW_WINDOW=Toplevel(background='grey')
NEW_WINDOW.geometry("2000x4000")
NEW_WINDOW.protocol("WM_DELETE_WINDOW",lambda:deleteWindow(NEW_WINDOW))
makeWindow(NEW_WINDOW)
#Main Window
def makeWindow(master):
#Title Frame
TITLE_FRAME = Frame(master, relief = GROOVE, bg = "black", width=2000, height=160)
TITLE_FRAME.grid(row=0, column=0, columnspan = 150, rowspan = 30, sticky=E+W)
Label(TITLE_FRAME, text= 'THIS IS THE TITLE PART', fg='sky blue', bg='black', font='none 40 bold',
borderwidth=5).grid(row=0,column=10)
#New Window Button
ENTRY_FRAME = Frame(master, bg='sky blue', relief = SUNKEN)
ENTRY_FRAME.grid(row=40, column=0, columnspan=20, padx=15, pady=15)
Label(ENTRY_FRAME, text= 'SELECT THE APPROPRIATE DETAILS:',
bg = 'sky blue', fg='black', font='none 10 bold', borderwidth=5).grid(row=0, column=0, columnspan=20)
NEW_WINDOW = Button(ENTRY_FRAME, text="NEW WINDOW", font='None 8 bold', width=30, command=New_page, fg= 'black', bg='white')
NEW_WINDOW.grid(row = 3, column = 0, columnspan = 3, padx = 10, pady = 10)
window.protocol("WM_DELETE_WINDOW",lambda: deleteWindow(window))
#Calling the Tkinter function
makeWindow(window)
window.mainloop()
Hey guys I have a problem and I have been searching for answers but I can't seem to solve my problem.
I want to move my "Enter" and "Quit" buttons more to the bottom of my window but I'm not sure I understand the grid function.
I made two frames inside my window and the buttons are in the bottom frame but I can't seem to get them lower with the row function.
I'm like just started with Python and have no experience with programming so this is my first project.(please don't laugh)
#import tkinder module
from tkinter import *
#make frame
root = Tk()
root.geometry("600x300")
top_frame = Frame(root)
bottom_frame = Frame(root)
top_frame.pack()
bottom_frame.pack()
#headline
headline = Label(top_frame, text="Welcome to PrintAssistant.", bg='blue', fg='white')
headline.config(font=('Courier', 27))
headline.grid(padx=10, pady=10)
Name = Label(bottom_frame, text="Name:", fg='blue')
Name.config(font=('Courier', 20))
Name.grid(row=1)
Password = Label(bottom_frame, text="Password:", fg='blue')
Password.config(font=('Courier', 20))
Password.grid(row=2)
Name_entry = Entry(bottom_frame)
Name_entry.grid(row=1, column=1)
Password_entry = Entry(bottom_frame)
Password_entry.grid(row=2, column=1)
#enter_button
enter_button = Button(bottom_frame, text="Enter", bg='blue', fg='white')
enter_button.config(height = 2, width = 15)
enter_button.grid(sticky = S)
#quit_button
quit_button = Button(bottom_frame, text="Quit", bg="blue", fg="white")
quit_button.config(height = 2, width = 15)
quit_button.grid(sticky = S)
root.mainloop()
Here is a quick and simple way to have your buttons sit at the bottom of your program.
First move enter_button and quit_button to the root window. A frame is not needed here.
then add root.rowconfigure() and apply a weight of 1 to the row the buttons are on. Configuring the row with a weight allows that row to expand and contract with the frame or window it is placed in. In this case we placed the buttons in root so we configured root. You could do the same to a frame but that is just adding another layer that is not needed for something simple like this.
Here is a section of code you can replace in your program and the result should be what you are looking for. Note I placed the buttons side by side but you can accomplish a top to bottom with the same method.
Edit:
I forgot to add the columnspan part. You also need to add a columnspan of the other frames so you can have your buttons placed centered when side by side. A columspan is used to tell the program how many columns that widget is going to be taking up. the same can be done with rows as well.
top_frame.grid(row = 0, column = 0, columnspan = 2)
bottom_frame.grid(row = 1, column = 0, columnspan = 2)
root.rowconfigure(2, weight = 1)
#enter_button
enter_button = Button(root, text="Enter", bg='blue', fg='white')
enter_button.config(height = 2, width = 15)
enter_button.grid(row = 2, column=0, sticky = 'se')
#quit_button
quit_button = Button(root, text="Quit", bg="blue", fg="white")
quit_button.config(height = 2, width = 15)
quit_button.grid(row = 2, column=1, sticky = 'sw')
If you want to keep the enter button on top of the quit button then you could do the following.
top_frame.grid(row = 0, column = 0) # remove columnspan or set it to 1
bottom_frame.grid(row = 1, column = 0)# remove columnspan or set it to 1
root.rowconfigure(2, weight = 1)
root.rowconfigure(3, weight = 0)
#enter_button
enter_button = Button(root, text="Enter", bg='blue', fg='white')
enter_button.config(height = 2, width = 15)
enter_button.grid(row = 2, column = 0, sticky = 's')
#quit_button
quit_button = Button(root, text="Quit", bg="blue", fg="white")
quit_button.config(height = 2, width = 15)
quit_button.grid(row = 3, column = 0, sticky = 's')
If you are just trying to place a little space between your entry fields and buttons you could use an spacer label. Something like this:
#empty row spacers
spacer1 = Label(bottom_frame, text = "")
spacer1.grid(row = 3)
spacer2= Label(bottom_frame, text = "")
spacer2.grid(row = 4)
#enter_button
enter_button = Button(bottom_frame, text="Enter", bg='blue', fg='white')
enter_button.config(height = 2, width = 15)
enter_button.grid(row = 5, column=1)
#quit_button
quit_button = Button(bottom_frame, text="Quit", bg="blue", fg="white")
quit_button.config(height = 2, width = 15)
quit_button.grid(row = 6, column=1)
I think you can do it by adding an extra Frame and set it to the bottom. After that put the Enter and Quit buttons on that frame. I have modified your code and you can try it.
#import tkinder module
from tkinter import *
#make frame
root = Tk()
root.geometry("600x300")
top_frame = Frame(root)
center_frame = Frame(root)
bottom_frame = Frame(root)
top_frame.pack()
center_frame.pack()
bottom_frame.pack(side = BOTTOM, fill = BOTH)
#headline
headline = Label(top_frame, text="Welcome to PrintAssistant.", bg='blue', fg='white')
headline.config(font=('Courier', 27))
headline.grid(padx=10, pady=10)
Name = Label(center_frame, text="Name:", fg='blue')
Name.config(font=('Courier', 20))
Name.grid(row=1)
Password = Label(center_frame, text="Password:", fg='blue')
Password.config(font=('Courier', 20))
Password.grid(row=2)
Name_entry = Entry(center_frame)
Name_entry.grid(row=1, column=1)
Password_entry = Entry(center_frame)
Password_entry.grid(row=2, column=1)
#enter_button
enter_button = Button(bottom_frame, text="Enter", bg='blue', fg='white')
enter_button.config(height = 2, width = 15)
enter_button.pack()
#quit_button
quit_button = Button(bottom_frame, text="Quit", bg="blue", fg="white")
quit_button.config(height = 2, width = 15)
quit_button.pack()
root.mainloop()
Please leave a comment if it worked for you :)
My gui layout
looks almost nothing like what I expect
so I assume there are some basics that I don't understand.
I assumed that frames contain their own 'grid space' (row, column) but the behavior I see doesn't bear that out, and I'm at a loss for getting things working the way I want for the top frame. My labels are supposed to be on the same row L to R, under a 'frame label' that spans the entire frame - except they don't. I want the actual to look more like the goal jpg, and I want to use grid to do it.
You can just see one of the entry fields to the right of the green frame. Why is it going there ?
from Tkinter import *
root = Tk()
root.title('Model Definition')
root.resizable(width=FALSE, height=FALSE)
root.geometry('{}x{}'.format(460, 350))
top_frame = Frame(root, bg='cyan', width = 450, height=50, pady=3).grid(row=0, columnspan=3)
Label(top_frame, text = 'Model Dimensions').grid(row = 0, columnspan = 3)
Label(top_frame, text = 'Width:').grid(row = 1, column = 0)
Label(top_frame, text = 'Length:').grid(row = 1, column = 2)
entry_W = Entry(top_frame).grid(row = 1, column = 1)
entry_L = Entry(top_frame).grid(row = 1, column = 3)
#Label(top_frame, text = '').grid(row = 2, column = 2)
center = Frame(root, bg='gray2', width=50, height=40, padx=3, pady=3).grid(row=1, columnspan=3)
ctr_left = Frame(center, bg='blue', width=100, height=190).grid(column = 0, row = 1, rowspan = 2)
ctr_mid = Frame(center, bg='yellow', width=250, height=190, padx=3, pady=3).grid(column = 1, row=1, rowspan=2)
ctr_right = Frame(center, bg='green', width=100, height=190, padx=3, pady=3).grid(column = 2, row=1, rowspan=2)
btm_frame = Frame(root, bg='white', width = 450, height = 45, pady=3).grid(row = 3, columnspan = 3)
btm_frame2 = Frame(root, bg='lavender', width = 450, height = 60, pady=3).grid(row = 4, columnspan = 3)
root.mainloop()
So specifically, where did my labels and Entry widgets go, and how do I get them to look more like the goal (top frame, the rest are for later).
I assumed that frames contain their own 'grid space'
That is a correct assumption.
You can just see one of the entry fields to the right of the green
frame. Why is it going there ?
The problem starts here:
top_frame = Frame(root, ...).grid(row=0, ...)
In python, x = y().z() will always set x to the result of .z(). In the case of top_frame = Frame(...).grid(...), grid(...) always returns None so top_frame will be None. That causes every widget that you think is going into the top frame to actually go in the root window.
Solution Overview
As a general rule of thumb, you should never call grid, pack or place as part of the same statement that creates the widget. Partially it is for this exact behavior that you're experiencing, but also because I think it makes your code harder to write and harder to maintain over time.
Widget creation and widget layout are two different things. In my experience, layout problems are considerably easier to debug when you group your layout commands together.
Also, you should be consistent when using grid and always put the options in the same order so you can more easily visualize the layout. And finally, when using grid you should get in the habit of always specifying the sticky option, and always give one row and one column in each containing frame a non-zero weight.
Solution Example
Here's how I would write your code. It's much longer, but much easier to understand.
from Tkinter import *
root = Tk()
root.title('Model Definition')
root.geometry('{}x{}'.format(460, 350))
# create all of the main containers
top_frame = Frame(root, bg='cyan', width=450, height=50, pady=3)
center = Frame(root, bg='gray2', width=50, height=40, padx=3, pady=3)
btm_frame = Frame(root, bg='white', width=450, height=45, pady=3)
btm_frame2 = Frame(root, bg='lavender', width=450, height=60, pady=3)
# layout all of the main containers
root.grid_rowconfigure(1, weight=1)
root.grid_columnconfigure(0, weight=1)
top_frame.grid(row=0, sticky="ew")
center.grid(row=1, sticky="nsew")
btm_frame.grid(row=3, sticky="ew")
btm_frame2.grid(row=4, sticky="ew")
# create the widgets for the top frame
model_label = Label(top_frame, text='Model Dimensions')
width_label = Label(top_frame, text='Width:')
length_label = Label(top_frame, text='Length:')
entry_W = Entry(top_frame, background="pink")
entry_L = Entry(top_frame, background="orange")
# layout the widgets in the top frame
model_label.grid(row=0, columnspan=3)
width_label.grid(row=1, column=0)
length_label.grid(row=1, column=2)
entry_W.grid(row=1, column=1)
entry_L.grid(row=1, column=3)
# create the center widgets
center.grid_rowconfigure(0, weight=1)
center.grid_columnconfigure(1, weight=1)
ctr_left = Frame(center, bg='blue', width=100, height=190)
ctr_mid = Frame(center, bg='yellow', width=250, height=190, padx=3, pady=3)
ctr_right = Frame(center, bg='green', width=100, height=190, padx=3, pady=3)
ctr_left.grid(row=0, column=0, sticky="ns")
ctr_mid.grid(row=0, column=1, sticky="nsew")
ctr_right.grid(row=0, column=2, sticky="ns")
root.mainloop()
Result:
variable = Widget(...).grid() assigns None to variable because grid()/pack()/place() return None
use
variable = Widget(...)
variable.grid() # .pack() .place()
In my Python GUI script, I have a pop up window, and there is text area widget on the pop-up window, users can input some content inside, and then click one button on the pop-up window to get the input text.
But it seems that in the defined function, the widget on the pop-up window can not be accessed. the code goes as following:
from Tkinter import *
def Add_Content():
content = ent_new.get("1.0","end")
print content
def Add_Task():
task_index = 1
new_window = Toplevel()
label1 = Label(new_window, text="New Goal:")
label1.grid(row = 0, column = 0)
ent_new = Text(new_window, bg= "white", height=5, width= 30)
ent_new.grid(row=0,column =1,padx=5, pady=5)
bu_new = Button( new_window,text="Add", command = Add_Content)
bu_new.grid(row=0, column =2)
new_window.focus_force()
master = Tk()
group = LabelFrame(master, text="Operation", padx=5, pady=5, relief = RAISED)
group.grid(row=0,column= 0, padx=10, pady=10, sticky=N)
bu_add = Button(group, text = "Add Task",width = 15, command = Add_Task)
bu_add.grid(row=0,column=0)
mainloop()
in the above script, the ent_new can not be found in function Add_Content
The problem is that ent_new is in another namespace. You can solve it by making Add_Content recieve ent_new in the arguments like that,
def Add_Content(my_ent):
content = my_ent.get("1.0","end")
print content
and then using a wrapper function (lambda) when passing it to Button
bu_new = Button( new_window,text="Add", command = lambda: Add_Content(ent_new))
Without adding a class and the concept of self and parent, you can use lambda given in the first answer or you can use a global variable.
Note: In python circles globals are rather frowned upon but they work and get the job done.
from Tkinter import *
global ent_new
def Add_Content():
content = ent_new.get("1.0","end")
print content
def Add_Task():
global ent_new
task_index = 1
new_window = Toplevel()
label1 = Label(new_window, text="New Goal:")
label1.grid(row = 0, column = 0)
ent_new = Text(new_window, bg= "white", height=5, width= 30)
ent_new.grid(row=0,column =1,padx=5, pady=5)
bu_new = Button( new_window,text="Add", command = Add_Content)
bu_new.grid(row=0, column =2)
new_window.focus_force()
master = Tk()
group = LabelFrame(master, text="Operation", padx=5, pady=5, relief = RAISED)
group.grid(row=0,column= 0, padx=10, pady=10, sticky=N)
bu_add = Button(group, text = "Add Task",width = 15, command = Add_Task)
bu_add.grid(row=0,column=0)
mainloop()