In my application, I have two pages that are created when the App class instance is run. Within each page, I have an Entry to which I have applied the focus() method. The Login page (Page1 class) has an Entry widget called user_ent. I have set the focus by using the following code:
user_ent = ttk.Entry(login_frm, width=20,
textvariable=self.username_str)
user_ent.grid(row=0, column=1, pady=(20, 0), sticky=tk.W)
user_ent.focus()
If the Entry widget on Page 2 does not have the focus set (test_ent.focus() commented out), the Login page functions as intended. With the focus also set on the second page, it is the Page2 widget that has the focus.
test_ent = ttk.Entry(screen_frm)
test_ent.grid(column = 0, row = 1,pady=(30, 20), sticky = tk.N)
test_ent.focus()
I understand why this is occurring. My question is how can I set the focus to a specific Entry only when that page is displayed? I think the best place for this may be the show_frames() method in the App class.
show_frames() method in the App class.
def show_frame(self, page_name: str) -> None:
frame = self.frames[page_name]
frame.tkraise()
However, I am not sure how to implement this. I thought I could use an if statement, something like this:
def show_frame(self, page_name: str) -> None:
frame = self.frames[page_name]
if page_name == 'Page1':
frame.user_ent.focus()
elif page_name == 'Page2':
frame.test_ent.focus()
frame.tkraise()
When I select the Login page form the menu, I get the following error:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\wolfg\AppData\Local\Programs\Python\Python310\lib\tkinter\__init__.py", line 1921, in __call__
return self.func(*args)
File "e:\login_demo4.py", line 168, in <lambda>
label='Login', command=lambda: self.show_frame('Page1'))
File "e:\login_demo4.py", line 184, in show_frame
frame.user_ent.focus()
AttributeError: 'Page1' object has no attribute 'user_ent'
I thought the frame was the instance of the page frame from the self.frames dictionary (created in the App class). I guess that may not the case or my syntax is incorrect. Note that not every page would have a widget with the focus set. For example, the Splash page.
Here is the code for the complete application. Thanks in advance for your help.
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox
class Page0(tk.Frame): # Splash page
def __init__(self, parent, controller):
# constructor allows the use of the same
# args as the subclassed tk.Frame
tk.Frame.__init__(self, parent)
TITLE_FONT = ("Helvetica", 16, "bold")
self.columnconfigure(0, weight = 1)
self.rowconfigure(0, weight = 1)
screen_frm = tk.LabelFrame(self, text='Screen Frame')
screen_frm.grid(column=0, row=0, padx=10, pady=10, sticky=tk.NSEW)
screen_frm.columnconfigure(0, weight = 1)
screen_frm.rowconfigure(0, weight = 1)
screen_frm.rowconfigure(1, weight = 1)
title_lbl = tk.Label(screen_frm, text = '-- Splash Page --', font=TITLE_FONT)
title_lbl.grid(column = 0, row = 0, pady=(10, 0), sticky = tk.EW)
test_lbl = ttk.Label(screen_frm, text='-- Stuff Here --')
test_lbl.grid(column = 0, row = 1,pady=(30, 20), sticky = tk.N)
class Page1(tk.Frame): # Login page
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.username_str = tk.StringVar()
self.password_str = tk.StringVar()
self.display_str = tk.StringVar(value='Enter User ID and Password')
# Start page layout
self.columnconfigure(0, weight=1)
login_frm = tk.LabelFrame(self, text='Login')
login_frm.grid(column=0, row=0, padx=10, pady=10, sticky=tk.NSEW)
login_frm.columnconfigure(0, weight=1)
login_frm.columnconfigure(1, weight=1)
login_frm.rowconfigure(0, weight=1)
login_frm.rowconfigure(1, weight=1)
login_frm.rowconfigure(2, weight=1)
user_lbl = ttk.Label(login_frm, text='Username: ')
user_lbl.grid(row=0, column=0, padx=(20, 0), pady=(10, 0), sticky=tk.W)
user_ent = ttk.Entry(login_frm, width=20,
textvariable=self.username_str)
user_ent.grid(row=0, column=1, pady=(20, 0), sticky=tk.W)
user_ent.focus()
password_lbl = ttk.Label(login_frm, text='Password: ')
password_lbl.grid(row=1, column=0, padx=(20, 0), sticky=tk.W)
password_ent = ttk.Entry(
login_frm, width=20, textvariable=self.password_str)
password_ent.grid(row=1, column=1, pady=(5, 0), sticky=tk.W)
login_btn = ttk.Button(login_frm, text='Login',
width=20, command=self.login)
login_btn.grid(row=2, column=1, padx=(
0, 20), pady=(10, 20), sticky=tk.E)
display_lbl = ttk.Label(self, textvariable=self.display_str)
display_lbl.grid(row=1, column=0, columnspan=3)
# end page layout
def login(self):
pass
class Page2(tk.Frame): # Contains test_ent widget
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
# Class constant(s)
TITLE_FONT = ("Helvetica", 16, "bold")
self.columnconfigure(0, weight = 1)
self.rowconfigure(0, weight = 1)
screen_frm = tk.LabelFrame(self, text='Screen Frame')
screen_frm.grid(column=0, row=0, padx=10, pady=10, sticky=tk.NSEW)
screen_frm.columnconfigure(0, weight = 1)
screen_frm.rowconfigure(0, weight = 1)
screen_frm.rowconfigure(1, weight = 1)
# Create and place two Label widgets
title_lbl = tk.Label(screen_frm, text = '-- Page 2 --', font=TITLE_FONT)
title_lbl.grid(column = 0, row = 0, pady=(10, 0), sticky = tk.EW)
test_ent = ttk.Entry(screen_frm)
test_ent.grid(column = 0, row = 1,pady=(30, 20), sticky = tk.N)
test_ent.focus()
class App(tk.Tk):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.title('Login Demo')
self.geometry('300x300')
self.resizable(width=False, height=False)
self.columnconfigure(0, weight=1)
# Create menu bar
self.create_menu_frame(self)
# Create frame container
container = tk.Frame(self)
container.grid(column=0, row=0, sticky=tk.NSEW)
container.rowconfigure(0, weight=1)
container.columnconfigure(0, weight=1)
# Create a dictionary of frames
self.frames = {}
# Add the two page frames to the dictionary.
for pg_frm in (Page0, Page1, Page2):
page_name = pg_frm.__name__
frame = pg_frm(parent=container, controller=self)
self.frames[page_name] = frame
frame.grid(row=0, column=0, sticky=tk.NSEW)
# Display login page
self.show_frame('Page0')
def create_menu_frame(self, container: ttk.Frame) -> None:
# Create menu bar
menu_bar = tk.Menu(container)
# Create options for File menu
file_menu = tk.Menu(menu_bar, tearoff=0)
file_menu.add_command(
label='Login', command=lambda: self.show_frame('Page1'))
file_menu.add_command(
label='Page 2', command=lambda: self.show_frame('Page2'))
file_menu.add_separator()
file_menu.add_command(label='Exit', command=self.quit)
# Assign file menu list to the File option
menu_bar.add_cascade(label="File", menu=file_menu)
# Assign assign menu bar to the window
self.config(menu=menu_bar)
def show_frame(self, page_name: str) -> None:
frame = self.frames[page_name]
# if page_name == 'Page1':
# frame.user_ent.focus()
# elif page_name == 'Page2':
# frame.user_ent.focus()
frame.tkraise()
def main():
prog_app = App()
prog_app.mainloop()
if __name__ == '__main__':
main()
I have one possible solution to my focus issue. I created a set_focus() method in the App class.
def set_focus(self, page_name: str) -> None:
self.frames[page_name].set_initial_focus()
The set_intital_focus() method on the specified page then sets the focus for the Entry widget.
def set_initial_focus(self) -> None:
self.user_ent.focus()
The set_focus() is called in the show show_frame method on the App class.
def show_frame(self, page_name: str) -> None:
frame = self.frames[page_name]
self.set_focus(page_name)
frame.tkraise()
This works for the Entry widget on both pages. The only problem is that I had to include the set_initial_focus() also on the Splash page.
def set_initial_focus(self) -> None:
pass
Not happy about that. Are there better alternatives?