Adding placeholders to tkinter Entry widget in a procedural way

后端 未结 4 1265
独厮守ぢ
独厮守ぢ 2021-01-25 13:20

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

相关标签:
4条回答
  • 2021-01-25 13:58

    No, this is not directly, possible with tkinter. You might want to use classes and OOP.

    0 讨论(0)
  • 2021-01-25 14:03

    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.

    Using functions

    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()
    

    Using a custom class

    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()
    
    0 讨论(0)
  • 2021-01-25 14:12

    Here is a very simple example. In this example we include a couple of features/caveats:

    • ghost text for the placeholder
    • entry.input will return None if it's text is the placeholder or empty
    • entry.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
    • placeholder is juggled while you type
    • placeholder can be overwritten with .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()
            
        
        
    
    0 讨论(0)
  • 2021-01-25 14:19

    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.

    0 讨论(0)
提交回复
热议问题