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.
self.cget('foreground').string
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.