Есть ли способ автоматически добавлять тире в номер телефона, когда человек вводит свой номер телефона, например, например, номер телефона 5551111234, но когда он вводит его в поле ввода, номер должен автоматически отображаться с дефисом ч/б номер, например < сильный>555-1111234.
Автоматически добавлять тире в поля ввода в tkinter
Ответы (3)
Это сложный пример, но он обрабатывает не только телефонные номера. Это закомментировано до смерти.
#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()
person
Michael Guidry
schedule
27.08.2020
Спасибо за этот удивительный ответ и ваше время на это, но есть ли способ без классов? Поскольку в моем графическом интерфейсе нет классов, и реализация классов только для этой цели немного излишняя, верно?
- person Cool Cloud; 28.08.2020
@CoolCloud ~ каждый виджет, который вы используете в
tkinter
, является классом, включая Tk
и Toplevel
. Если реализация класса была излишним для вашего проекта, то вы вообще не могли использовать tkinter
. Кроме того, я отредактировал свой ответ после вашего комментария. Я нашел 2 маленькие ошибки и исправил их.
- person Michael Guidry; 28.08.2020
@CoolCloud ~ Я обновил свой пример двумя примерами
main.py
. Один в стиле ООП, а другой процедурный.
- person Michael Guidry; 28.08.2020
Спасибо за ваше время, я скоро проверю и приму его.
- person Cool Cloud; 28.08.2020
Вау, это работает как CHARM, но как мне получить данные из поля ввода?
get()
метод не работает
- person Cool Cloud; 28.08.2020
@CoolCloud ~ Я не делал ссылок в своем примере. Вы должны начать с создания ссылки (например:)
time = ctk.FormatEntry(root, tformat)
, а затем вы можете использовать time.input
. .input
- это @property
только для чтения, поэтому вы называете его как переменную, а не метод/функцию.
- person Michael Guidry; 28.08.2020
хорошо, понял, в любом случае немного изменить формат, например, сейчас это 123-102-1712 и сделать его 101-1203215
- person Cool Cloud; 28.08.2020
Давайте продолжим обсуждение в чате.
- person Michael Guidry; 28.08.2020
@CoolCloud ~ все исправлено
- person Michael Guidry; 29.08.2020
stackoverflow.com/questions/63651586/ вот, пожалуйста;)
- person Cool Cloud; 29.08.2020
Эй, привет, не могли бы вы сказать мне, что
FormEntryFormat_dc(re.compile('^(\d{1,3}(-(\d{1,7})?)?)?$'), '-' , [3] , True)
будет для числа, которое должно вставлять -
в формате 12345-12345-12345
- person Cool Cloud; 27.10.2020
@CoolCloud ~
FormEntryFormat_dc(re.compile('^(\d{1,5}(-(\d{1,5}(-(\d{1,5})?)?)?)?)?$'), '-' , [5, 11], True)
. Это довольно просто. Вы хотите 5 символов, тире, 5 символов, тире, 5 символов. Поэтому просто измените максимальное число на 5 в диапазонах регулярных выражений и настройте список позиций для правильных позиций тире ([5, 11]). Имейте в виду, что это регулярное выражение просто проверяется при вводе. Неважно, каков будет конечный результат. Вот почему существуют диапазоны от 1 до макс. Он должен сделать это, чтобы позволить вам печатать.
- person Michael Guidry; 27.10.2020
Спасибо чувак!!! Вы можете помочь мне еще раз? Слишком мало, чтобы спросить в качестве вопроса, есть идеи о том, как разрешить только 5 чисел в поле ввода? без использования регулярных выражений или чего-то еще?
- person Cool Cloud; 27.10.2020
@CoolCloud ~ просто запишите ввод в поле и разрешите добавлять новые символы только в том случае, если текущая строка имеет длину менее 5. Используйте
vcmd
. Таким образом, вы можете просто return len(entry.get()) < 5)
, что, если оно ложно, не будет вводить символ.
- person Michael Guidry; 27.10.2020
@CoolCloud ~ Тем не менее, вы должны убедиться, что нажатие клавиши было фактическим символом, прежде чем выполнять этот возврат. Например, если
entry.get()
имеет длину 5, и вы нажмете backspace
, он все равно вернет false и запретит выполнение возврата.
- person Michael Guidry; 27.10.2020
Я использовал комбинацию обоих этого примера трассировки переменных tkinter и объединил его с этим ответом, я не уверен на 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"\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()
Альтернатива
# 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()
Это было мое решение с использованием phonenumbers
из PyPi, которое, казалось, заставило его работать.
person
jimbob88
schedule
31.05.2020
ценю это, но форматирование довольно испорчено, вы пробовали это?
- person Cool Cloud; 31.05.2020
Извините, я запутался, просто скопировал и вставил это, и это сработало в моей системе? В чем проблема?
- person jimbob88; 31.05.2020
метка не такая, как мы вводим в поле ввода
- person Cool Cloud; 31.05.2020
@CoolCloud Это должно быть исправлено сейчас! Прости
- person jimbob88; 31.05.2020
такая же ошибка, попробуй ввести 055 на ул, может и заметишь
- person Cool Cloud; 31.05.2020
Вот процедурный пример. Пример очень сильно прокомментирован.
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()
person
Michael Guidry
schedule
27.08.2020