Is there a way to auto add dashes in a phone number when the person types their phone number like for say phone number is 5551111234, but when they type it in the entry box the number should appear with hyphen b/w the number automatically like 555-1111234.
Asked
Active
Viewed 1,016 times
3 Answers
1
I used a combination of both this example of tracing tkinter variables and combined it with this answer, I am not 100% sure if this is the correct American formatting because I live in the UK and we format things differently here, but this is the rough example of how that would work:
# Python program to trace
# variable in tkinter
from tkinter import *
import re
root = Tk()
my_var = StringVar()
# defining the callback function (observer)
def phone_format(phone_number):
try:
clean_phone_number = re.sub('[^0-9]+', '', phone_number)
formatted_phone_number = re.sub(
r"(\d)(?=(\d{3})+(?!\d))", r"\1-", "%d" % int(clean_phone_number[:-1])) + clean_phone_number[-1]
return formatted_phone_number
except ValueError:
return phone_number
def my_callback(var, indx, mode):
my_var.set(phone_format(my_var.get()))
label.configure(text=my_var.get())
my_var.trace_add('write', my_callback)
label = Label(root)
label.pack(padx=5, pady=5)
Entry(root, textvariable=my_var).pack(padx=5, pady=5)
root.mainloop()
Alternative
# Python program to trace
# variable in tkinter
from tkinter import *
import phonenumbers
import re
root = Tk()
my_var = StringVar()
# defining the callback function (observer)
# def phone_format(phone_number):
# try:
# clean_phone_number = re.sub('[^0-9]+', '', phone_number)
# formatted_phone_number = re.sub(
# r"(\d)(?=(\d{3})+(?!\d))", r"\1-", "%d" % int(clean_phone_number[:-1])) + clean_phone_number[-1]
# return formatted_phone_number
# except ValueError:
# return phone_number
def phone_format(n):
# return format(int(n[:-1]), ",").replace(",", "-") + n[-1]
# return phonenumbers.format_number(n, phonenumbers.PhoneNumberFormat.NATIONAL)
formatter = phonenumbers.AsYouTypeFormatter("US")
for digit in re.findall(r'\d', n)[:-1]:
formatter.input_digit(digit)
return formatter.input_digit(re.findall(r'\d', n)[-1])
def my_callback(var, indx, mode):
print(my_var.get())
my_var.set(phone_format(my_var.get()))
label.configure(text=my_var.get())
def callback(event):
entry.icursor(END)
my_var.trace_add('write', my_callback)
label = Label(root)
label.pack(padx=5, pady=5)
entry = Entry(root, textvariable=my_var)
entry.bind("<Key>", callback)
entry.pack(padx=5, pady=5)
root.mainloop()
This was my solution, using phonenumbers
from PyPi, which seemed to make it work.
-
appreciate this but the formatting is quite messed up, have u tried this onn? – Delrius Euphoria May 31 '20 at 18:19
-
Sorry, I am confused, just copied and pasted this and it worked on my system? What eems to be the problem? – jimbob88 May 31 '20 at 18:46
-
the label is not as same as wt we type in the entrybox – Delrius Euphoria May 31 '20 at 18:50
-
@CoolCloud This should be fixed now! Sorry – jimbob88 May 31 '20 at 19:03
-
the same error comes, try entering 055 at the strt and u might notice it – Delrius Euphoria May 31 '20 at 19:33
1
This is a complex example, but it handles more than just phone numbers. It is commented to death.
#widgets.py
import tkinter as tk, re
from dataclasses import dataclass, field
from typing import List, Pattern, Iterable
from copy import deepcopy
Char: Pattern = re.compile('[a-z0-9]', re.I)
''' FormEntryFormat_dc
this serves as a configuration for the behavior of FormEntry
'''
@dataclass
class FormEntryFormat_dc:
valid :Pattern = None #pattern to validate text by
separator :str = None #the separator to use
marks :List = field(default_factory=list) #list of positions to apply separator
strict :bool = False #True|False strict typing
def config(self, ascopy:bool=True, **data):
c = deepcopy(self) if ascopy else self
for key in c.__dict__:
if key in data:
c.__dict__[key] = data[key] #assign new value
return c
#prepare a few formats
TimeFormat = FormEntryFormat_dc(re.compile('^(\d{1,2}(:(\d{1,2}(:(\d{1,2})?)?)?)?)?$' ), ':' , [2, 5])
DateFormat = FormEntryFormat_dc(re.compile('^(\d{1,2}(\\\\(\d{1,2}(\\\\(\d{1,4})?)?)?)?)?$'), '\\', [2, 5])
PhoneFormat = FormEntryFormat_dc(re.compile('^(\d{1,3}(-(\d{1,3}(-(\d{1,4})?)?)?)?)?$' ), '-' , [3, 7], True)
PhoneFormat2 = FormEntryFormat_dc(re.compile('^(\d{1,3}(-(\d{1,7})?)?)?$' ), '-' , [3] , True)
''' FormEntry
an entry with format behavior
'''
class FormEntry(tk.Entry):
@property
def input(self) -> str:
return self.get()
def offset(self, separator:str, marks:Iterable):
sep_marks = [] #cache for positions of already inserted separators
offset = 0 #the overall offset between inserted and expected separator marks
#get a mark for every current separator
for i, c in enumerate(self.input):
if c == separator:
sep_marks.append(i)
#if any sep_marks ~ subtract the value of sep_marks last index
#~from the value of the corresponding index in marks
n = len(sep_marks)
if n:
offset = max(0, marks[n-1]-sep_marks[-1])
return offset
def __init__(self, master, frmt:FormEntryFormat_dc, **kwargs):
tk.Entry.__init__(self, master, **kwargs)
self.valid = frmt.valid
if self.valid:
#register validatecommand and assign to options
vcmd = self.register(self.validate)
self.configure(validate="all", validatecommand=(vcmd, '%P'))
if frmt.marks and frmt.separator:
#bind every key to formatting
self.bind('<Key>', lambda e: self.format(e, frmt.separator, frmt.marks, frmt.strict))
def validate(self, text:str):
return not (self.valid.match(text) is None) #validate with regex
def format(self, event, separator:str, marks:Iterable, strict:bool):
if event.keysym != 'BackSpace': #allow backspace to function normally
i = self.index('insert') #get current index
if Char.match(event.char) is None and (i in marks or not strict):
event.char = separator #overwrite with proper separator
else:
#automatically add separator
if i+self.offset(separator, marks) in marks:
event.char = f'{separator}{event.char}'
self.insert(i, event.char) #validation will check if this is allowed
return 'break'
#main.py (OOP style)
import widgets as ctk #custom tk
import tkinter as tk
class Main(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.title("Formatted Entry")
self.grid_columnconfigure(2, weight=1)
#create labels
self.labels = ['time', 'date', 'phone', 'phone2']
for n, label in enumerate(self.labels):
tk.Label(self, text=f'{label}: ', width=14, font='consolas 12 bold', anchor='w').grid(row=n, column=0, sticky='w')
#create entries
self.entries = []
for n, format in enumerate([ctk.TimeFormat, ctk.DateFormat, ctk.PhoneFormat, ctk.PhoneFormat2]):
self.entries.append(ctk.FormEntry(self, format, width=14, font='consolas 12 bold'))
self.entries[-1].grid(row=n, column=1, sticky='w')
#form submit button
tk.Button(self, text='submit', command=self.submit).grid(column=1, sticky='e')
def submit(self):
for l, e in zip(self.labels, self.entries):
print(f'{l}: {e.input}')
Main().mainloop() if __name__ == "__main__" else None
#main.py (procedural style)
import widgets as ctk #custom tk
import tkinter as tk
if __name__ == "__main__":
root = tk.Tk()
root.title("Formatted Entry")
root.grid_columnconfigure(2, weight=1)
#create labels
labels = ['time', 'date', 'phone', 'phone2']
for n, label in enumerate(labels):
tk.Label(root, text=f'{label}: ', width=14, font='consolas 12 bold', anchor='w').grid(row=n, column=0, sticky='w')
#create entries
entries = []
for n, format in enumerate([ctk.TimeFormat, ctk.DateFormat, ctk.PhoneFormat, ctk.PhoneFormat2]):
entries.append(ctk.FormEntry(root, format, width=14, font='consolas 12 bold'))
entries[-1].grid(row=n, column=1, sticky='w')
def submit():
for l, e in zip(labels, entries):
print(f'{l}: {e.input}')
#form submit button
tk.Button(root, text='submit', command=submit).grid(column=1, sticky='e')
root.mainloop()

OneMadGypsy
- 4,640
- 3
- 10
- 26
-
Thanks for this amazing answer and your time on this, but is there any way without classes? As my GUI has no classes and implementing classes just for the purpose of this is a bit overkill right? – Delrius Euphoria Aug 27 '20 at 21:49
-
@CoolCloud ~ every single widget you use in `tkinter` is a class, including `Tk` and `Toplevel`. If implementing a class was overkill for your project, then you couldn't use `tkinter`, at all. Also, I edited my answer since your comment. I found 2 tiny bugs and fixed them. – OneMadGypsy Aug 27 '20 at 21:59
-
1@CoolCloud ~ I updated my example with 2 example `main.py`. One is OOP style and the other is procedural. – OneMadGypsy Aug 27 '20 at 22:16
-
-
Wow, this works like a CHARM, but how do i get the data from the entry box? `get()` method seems to not work – Delrius Euphoria Aug 28 '20 at 13:04
-
1@CoolCloud ~ I didn't make references in my example. You have to start by making a reference (ex:) `time = ctk.FormatEntry(root, tformat)` and then you can use `time.input`. `.input` is a read-only `@property` so you call it like a var, not a method/function. – OneMadGypsy Aug 28 '20 at 13:08
-
okays, got that up, anyway to change the format a bit, like currently it is 123-102-1712 and make it 101-1203215 – Delrius Euphoria Aug 28 '20 at 13:12
-
Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/220620/discussion-between-michael-guidry-and-cool-cloud). – OneMadGypsy Aug 28 '20 at 13:13
-
1
-
https://stackoverflow.com/questions/63651586/adding-placeholders-to-tkinter-entry-widget-in-a-procedural-way here you go ;) – Delrius Euphoria Aug 29 '20 at 20:41
-
Hey, hi, can you actually tell me what `FormEntryFormat_dc(re.compile('^(\d{1,3}(-(\d{1,7})?)?)?$'), '-' , [3] , True)` would be for a number supposed to insert `-` in the format `12345-12345-12345` – Delrius Euphoria Oct 27 '20 at 14:08
-
@CoolCloud ~ `FormEntryFormat_dc(re.compile('^(\d{1,5}(-(\d{1,5}(-(\d{1,5})?)?)?)?)?$'), '-' , [5, 11], True)`. It's pretty simple. You want 5 characters, dash, 5 characters, dash, 5 characters. So just turn the max number to 5 in the regex ranges, and customize the position list for the proper dash positions ([5, 11]). Keep in mind that this regex is just validating as you type. It doesn't care what the final result is. That's why there are ranges from 1 to max. It has to do that to allow you to type. – OneMadGypsy Oct 27 '20 at 18:48
-
Thanks man!!! Can you help me out once more? Too small to ask as a Q, any idea on how to allow just 5 numbers on an entry field? without using regex or something? – Delrius Euphoria Oct 27 '20 at 18:50
-
@CoolCloud ~ simply capture typing into the field and only allow it to add new characters if the current string has a length less than 5. Use `vcmd`. That way you can simply `return len(entry.get()) < 5)`, which if false, will not type the character. – OneMadGypsy Oct 27 '20 at 18:56
-
@CoolCloud ~ However, you will want to make sure that the keypress was an actual character before you make that return. For instance, if `entry.get()` has a length of 5 and you press `backspace` it will still return false and disallow the backspace from happening. – OneMadGypsy Oct 27 '20 at 19:17
1
Here is a procedural example. The example is very heavily commented.
import tkinter as tk, re
from dataclasses import dataclass, field
from typing import List, Pattern, Iterable
from copy import deepcopy
Char: Pattern = re.compile('[a-z0-9]', re.I)
''' FormField_dc
this serves as a configuration for the behavior of form_field
'''
@dataclass
class FormEntryFormat_dc:
valid :Pattern = None #pattern to validate text by
separator :str = None #the separator to use
marks :List = field(default_factory=list) #list of positions to apply separator
strict :bool = False #True|False strict typing
def config(self, ascopy:bool=True, **data):
c = deepcopy(self) if ascopy else self
for key in c.__dict__:
if key in data:
c.__dict__[key] = data[key] #assign new value
return c
#prepare a few formats
TimeFormat = FormEntryFormat_dc(re.compile('^(\d{1,2}(:(\d{1,2}(:(\d{1,2})?)?)?)?)?$' ), ':' , [2, 5])
DateFormat = FormEntryFormat_dc(re.compile('^(\d{1,2}(\\\\(\d{1,2}(\\\\(\d{1,4})?)?)?)?)?$'), '\\', [2, 5])
PhoneFormat = FormEntryFormat_dc(re.compile('^(\d{1,3}(-(\d{1,3}(-(\d{1,4})?)?)?)?)?$' ), '-' , [3, 7], True)
PhoneFormat2 = FormEntryFormat_dc(re.compile('^(\d{1,3}(-(\d{1,7})?)?)?$' ), '-' , [3] , True)
''' FormField
An entry field intended to force a specific format while the user types
'''
def form_field(master, f:FormEntryFormat_dc, **kwargs) -> tk.Entry:
entry = tk.Entry(master, **kwargs)
def offset(separator:str, marks:Iterable):
sep_marks = [] #cache for positions of already inserted separators
offset = 0 #the overall offset between inserted and expected separator marks
#get a mark for every current separator
for i, c in enumerate(entry.get()):
if c == separator:
sep_marks.append(i)
#if any sep_marks ~ subtract the value of sep_marks last index
#~from the value of the corresponding index in marks
n = len(sep_marks)
if n:
offset = max(0, marks[n-1]-sep_marks[-1])
return offset
#test text against validity conditions
def validate(text):
#if numeric check is True and len(text) > 0
return not (f.valid.match(text) is None) #validate with regex
if f.valid:
#register validatecommand and assign to options
vcmd = entry.register(validate)
entry.configure(validate="all", validatecommand=(vcmd, '%P'))
#add separators when entry "insert" index equals a mark
#~and separator isn't already present
def format(event, separator:str, marks:Iterable, strict:bool):
#allow backspace to function normally
if event.keysym != 'BackSpace':
i = entry.index('insert') #get current index
if Char.match(event.char) is None and (i in marks or not strict):
event.char = separator #overwrite with proper separator
else:
#automatically add separator
if i+offset(separator, marks) in marks:
event.char = f'{separator}{event.char}'
entry.insert(i, event.char) #validation will check if this is allowed
return 'break'
if f.marks and f.separator:
#bind every keypress to formatting
entry.bind('<Key>', lambda e: format(e, f.separator, f.marks, f.strict))
return entry
##USAGE EXAMPLE
if __name__ == "__main__":
root = tk.Tk()
root.title("Formatted Entry")
root.grid_columnconfigure(2, weight=1)
#create labels
labels = ['time', 'date', 'phone', 'phone2']
for n, label in enumerate(labels):
tk.Label(root, text=f'{label}: ', width=14, font='consolas 12 bold', anchor='w').grid(row=n, column=0, sticky='w')
#create entries
entries = []
for n, format in enumerate([TimeFormat, DateFormat, PhoneFormat, PhoneFormat2]):
entries.append(form_field(root, format, width=14, font='consolas 12 bold'))
entries[-1].grid(row=n, column=1, sticky='w')
def submit():
for l, e in zip(labels, entries):
print(f'{l}: {e.get()}')
#form submit button
tk.Button(root, text='submit', command=submit).grid(column=1, sticky='e')
root.mainloop()

OneMadGypsy
- 4,640
- 3
- 10
- 26