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

问题描述

我知道此问题已在本站点得到解答,但我正在寻找一个更简单的答案,并且我之前见过一个问题,但随后该问题已删除或无法找到,我找不到它。希望有人能够找到更好,更轻松的方法。与类有关的东西可能会更好,因为我可以在更多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

解决方法

我不清楚您要问的是什么,所以我猜您是在问如何知道输入窗口小部件何时有占位符文本,何时没有占位符文本,以便您知道何时清除它以及何时不清除它。

最简单的解决方案是将属性添加到带有替换文本的条目中,然后将其与内容进行比较,然后再删除。

使用功能

首先,让我们创建一个函数来初始化小部件的占位符文本。这个函数做一些简单的事情:在小部件上添加一个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而不是对特定小部件的硬编码引用。它还使用了我们添加到小部件中的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,"")
    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,"end")

def add_placeholder(event):
    """Add placeholder text if the widget is empty"""
    placeholder_text = getattr(event.widget,placeholder_text)

def init_placeholder(widget,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 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小部件一样使用此类,但可以指定一个占位符:

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

这是一个非常简单的示例。在此示例中,我们包含了几个功能/界面:

  • 占位符的幽灵文本
  • entry.input如果文本为占位符或为空,则将返回None
  • entry.input应该代替.get().insert()使用。 .input逻辑旨在为您提供此类小部件的正确结果。 .get()不够聪明,无法返回正确的数据,并且.insert()已重新配置为.input的代理
  • 您输入时占位符被弄乱了
  • 可以使用.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,**{'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 = ['[email protected]','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,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()
        
    
    
,

我已经尝试过了:

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,0))
e.bind('<FocusIn>',remove)
e.bind('<FocusOut>',add)

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

root.mainloop()

这样做只会清除Text中存在默认值的情况,而且如果该字段保留为空,则占位符将返回Text

,

否,tkinter不能直接做到这一点。您可能要使用类和OOP。