Based on the answer here I derived a different solution, because I didn't like the idea that the tk.DISABLED
state was used for two reasons:
- it can collide with existing code that need this entry to be disabled regardless of the placeholder text bindings
- it is deceiving to end users because they might not know that this entry is actually not disabled
For this purpose, I created an extension class that can pass on the configure_placeholder
method via inheritance. This also requires less code than implementing a custom Entry
widget from scratch.
# file: extensions.py
import tkinter as tk
class TkExtensions(tk.Frame):
@staticmethod
def __on_focus_in(event: tk.Event, entry: tk.Entry) -> None:
entry.configure(fg="black")
entry.delete(0, tk.END)
@staticmethod
def __on_focus_out(event: tk.Event, entry: tk.Entry, placeholder: str) -> None:
if entry.get() == "":
entry.insert(0, placeholder)
entry.configure(fg="grey")
@staticmethod
def configure_placeholder(entry: tk.Entry, placeholder: str) -> None:
entry.insert(0, placeholder)
# set color to grey instead of entering a disabled state
entry.configure(fg="grey")
entry.bind("<FocusIn>", lambda event: TkExtensions.__on_focus_in(event, entry))
# restore placeholder text if there was no user input after focus in
entry.bind("<FocusOut>", lambda event: TkExtensions.__on_focus_out(event, entry, placeholder))
Example usage in a consuming class:
import tkinter as tk
from tkinter import N, W, X
from typing import Self
from extensions import TkExtensions
class Window(TkExtensions, tk.Frame):
# stuff and things
def build_settings(self: Self, ...) -> None:
# ... some code
origin_lon = tk.Entry(self.settings)
origin_lon.pack(side=tk.LEFT, expand=True, fill=X, anchor=N+W)
Window.configure_placeholder(origin_lon, "placeholder")
# ... more code