Home > Net >  What is causing this strange focus behavior with my PlaceholderEntry class?
What is causing this strange focus behavior with my PlaceholderEntry class?

Time:10-29

I've put together a simple class that inherits from ttk.Entry in order to toggle some placeholder text on focus events (based on this answer).

The odd thing is that my placeholder text is only cleared on '<FocusIn>' if I include print(self['foreground']) in my set_placeholder() method, otherwise the placeholder text isn't cleared when the Entry is focused.

I've included an example app with two PlaceholderEntry widgets to demonstrate the focus behavior.

import tkinter as tk
from tkinter import ttk


class Root(tk.Tk):
    """Example app"""
    def __init__(self):
        super().__init__()
        self.ph_entry_a = PlaceholderEntry(self, 'Hello')
        self.ph_entry_a.pack()
        self.ph_entry_b = PlaceholderEntry(self, 'there')
        self.ph_entry_b.pack()


class PlaceholderEntry(ttk.Entry):
    """Entry widget with focus-toggled placeholder text"""
    def __init__(
        self, parent, placeholder='', color='#828790', *args, **kwargs
    ):
        super().__init__(parent, *args, **kwargs)
        self.placeholder = placeholder
        self._ph_color = color
        self._default_fg = self.cget('foreground')  # default foreground color
        # focus bindings
        self.bind('<FocusOut>', self.set_placeholder)
        self.bind('<FocusIn>', self.clear_placeholder)
        # initialize placeholder
        self.set_placeholder()

    def set_placeholder(self, *args):  # on focus out
        if not self.get():  # if the entry has no text...
            self.insert(0, self.placeholder)
            self.configure(foreground=self._ph_color)
            # clearing the placeholder doesn't work without this line:
            print(self['foreground'])
            # I've also tried this, and it works as well...
            # str(self.cget('foreground'))

    def clear_placeholder(self, *args):
        if self.cget('foreground') == self._ph_color:
            self.delete(0, tk.END)
            self.configure(foreground=self._default_fg)
            # it makes no difference if I have a 'print' or 'str' statement here


if __name__ == '__main__':
    app = Root()
    app.mainloop()

I'm wondering if this has something to do with garbage collection, but I'm honestly not sure. For what it's worth, assigning the foreground to a variable in the set_placeholder method e.g. fg = self['foreground'] makes no difference. Can anybody tell me what's going on here? Any info is much appreciated.

FWIW: I'm using Python 3.11 64-bit on Windows 10

CodePudding user response:

The short answer is that cget returns a <class '_tkinter.Tcl_Obj'> and not a string that is whats causing your evaluation to fail. You could solve it in different ways.

  1. self.cget('foreground').string
  2. str(self.cget('foreground'))

A slightly longer answer, but I'm not aware of all the details, is that in tcl everything is written in strings text and in order to be useful in python in needs to be translated. Therefore such methods on your root window, to be exact on the instance of _tkinter.tkapp exists as getint and so on.

The implementation of tkinter in python is really good and I can't imagine the countless hours it took to make it work. But some features however are not perfect. Take the return value from configure for example. Normally you would expect a dictionary of key and value pairs but they are not translated well and you get synonyms for the keyword in tcl as well. It's a complex topic that I wouldn't claim I'm an expert it.

As @Bryan Oakley pointed out in the comments:

I think this is just a bug in ttk. The cget returns this tclObj for the color options of a ttk widget, but it returns a proper string with tkinter widgets.

  • Related