I know this Q has been answered in this site, but im looking for a more simpler answer, and ive seen one before but then the question has been deleted or something, I cant find
No, this is not directly, possible with tkinter
. You might want to use classes and OOP.
I'm not really clear on what you're asking, so I'm guessing you're asking how to know when the entry widget has placeholder text and when it doesn't so that you know when to clear it and when not to clear it.
The easiest solution is to add an attribute to the entry with the replacement text, and then compare it to the contents before deleting.
First, let's create a function to initialize the placeholder text for a widget. This function does a few simple things: it adds a placeholder
attribute on the widget, and it establishes the bindings. It also inserts the placeholder if the widget is empty:
def init_placeholder(widget, placeholder_text):
widget.placeholder = placeholder_text
if widget.get() == "":
widget.insert("end", placeholder_text)
# set up a binding to remove placeholder text
widget.bind("<FocusIn>", remove_placeholder)
widget.bind("<FocusOut>", add_placeholder)
Now let's tweak your remove
function to be a bit more generic. Since it's called via an event, it can use event.widget
rather than a hard-coded reference to a specific widget. It also uses the placeholder
attribute which we added to the widget. These two techniques lets it be used by more than one widget.
def remove_placeholder(event):
placeholder_text = getattr(event.widget, "placeholder", "")
if placeholder_text and event.widget.get() == placeholder_text:
event.widget.delete(0, "end")
Finally we need to implement the add_placeholder
function. This function will add the placeholder when the widget loses focus and the user hasn't typed anything. It needs to check if the entry widget has a placeholder, and if it does and the widget is empty, it adds the placeholder. Like remove_placeholder
it uses event.widget
and the placeholder
attribute:
def add_placeholder(event):
placeholder_text = getattr(event.widget, "placeholder", "")
if placeholder_text and event.widget.get() == "":
event.widget.insert(0, placeholder_text)
I've modified your program to use different placeholder text for each of the two entry widgets to show that the functions are generic and not tied to a specific entry widget.
from tkinter import *
root = Tk()
def remove_placeholder(event):
"""Remove placeholder text, if present"""
placeholder_text = getattr(event.widget, "placeholder", "")
if placeholder_text and event.widget.get() == placeholder_text:
event.widget.delete(0, "end")
def add_placeholder(event):
"""Add placeholder text if the widget is empty"""
placeholder_text = getattr(event.widget, "placeholder", "")
if placeholder_text and event.widget.get() == "":
event.widget.insert(0, placeholder_text)
def init_placeholder(widget, placeholder_text):
widget.placeholder = placeholder_text
if widget.get() == "":
widget.insert("end", placeholder_text)
# set up a binding to remove placeholder text
widget.bind("<FocusIn>", remove_placeholder)
widget.bind("<FocusOut>", add_placeholder)
e = Entry(root)
e.pack(padx=100,pady=(30,0))
e2 = Entry(root)
e2.pack( pady=(20,100))
init_placeholder(e, "First Name")
init_placeholder(e2, "Last Name")
root.mainloop()
Arguably, a better way to implement this would be to create a custom class. That way everything is encapsulated in one place. Here's an example:
class EntryWithPlaceholder(Entry):
def __init__(self, *args, **kwargs):
self.placeholder = kwargs.pop("placeholder", "")
super().__init__(*args, **kwargs)
self.insert("end", self.placeholder)
self.bind("<FocusIn>", self.remove_placeholder)
self.bind("<FocusOut>", self.add_placeholder)
def remove_placeholder(self, event):
"""Remove placeholder text, if present"""
if self.get() == self.placeholder:
self.delete(0, "end")
def add_placeholder(self,event):
"""Add placeholder text if the widget is empty"""
if self.placeholder and self.get() == "":
self.insert(0, self.placeholder)
You can use this class just like an Entry
widget, but you can specify a placeholder:
e3 = EntryWithPlaceholder(root, placeholder="Address")
e3.pack()
Here is a very simple example. In this example we include a couple of features/caveats:
entry.input
will return None
if it's text is the placeholder or emptyentry.input
should be used in place of .get()
and .insert()
. The .input
logic is designed to give you the proper results for this type of widget. .get()
is not smart enough to return the proper data, and .insert()
has been reconfigured as a proxy to .input
.insert()
~ no need to use .delete()
. You should still use entry.input
instead#widgets.py
import tkinter as tk
class PlaceholderEntry(tk.Entry):
'''
All Of These Properties Are For Convenience
'''
@property
def input(self):
return self.get() if self.get() not in [self.__ph, ''] else None
@input.setter
def input(self, value):
self.delete(0, 'end')
self.insert(0, value)
self.configure(fg = self.ghost if value == self.__ph else self.normal)
@property
def isempty(self) -> bool:
return self.get() == ''
@property
def isholder(self) -> bool:
return self.get() == self.__ph
def __init__(self, master, placeholder, **kwargs):
tk.Entry.__init__(self, master, **{'disabledforeground':'#BBBBBB', **kwargs})
self.normal = self['foreground']
self.ghost = self['disabledforeground']
self.__ph = placeholder
self.input = placeholder
vcmd = self.register(self.validate)
self.configure(validate='all', validatecommand=(vcmd, '%S', '%s', '%d'))
self.bind('<FocusIn>' , self.focusin)
self.bind('<FocusOut>', self.focusout)
self.bind('<Key>' , self.check)
#rewire .insert() to be a proxy of .input
def validate(self, action_text, orig_text, action):
if action == '1':
if orig_text == self.__ph:
self.input = action_text
return True
#removes placeholder if necessary
def focusin(self, event=None):
if self.isholder:
self.input = ''
#adds placeholder if necessary
def focusout(self, event=None):
if self.isempty:
self.input = self.__ph
#juggles the placeholder while you type
def check(self, event):
if event.keysym == 'BackSpace':
if self.input and len(self.input) == 1:
self.input = self.__ph
self.icursor(0)
return 'break'
elif self.isholder:
if event.char:
self.input = ''
else:
return 'break'
usage example:
#__main__.py
import tkinter as tk
import widgets as ctk #custom tk
if __name__ == "__main__":
root = tk.Tk()
root.title("Placeholder Entry")
root.grid_columnconfigure(2, weight=1)
#init some data
entries = [] #for storing entry references
label_text = ['email', 'name']
entry_text = ['you@mail.com', 'John Smith']
#create form
for n, (label, placeholder) in enumerate(zip(label_text, entry_text)):
#make label
tk.Label(root, text=f'{label}: ', width=8, font='consolas 12 bold', anchor='w').grid(row=n, column=0, sticky='w')
#make entry
entries.append(ctk.PlaceholderEntry(root, placeholder, width=14, font='consolas 12 bold'))
entries[-1].grid(row=n, column=1, sticky='w')
#form submit function
def submit():
for l, e in zip(label_text, entries):
if e.input:
print(f'{l}: {e.input}')
#form submit button
tk.Button(root, text='submit', command=submit).grid(column=1, sticky='e')
root.mainloop()
I have tried this:
from tkinter import *
root = Tk()
def remove(event):
if e.get() == 'PLACEHOLDER': #Check default value
e.delete(0, END)
def add(event):
if not e.get(): #Check if left empty
e.insert(0, 'PLACEHOLDER')
e = Entry(root)
e.insert(0, 'PLACEHOLDER')
e.pack(padx=100,pady=(30,0))
e.bind('<FocusIn>', remove)
e.bind('<FocusOut>', add)
e2 = Entry(root)
e2.pack( pady=(20,100))
root.mainloop()
By doing this you will be clearing only if the default value is present in the Text
and also, if the field is left empty, the placeholder gets back into the Text
.