Home > other >  Issue using focus() for Entry widgets on a multiple page Tkinter application
Issue using focus() for Entry widgets on a multiple page Tkinter application

Time:12-24

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()

CodePudding user response:

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?

CodePudding user response:

You can use tkinter virtual event. The main app can send a virtual event to the raised frame, if the raised frame supports this virtual event, it can do whatever it wants inside the event callback.

Below is the modified code to use the virtual event:

...
class Page1(tk.Frame):
    def __init__(self, parent, controller):
        ...
        # use instance variable instead of local variable
        self.user_ent = ttk.Entry(...)
        ...
        # bind a virtual event
        self.bind("<<Raised>>", self.on_raised)

    def on_raised(self, event):
        self.user_ent.focus()

    ...

class Page2(tk.Frame):
    def __init__(self, parent, controller):
        ...
        # use instance variable instead of local variable
        self.test_ent = ttk.Entry(...)
        ...
        # bind a virtual event
        self.bind("<<Raised>>", self.on_raised)

    def on_raised(self, event):
        self.test_ent.focus()

class App(tk.Tk):
    ...

    def show_frame(self, page_name: str) -> None:
        frame = self.frames[page_name]
        frame.tkraise()
        # send a virtual event to the raised frame
        frame.event_generate("<<Raised>>")

...
  • Related