简体   繁体   English

在多页 Tkinter 应用程序上将 focus() 用于 Entry 小部件的问题

[英]Issue using focus() for Entry widgets on a multiple page Tkinter application

In my application, I have two pages that are created when the App class instance is run.在我的应用程序中,我有两个页面是在 App 类实例运行时创建的。 Within each page, I have an Entry to which I have applied the focus() method.在每个页面中,我都有一个应用了 focus() 方法的条目。 The Login page (Page1 class) has an Entry widget called user_ent.登录页面(Page1 类)有一个名为 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.如果第 2 页上的条目小部件没有设置焦点(test_ent.focus() 被注释掉),登录页面将按预期运行。 With the focus also set on the second page, it is the Page2 widget that has the focus.由于焦点也设置在第二页上,因此具有焦点的是 Page2 小部件。

        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.我认为最好的地方可能是 App 类中的 show_frames() 方法。

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:我想我可以使用 if 语句,像这样:

    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).我认为框架是 self.frames 字典中的页面框架实例(在 App 类中创建)。 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.我在 App 类中创建了一个 set_focus() 方法。

    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.然后,指定页面上的 set_intital_focus() 方法为 Entry 小部件设置焦点。

    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. set_focus() 在 App 类的 show show_frame 方法中被调用。

    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.唯一的问题是我还必须在 Splash 页面上包含 set_initial_focus() 。

   def set_initial_focus(self) -> None:
       pass 

Not happy about that.对此不高兴。 Are there better alternatives?有更好的选择吗?

You can use tkinter virtual event .您可以使用 tkinter虚拟事件 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>>")

...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM