I have created a login window in tkinter which has two Entry field, first one is Username and second one is Password.
code
from tkinter import *
ui = Tk()
e1 = Entry(ui)
#i need a placeholder "Username" in the above entry field
e1.pack()
ui.mainloop()
I want a placeholder called "Username" in the Entry, but if you click inside the entry box, the text should disappear.
You can create a class that inherits from Entry like below:
import tkinter as tk
class EntryWithPlaceholder(tk.Entry):
def __init__(self, master=None, placeholder="PLACEHOLDER", color='grey'):
super().__init__(master)
self.placeholder = placeholder
self.placeholder_color = color
self.default_fg_color = self['fg']
self.bind("<FocusIn>", self.foc_in)
self.bind("<FocusOut>", self.foc_out)
self.put_placeholder()
def put_placeholder(self):
self.insert(0, self.placeholder)
self['fg'] = self.placeholder_color
def foc_in(self, *args):
if self['fg'] == self.placeholder_color:
self.delete('0', 'end')
self['fg'] = self.default_fg_color
def foc_out(self, *args):
if not self.get():
self.put_placeholder()
if __name__ == "__main__":
root = tk.Tk()
username = EntryWithPlaceholder(root, "username")
password = EntryWithPlaceholder(root, "password", 'blue')
username.pack()
password.pack()
root.mainloop()
You need to set a default value for this entry. Like this:
from tkinter import *
ui = Tk()
e1 = Entry(ui)
e1.insert(0, 'username')
e1.pack()
ui.mainloop()
Then if you want to delete the content when you click the entry, then you have to bind a mouse click event with an event handler method to update content of this entry.
Here is a link for you.
Updated (Improved Answer):
Use the on_focus_out function to reinsert the placeholder if the text field is empty (if you don't want this to happen, you can use the method from the older code)
import tkinter as tk
def on_focus_in(entry):
if entry.cget('state') == 'disabled':
entry.configure(state='normal')
entry.delete(0, 'end')
def on_focus_out(entry, placeholder):
if entry.get() == "":
entry.insert(0, placeholder)
entry.configure(state='disabled')
root = tk.Tk()
entry_x = tk.Entry(root, width=50)
entry_x.pack(pady=10)
entry_x.insert(0, "Place Holder X")
entry_x.configure(state='disabled')
entry_y = tk.Entry(root, width=50)
entry_y.pack(pady=10)
entry_y.insert(0, "Place Holder Y")
entry_y.configure(state='disabled')
x_focus_in = entry_x.bind('<Button-1>', lambda x: on_focus_in(entry_x))
x_focus_out = entry_x.bind(
'<FocusOut>', lambda x: on_focus_out(entry_x, 'Place Holder X'))
y_focus_in = entry_y.bind('<Button-1>', lambda x: on_focus_in(entry_y))
y_focus_out = entry_y.bind(
'<FocusOut>', lambda x: on_focus_out(entry_y, 'Place Holder Y'))
root.mainloop()
Note:
It is discouraged to import *, so we should import like this import tkinter as tk.
I have created two Entry widgets to depict the changes.
Old (Not Recommended):
This will work for any placeholder you want.
from tkinter import *
root = Tk()
my_entry = Entry(root, width=50)
my_entry.pack()
my_entry.insert(0, "Place Holder")
my_entry.configure(state=DISABLED)
def on_click(event):
my_entry.configure(state=NORMAL)
my_entry.delete(0, END)
# make the callback only work once
my_entry.unbind('<Button-1>', on_click_id)
on_click_id = my_entry.bind('<Button-1>', on_click)
root.mainloop()
My solution is to subclass the tk.Entry and control the content and color, binding the <FocusIn> and <FocusOut> events to methods that fill and clear the text as necessary. This is the behavior:
Here the complete example code:
import tkinter as tk
class PlaceholderEntry(tk.Entry):
def __init__(self, master=None, placeholder='', cnf={}, fg='black',
fg_placeholder='grey50', *args, **kw):
super().__init__(master=None, cnf={}, bg='white', *args, **kw)
self.fg = fg
self.fg_placeholder = fg_placeholder
self.placeholder = placeholder
self.bind('<FocusOut>', lambda event: self.fill_placeholder())
self.bind('<FocusIn>', lambda event: self.clear_box())
self.fill_placeholder()
def clear_box(self):
if not self.get() and super().get():
self.config(fg=self.fg)
self.delete(0, tk.END)
def fill_placeholder(self):
if not super().get():
self.config(fg=self.fg_placeholder)
self.insert(0, self.placeholder)
def get(self):
content = super().get()
if content == self.placeholder:
return ''
return content
class App(tk.Frame):
def __init__(self, master=None):
self.root = master
super().__init__(master, borderwidth=0, relief=tk.RAISED)
self.root.title('Placeholder example')
self.pack_propagate(False)
self.pack()
self.entry = PlaceholderEntry(self.root, placeholder='This text is a placeholder')
self.entry.pack()
self.btn = tk.Button(self.root, text='Nothing', highlightcolor='cyan')
self.btn.pack()
root = tk.Tk()
app = App(master=root)
app.root.mainloop()
A working placeholder class. What this does is that it binds to <FocusIn> and <FocusOut> so that when you put focus on it if there is no text it will insert your placeholder into it. You can also change the color on if it is selected or not.
class Placeholder:
def __init__(self,master,placeholder='',placeholdercolor='grey',color='black',**kwargs):
self.e = Entry(master,fg=placeholdercolor,**kwargs)
self.e.bind('<FocusIn>',self.focus_in)
self.e.bind('<FocusOut>',self.focus_out)
self.e.insert(0, placeholder)
self.placeholder = placeholder
self.placeholdercolor=placeholdercolor
self.color = color
def pack(self,side=None,**kwargs):
self.e.pack(side=side,**kwargs)
def place(self,side=None,**kwargs):
self.e.place(side=side,**kwargs)
def grid(self,column=None,**kwargs):
self.e.grid(column=column,**kwargs)
def focus_in(self,e):
if self.e.get() == self.placeholder:
self.e.delete(0,END)
self.e.configure(fg=self.color)
def focus_out(self,e):
if self.e.get() == '':
self.e.configure(fg=self.placeholdercolor)
self.e.delete(0,END)
self.e.insert(0,self.placeholder)
from tkinter import *
root=Tk()
root.geometry("300x200+600+250")
root.config(background="#E0FFFF")
root.resizable(False,False)
def userText(event):
e1.delete(0,END)
usercheck=True
def passText(event):
e2.delete(0, END)
passcheck=True
a=StringVar()
b=StringVar()
usercheck=False
passcheck=False
Label(root,text="User name",bg="#E0FFFF").place(x=20,y=50)
e1= Entry(root,textvariable=a)
e1.place(x=100,y=50)
e1.insert(0,"Enter username")
e1.bind("<Button>",userText)
Label(root,text="Password",bg="#E0FFFF").place(x=20,y=95)
e2= Entry(root,textvariable=b)
e2.place(x=100,y=95)
e2.insert(0,"Enter password")
e2.bind("<Button>",passText)
root.mainloop()
For a more compact solution than the above listed, I suggest that you create a function that would erase the text box on a click event (lambda), as shown here.
from tkinter import *
def clear_entry(event, entry):
entry.delete(0, END)
entry.unbind('<Button-1>', click_event)
ui = Tk()
entry = Entry(ui)
entry.pack()
placeholder_text = '<enter-placeholder>'
entry.insert(0, placeholder_text)
entry.bind("<Button-1>", lambda event: clear_entry(event, entry))
ui.mainloop()
The "<Button-1>" stands for when you left-click the entry box, so do not alter it, and once you click on the box, it will trigger the event and run the function clear_entry. You have to declare the function and the entry element before defining placeholder_text and using entry.insert. Hopefully, this is a viable solution to this problem.
Joining Nae and Stephen Lins solutions with text field
text = Text(ui)
text.pack()
text.insert('1.0', 'text placeholder')
text.bind("<FocusIn>", lambda args: text.delete('1.0', 'end'))
# This is a easy way to add placeholder to an Entry
from tkinter import *
w = Tk()
w.title("Demo")
w.geomerty("500x500")
EntrySetText = StringVar()
EntryText = Entry(w)
EntryText.pack()
EntrySetText.set("Hello World!")
w.mainloop()
If you combine the entry field with a string variable all of this becomes a lot easier
from tkinter import *
from tkinter import ttk
from typing import Optional
class PlaceholderEntry:
def __init__(self, root: Tk | Frame | ttk.Frame | ttk.LabelFrame | Toplevel, string_variable: Optional[StringVar] = None, style: str = '', width: int = 25, placeholder_text: str = '', text_color: str = 'black', placeholder_color: str = 'grey50'):
self._placeholder_text = placeholder_text
self._placeholder_color = placeholder_color
self._text_color = text_color
self._text_var = string_variable if string_variable else StringVar(root)
self.__text_var.set(placeholder_text)
self.__entry = ttk.Entry(root, textvariable=self.__text_var, style=style, width=width)
self._entry.bind('<FocusOut>', self._focus_out)
self._entry.bind('<FocusIn>', self._focus_in)
self._change_color(False)
#property
def entry(self) -> ttk.Entry:
return self.__entry
#property
def string_var(self) -> StringVar:
return self.__text_var
def _change_color(self, placeholder: bool) -> None:
if placeholder:
self.__entry.configure(foreground=self._text_color)
else:
self.__entry.configure(foreground=self._placeholder_color)
def _focus_in(self, event: Event) -> None:
if self.__text_var.get() == self._placeholder_text:
self.__text_var.set('')
self._change_color(True)
def _focus_out(self, event: Event) -> None:
if not self._entry.get():
self.__text_var.set(self._placeholder_text)
if self.__entry.get() == self._placeholder_text:
self._change_color(False)
else:
self._change_color(True)
Related
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 it. Hopefully someone is having a better and easier way around to it. Something related to class might be better as I could use it easily with more Entry widgets
Here is a snippet:
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()
Yes, this will remove all the other items inside of the box once it looses focus and gains it again, including the text we enter at first. Anyway to get around that and have a perfect placeholder with tkinter, I'm aware there is no inbuilt way.
Thanks in advance :D
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()
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()
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.
No, this is not directly, possible with tkinter. You might want to use classes and OOP.
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 it. Hopefully someone is having a better and easier way around to it. Something related to class might be better as I could use it easily with more Entry widgets
Here is a snippet:
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()
Yes, this will remove all the other items inside of the box once it looses focus and gains it again, including the text we enter at first. Anyway to get around that and have a perfect placeholder with tkinter, I'm aware there is no inbuilt way.
Thanks in advance :D
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()
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()
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.
No, this is not directly, possible with tkinter. You might want to use classes and OOP.
I'm going to need a list control with multiple selection and a way for the user to edit the displayed list.
Judging by Python - python-list - ttk Listbox, ttk.Treeview is the new black way to display a list and a replacement for Tkinter.Listbox.
Is there some stock/recommended way provided to incorporate list editing, too?
This is typically done with three buttons "add"/"edit"/"delete" somewhere around the control (the first two may cause a small window with an edit control to pop up), or a list entry itself becomes an editor e.g. on a double click. Implementing either logic by hand would be nontrivial.
Tk and thus Tkinter only provide base widgets; reusable combinations of them are beyond their scope. The abandoned Tix package offered a mechanism for and a collection of these, called "mega-widgets", but the idea didn't catch on apparently.
I did it my way the following way (using the Model-View-Presenter design pattern).
It looks like this:
import tkinter
from tkinter import ttk
from tkinter import W, E
class View_EditableList(object):
def __init__(self,root,root_row):
""" List with buttons to edit its contents.
:param root: parent widget
:param roow_row: row in `root'. The section takes 2 rows.
"""
self.list = tkinter.Listbox(root, selectmode=tkinter.EXTENDED)
self.list.grid(row=root_row, sticky=(W, E))
root.rowconfigure(root_row, weight=1)
self.frame = ttk.Frame(root)
self.frame.grid(row=root_row+1, sticky=(W, E))
self.add = ttk.Button(self.frame, text="+", width=5)
self.add.grid(row=0, column=1)
self.edit = ttk.Button(self.frame, text="*", width=5)
self.edit.grid(row=0, column=2)
self.del_ = ttk.Button(self.frame, text="-", width=5)
self.del_.grid(row=0, column=3)
self.up = ttk.Button(self.frame, text=u"↑", width=5)
self.up.grid(row=0, column=4)
self.down = ttk.Button(self.frame, text=u"↓", width=5)
self.down.grid(row=0, column=5)
self.frame.grid_columnconfigure(0, weight=1)
self.frame.grid_columnconfigure(6, weight=1)
class Presenter_EditableList(object):
def __init__(self,view,root):
"""
:param view: View_EditableList
:param root: root widget to be used as parent for modal windows
"""
self.root = root
self.view = view
view.add.configure(command=self.add)
view.edit.configure(command=self.edit)
view.del_.configure(command=self.del_)
view.up.configure(command=self.up)
view.down.configure(command=self.down)
def add(self):
w=View_AskText(self.root)
self.root.wait_window(w.top)
if w.value:
self.view.list.insert(self.view.list.size(),w.value)
def edit(self):
l=self.view.list
try:
[index]=l.curselection()
except ValueError:
return
w=View_AskText(self.root,l.get(index))
self.root.wait_window(w.top)
if w.value:
l.delete(index)
l.insert(index,w.value)
def del_(self):
l=self.view.list
try:
[index]=l.curselection()
except ValueError:
return
l.delete(index)
l.select_set(max(index,l.size()-1))
def up(self):
l = self.view.list
try:
[index] = l.curselection()
except ValueError:
return
if index>0:
v = l.get(index)
l.delete(index)
l.insert(index-1,v)
l.select_set(index-1)
def down(self):
l = self.view.list
try:
[index] = l.curselection()
except ValueError:
return
if index<l.size()-1:
v = l.get(index)
l.delete(index)
l.insert(index+1,v)
l.select_set(index+1)
def getlist(self):
return [self.view.list.get(i) for i in range(self.view.list.size())]
def setlist(self,list_):
self.view.list.delete(0,tkinter.END)
for i,v in enumerate(list_):
self.view.list.insert(i,v)
# supplemental class; it's in another file in my actual code
class View_AskText(object):
"""
A simple dialog that asks for a text value.
"""
def __init__(self, master, value=u""):
self.value = None
top = self.top = tkinter.Toplevel(master)
top.grab_set()
self.l = ttk.Label(top, text=u"Value:")
self.l.pack()
self.e = ttk.Entry(top)
self.e.pack()
self.b = ttk.Button(top, text='Ok', command=self.save)
self.b.pack()
if value: self.e.insert(0, value)
self.e.focus_set()
top.bind('<Return>', self.save)
def save(self, *_):
self.value = self.e.get()
self.top.destroy()
root = tkinter.Tk()
view = View_EditableList(root, 5)
Presenter_EditableList(view, root)
root.mainloop()
I am trying to pass a variable from a tkinter combobox to a function called when clicking a 'Run' button. I am relatively new to python and every option I have tried creates an error - mostly that the variable is not defined. I believe this is because I am not defining it in the correct place. Any help is greatly appreciated.
from tkinter import *
from tkinter import ttk
from URL_Generator import crawl_site
listFile = open('regions1.txt','r')
root = Tk()
root.configure()
varItems = StringVar(root, value='')
class MainWindow(Frame):
def __init__(self,master = None):
Frame.__init__(self,master)
self.master = master
self.grid()
self.create_widgets()
def create_widgets(self):
"""Create Window Layout"""
self.label = Label(self, text="List Items").pack()
self.itemCombo = ttk.Combobox(self, width = 16, textvariable = varItems)
self.itemCombo.bind("<Return>", self.itemCombo_onEnter)
self.itemCombo.bind('<<ComboboxSelected>>',self.itemCombo_onEnter)
self.itemCombo['values'] = [l.strip() for l in listFile.readlines()]
self.itemCombo.pack()
self.blank = Label(self,text='').pack()
"""I want to pass the value selected in the combobox to the crawl_region() function when pushing Run"""
self.RunButton = Button(self, text="Run",command = crawl_site.crawl_region(region))
self.RunButton.pack()
def itemCombo_onEnter(self,event):
varItems.set(varItems.get().lower().strip())
mytext = varItems.get().strip()
vals = self.itemCombo.cget('values')
self.itemCombo.select_range(0,END)
print(mytext)
region = mytext
"""I want to pass mytext to the function called when pushing Run"""
if not vals:
self.itemCombo.configure(values = (mytext,))
elif mytext not in vals:
with open('regions1.txt', 'w') as f:
self.itemCombo.configure(values=vals + (mytext,))
f.write("\n".join(vals + (mytext,)))
f.close()
return 'break'
app = MainWindow(root)
root.mainloop()
Sample function called (crawl_site.crawl_region()):
class crawl_site():
def crawl_region(region):
print('passed region '+ str(region))
passed region [] is immediately returned, but nothing happens when I make a selection or press the Run button.
Try the below code.
I've created a class property self.mytext which is set when the combo button is entered itemCombo_onEnter. When the button is pressed the onRunButton function is called. if self.mytext has been set, it will call the crawl_region function with self.mytext as an argument.
from tkinter import *
from tkinter import ttk
from URL_Generator import crawl_site
listFile = open('regions1.txt','r')
root = Tk()
root.configure()
varItems = StringVar(root, value='')
class MainWindow(Frame):
def __init__(self,master = None):
Frame.__init__(self,master)
self.master = master
self.grid()
self.create_widgets()
def create_widgets(self):
"""Create Window Layout"""
self.label = Label(self, text="List Items").pack()
self.itemCombo = ttk.Combobox(self, width = 16, textvariable = varItems)
self.itemCombo.bind("<Return>", self.itemCombo_onEnter)
self.itemCombo.bind('<<ComboboxSelected>>',self.itemCombo_onEnter)
self.itemCombo['values'] = [l.strip() for l in listFile.readlines()]
self.itemCombo.pack()
self.blank = Label(self,text='').pack()
"""I want to pass the value selected in the combobox to the crawl_region() function when pushing Run"""
self.RunButton = Button(self, text="Run",command = self.onRunButton)
self.RunButton.pack()
def onRunButton(self):
if self.mytext:
crawl_site.crawl_region(self.mytext)
def itemCombo_onEnter(self,event):
varItems.set(varItems.get().lower().strip())
mytext = varItems.get().strip()
vals = self.itemCombo.cget('values')
self.itemCombo.select_range(0,END)
print(mytext)
self.mytext = mytext
"""I want to pass mytext to the function called when pushing Run"""
if not vals:
self.itemCombo.configure(values = (mytext,))
elif mytext not in vals:
with open('regions1.txt', 'w') as f:
self.itemCombo.configure(values=vals + (mytext,))
f.write("\n".join(vals + (mytext,)))
f.close()
return 'break'
app = MainWindow(root)
root.mainloop()
Your code wasn't working because of this line
self.RunButton = Button(self, text="Run",command = crawl_site.crawl_region(region))
This immediately calls the method crawl_region with the region as an argument and tries to set the callback of the button to the result of that method.
Another way to 'fix' your problem without creating another function would be to use lambda but I think my method is more readable.
I want to enable double click to edit a label. Is there a way to replace the label, which user double-clicks, with entry widget without destroying it?
This example lists label widgets and after double-click destroys it and sets entry widget at the end:
from tkinter import *
class MainWindow:
def __init__(self, root):
self.root = root
self.tasks = ['text1', 'text2', 'text3']
self.list_tasks()
def list_tasks(self):
self.tasks_frame = Frame(self.root)
for task in self.tasks:
task_label = Label(self.tasks_frame, text=task)
task_label.bind('<Double-Button-1>', self.replace_with_entry)
task_label.pack()
self.tasks_frame.pack()
def replace_with_entry(self, event):
widget = event.widget
widget.destroy()
entry_widget = Entry(self.tasks_frame)
entry_widget.pack()
root = Tk()
main_window = MainWindow(root)
root.mainloop()
I want entry widget in exactly the same place, where label was. I think it is possible with grid, but maybe there is a better way?
The simplest solution is not to replace it, but to instead overlay it. This is one circumstance where place is very useful.
For example:
def replace_with_entry(self, event):
widget = event.widget
entry_widget = Entry(widget)
entry_widget.place(x=0, y=0, anchor="nw", relwidth=1.0, relheight=1.0)
entry_widget.bind("<Return>", self.remove_entry)
entry_widget.focus_set()
def remove_entry(self, event):
entry = event.widget
label = entry.place_info()["in"]
label.configure(text=entry.get())
entry.destroy()
Here is the grid solution. I think it is acceptable, but you can wait to see if someone else provides a better solution.
from tkinter import *
from functools import partial
class MainWindow:
def __init__(self, root):
self.root = root
self.tasks = ['text1', 'text2', 'text3']
self.list_tasks()
def list_tasks(self):
self.tasks_frame = Frame(self.root)
for i, task in enumerate(self.tasks):
task_label = Label(self.tasks_frame, text=task)
task_label.bind('<Double-Button-1>', partial(self.replace_with_entry, i))
task_label.grid(row=i, column=0)
self.tasks_frame.pack()
def replace_with_entry(self, i, event):
widget = event.widget
widget.destroy()
entry_widget = Entry(self.tasks_frame)
entry_widget.grid(row=i, column=0)
root = Tk()
main_window = MainWindow(root)
root.mainloop()
I also made another version that uses tkinter variables to include the text from the label in the entry. It could be modified to allow switching back to the label with the modified text.
from tkinter import *
from functools import partial
class MainWindow:
def __init__(self, root):
self.root = root
self.tasks = ['text1', 'text2', 'text3']
self.list_tasks()
def list_tasks(self):
self.tasks_frame = Frame(self.root)
self.task_widgets = []
for i, task in enumerate(self.tasks):
textvar = StringVar()
textvar.set(task)
task_label = Label(self.tasks_frame, textvariable=textvar)
task_label.bind('<Double-Button-1>', partial(self.replace_with_entry, i))
task_label.grid(row=i, column=0)
task_entry = Entry(self.tasks_frame, textvariable=textvar)
self.task_widgets.append((task_label, task_entry, textvar))
self.tasks_frame.pack()
def replace_with_entry(self, i, event):
widget = event.widget
widget.grid_forget()
self.task_widgets[i][1].grid(row=i, column=0)
root = Tk()
main_window = MainWindow(root)
root.mainloop()