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

Posted

技术标签:

【中文标题】以程序方式将占位符添加到 tkinter Entry 小部件【英文标题】:Adding placeholders to tkinter Entry widget in a procedural way 【发布时间】:2020-12-18 10:31:36 【问题描述】:

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

这是一个sn-p:

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

【问题讨论】:

您似乎已经有了可以工作的代码。不清楚你在问什么。 @bryanoakley “一旦失去焦点并再次获得焦点,这将删除框内的所有其他项目,包括我们最初输入的文本 【参考方案1】:

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

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

使用函数

首先,让我们创建一个函数来初始化小部件的占位符文本。这个函数做了一些简单的事情:它在小部件上添加了一个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, "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 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()

【讨论】:

【参考方案2】:

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

占位符的幻影文本 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, 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()
        
    
    

【讨论】:

你能把第一个函数放到一个单独的文件中的类中,这样我就可以像ctk那样从小部件中导入它吗? 还有一种方法可以在焦点位于小部件上且条目小部件为空时显示占位符。就像当我们退格所有字符并且该字段为空时,不移动焦点我们可以显示占位符文本 @CoolCloud ~ 我必须改正一个错误。我不小心有self.bind('&lt;FocusOut&gt;', self.focusin)。它是固定的 哦,没问题,没问题!! @CoolCloud ~ 仅供参考,您可以简单地使用entry.input = 'whatever' 并完全跳过.insert().input 有一个 getter 和一个 setter。【参考方案3】:

我试过这个:

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

【讨论】:

如果我正在打字,我将所有内容都退格并移至下一个小部件,那会导致条目为空而没有占位符? @CloudCool 不完全是,退格所有内容都会使条目为空,但是一旦您转到下一个小部件,就会插入占位符,因为条件只检查它是否为空,它是否输入和退格都没关系。 你知道占位符工作正常,当我们退格所有字符并且字段为空时,不移动焦点我们可以显示占位符文本,我猜其他两个答案有这个概念。感谢我错过了这个伟大而简单的想法:D 我肯定会在小项目中使用它【参考方案4】:

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

【讨论】:

以上是关于以程序方式将占位符添加到 tkinter Entry 小部件的主要内容,如果未能解决你的问题,请参考以下文章

将占位符文本添加到文本字段时,iOS 7 中的应用程序崩溃

以编程方式添加控件时未实例化占位符

以编程方式将用户控件加载到占位符 (asp.net)

jQuery UI Sortable 将拖动对象的类添加到占位符以确定大小

将自定义占位符添加到 django-cms

如何将占位符图像添加到嵌入的 Vimeo 和 Youtube 视频中?