【问题标题】:Auto add dashes to entryboxes in tkinter自动将破折号添加到 tkinter 中的输入框
【发布时间】:2020-09-19 00:08:38
【问题描述】:

当人们键入他们的电话号码时,有没有办法在电话号码中自动添加破折号,比如电话号码是 5551111234,但是当他们在输入框中键入它时,该号码应该显示为带有连字符的数字自动点赞555-1111234

【问题讨论】:

    标签: python tkinter phone-number tkinter-entry


    【解决方案1】:

    这是一个程序示例。该示例被大量评论。

    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()
    

    【讨论】:

      【解决方案2】:

      这是一个复杂的示例,但它处理的不仅仅是电话号码。被评论死了。

      #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()
      

      【讨论】:

      • 感谢您的精彩回答和您在这方面的时间,但是有什么办法可以不用上课吗?由于我的 GUI 没有类,仅为此目的实现类有点矫枉过正,对吧?
      • @CoolCloud ~ 你在tkinter 中使用的每一个小部件都是一个类,包括TkToplevel。如果实现一个类对你的项目来说太过分了,那么你根本就不能使用tkinter。另外,自从您发表评论以来,我编辑了我的答案。我发现了 2 个小错误并修复了它们。
      • @CoolCloud ~ 我用 2 个示例 main.py 更新了我的示例。一种是 OOP 风格,另一种是程序化。
      • @CoolCloud ~ 我没有在我的例子中引用。您必须首先引用(例如:)time = ctk.FormatEntry(root, tformat),然后您可以使用time.input.input 是只读的 @property,所以你把它当成 var,而不是方法/函数。
      • @CoolCloud ~ 一切都已修复
      【解决方案3】:

      我使用了跟踪 tkinter 变量的 this example 的组合并将其与 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"\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()
      

      这是我的解决方案,使用来自 PyPi 的 phonenumbers,它似乎可以正常工作。

      【讨论】:

      • 欣赏这个,但格式很乱,你试过这个吗?
      • 对不起,我很困惑,只是复制并粘贴了这个,它在我的系统上工作了吗?问题出在哪里?
      • 标签和我们在输入框中输入的wt不一样
      • @CoolCloud 现在应该修复了!对不起
      • 同样的错误来了,尝试在strt输入055,你可能会注意到它
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-04-07
      • 2013-06-03
      • 2015-09-29
      • 2018-05-16
      • 2013-06-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多