自动将破折号添加到 tkinter 中的输入框
Auto add dashes to entryboxes in tkinter
当人们输入他们的 phone 号码时,有没有一种方法可以在 phone 号码中自动添加破折号,比如说 phone 号码是 5551111234,但是当他们输入输入框的数字应显示连字符 b/w 数字自动像 555-1111234.
我结合使用了两者 this example of tracing tkinter variables and combined it with this answer,我不能 100% 确定这是否是正确的美国格式,因为我住在英国,我们这里的格式不同,但这是粗略的示例这将如何运作:
# 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"-", "%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()
备选
# 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"-", "%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()
这是我的解决方案,使用 PyPi 中的 phonenumbers
,似乎可以正常工作。
这是一个复杂的示例,但它处理的不仅仅是 phone 个数字。被评论死了
#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()
这是一个程序示例。这个例子有很多评论。
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()
当人们输入他们的 phone 号码时,有没有一种方法可以在 phone 号码中自动添加破折号,比如说 phone 号码是 5551111234,但是当他们输入输入框的数字应显示连字符 b/w 数字自动像 555-1111234.
我结合使用了两者 this example of tracing tkinter variables and combined it with this answer,我不能 100% 确定这是否是正确的美国格式,因为我住在英国,我们这里的格式不同,但这是粗略的示例这将如何运作:
# 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"-", "%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()
备选
# 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"-", "%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()
这是我的解决方案,使用 PyPi 中的 phonenumbers
,似乎可以正常工作。
这是一个复杂的示例,但它处理的不仅仅是 phone 个数字。被评论死了
#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()
这是一个程序示例。这个例子有很多评论。
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()