以程序方式将占位符添加到 tkinter Entry 小部件

Adding placeholders to tkinter Entry widget in a procedural way

我知道这个问题已经在这个网站上得到了回答,但我正在寻找一个更简单的答案,我以前见过一个,但后来这个问题被删除了,我找不到了。希望有人有更好更简单的方法来解决它。与 class 相关的东西可能会更好,因为我可以轻松地将它与更多 Entry 小部件一起使用

这是一个片段:

from tkinter import *

root = Tk()

def remove(event):
    e.delete(0, END)

e = Entry(root)
e.insert(0, 'PLACEHOLDER')
e.pack(padx=100,pady=(30,0))
e.bind('<FocusIn>', remove)

e2 = Entry(root)
e2.pack( pady=(20,100))

root.mainloop()

是的,一旦失去焦点并再次获得焦点,这将删除框中的所有其他项目,包括我们首先输入的文本。无论如何要解决这个问题并使用 tkinter 拥有一个完美的占位符,我知道没有内置的方法。

提前致谢:D

我试过这个:

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

通过这样做,只有当 Text 中存在默认值时,您才会清除,而且,如果该字段留空,占位符将返回到 Text

我不是很清楚你在问什么,所以我猜你是在问如何知道条目小部件何时有占位符文本,何时没有,以便你知道何时清除它以及何时不清除它。

最简单的解决方案是用替换文本向条目添加一个属性,然后在删除之前将其与内容进行比较。

使用函数

首先,让我们创建一个函数来初始化小部件的占位符文本。这个函数做了一些简单的事情:它在小部件上添加了一个 placeholder 属性,并建立了绑定。如果小部件为空,它还会插入占位符:

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)

现在让我们调整您的 remove 函数,使其更通用一些。由于它是通过事件调用的,因此它可以使用 event.widget 而不是对特定小部件的 hard-coded 引用。它还使用我们添加到小部件的 placeholder 属性。这两项技术让它可以被多个小部件使用。

def remove_placeholder(event):
    placeholder_text = getattr(event.widget, "placeholder", "")
    if placeholder_text and event.widget.get() == placeholder_text:
        event.widget.delete(0, "end")

最后我们需要实现add_placeholder功能。当小部件失去焦点并且用户没有输入任何内容时,此函数将添加占位符。它需要检查条目小部件是否有占位符,如果有并且小部件为空,则添加占位符。与 remove_placeholder 一样,它使用 event.widgetplaceholder 属性:

def add_placeholder(event):
    placeholder_text = getattr(event.widget, "placeholder", "")
    if placeholder_text and event.widget.get() == "":
        event.widget.insert(0, placeholder_text)

我已经修改了您的程序,为两个条目小部件中的每一个使用不同的占位符文本,以表明这些功能是通用的,并且不依赖于特定的条目小部件。

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

使用自定义 class

可以说,更好的实现方式是创建自定义 class。这样一切都被封装在一个地方。这是一个例子:

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)

您可以像使用 Entry 小部件一样使用此 class,但您可以指定一个占位符:

e3 = EntryWithPlaceholder(root, placeholder="Address")
e3.pack()

这是一个非常简单的例子。在此示例中,我们包括几个 features/caveats:

  • 占位符的幻影文本
  • entry.input 将 return None 如果文本是占位符或为空
  • entry.input 应该用来代替 .get().insert().input 逻辑旨在为您提供此类小部件的正确结果。 .get() 不够智能,无法 return 正确的数据,并且 .insert() 已被重新配置为 .input
  • 的代理
  • 占位符在您键入时变戏法
  • placeholder可以用.insert()覆盖~不需要用.delete()。您仍然应该使用 entry.input 而不是

#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'

用法示例:

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

不,这不是直接的,可以用 tkinter。您可能想使用 类 和 OOP。