Search code examples
pythontkinterttk

Display which widget has focus


Before you read: I am a complete novice at programming let alone Python. I don't expect solutions. I will appreciate it even if I just get a pointer in the right direction. If you feel the way I've requested help is unrefined, please let me know so next time I need help, I'll do a better job at asking.

Goal: I'm making a program to test how to get a program to respond depending on which widget has to focus on. Currently, I'm just having it respond by displaying 'what' has focus. Eventually, once I've figured this out I can implement what I've learnt into another program I'm working on was focusing on an entry field will result in the field clearing of all input. So naturally, I will want this program to not only change the label but the entry widget too. That will be for later.

Progress: I've managed to have the program print which widget has a focus in both the terminal and in a label widget.

Problem: My issue is that the message is ugly, and I want it to look neater.

Example of the problem: Instead of saying "Entry has focus", it says ".!frame.!entry {has focus}"

I have tried: Checking if other's have made similar programs. The only one I found was how I made this much progress but it didn't make the text nicer. I've also done research on various sites on how Python handles certain commands I've used in this program. I am a novice with programming so I admit I'm not completely sure what the best way to run this program is and so trying to research it is difficult for me.

Code:

# Call tkinter tools
from tkinter import *
from tkinter import ttk

"""
TODO:
Add different widgets that can 'gain' focus.
Print in the terminal which widget has focus.
Change entry and label text depending on which widget has focus.
"""

# Develop the window and frame
root = Tk()
root.title("Focus Test")
root.columnconfigure(0, weight = 1)
root.rowconfigure(0, weight = 1)

frame = ttk.Frame(root, padding = "1 1 1 1")
frame.grid(column = 0, row = 0, sticky = (N, S, E, W))

# Resizes columns in frame
for col in range(1, 3):
    frame.columnconfigure(col, weight = 1)

# Resizes rows in frame
for row in range(1, 3):
    frame.rowconfigure(row, weight = 1)

# Add response label
foc_labvar = StringVar()
foc_labvar.set("No focus")
foc_lab = ttk.Label(frame, width = 7, textvariable = foc_labvar)
foc_lab.grid(column = 2, row = 2, sticky = (W, E))

# Add entry box
foc_entvar = StringVar()
foc_entvar.set("Entry widget")
foc_ent = ttk.Entry(frame, width = 7, textvariable = foc_entvar)
foc_ent.grid(column = 1, row = 1, sticky = (W, E))

# Add button
foc_butvar = StringVar()
foc_butvar.set("Button widget")
foc_but = ttk.Button(frame, width = 7, textvariable = foc_butvar)
foc_but.grid(column = 2, row = 1, sticky = (W, E))

# Focus commands
def focus(event):
    focused_widget = frame.focus_get()
    foc_labvar.set((focused_widget, "has focus"))
    print(focused_widget, "has focus")

# Bind mouse click to run focus command
root.bind("<Button-1>", focus)

# Resize widgets inside frame
for child in frame.winfo_children():
    child.grid_configure(padx = 5, pady = 5)


root.mainloop()

Solution

  • You can just easily do this having event handle the widget identification part, like:

    def focus(event):
        focused_widget = event.widget.winfo_class()
        foc_labvar.set((focused_widget, "has focus")) # Or f'{focused_widget} has focus'
        print(focused_widget, "has focus")
    

    Here event will provide the widget with widget method and then you can use any widget methods on it, like focus() or insert()(if its an entry widget) and so on. Here winfo_class should work, because it is a universal method and works with all the widgets.

    More info on winfo_class, it will provide the class of the widget, like TButton for buttons, because that is how tk refers to them. If you adamantly still want to get rid of the T then just go for event.widget.winfo_class()[1:] to trim the 'T' off.