代码之家  ›  专栏  ›  技术社区  ›  Sun Bear

用“validatecommand”选项替换ttk.Entry小部件中的选定项

  •  2
  • Sun Bear  · 技术社区  · 8 年前

    下面的脚本创建 ttk.Entry 只接受可转换为浮点类型的条目的小部件。当我用鼠标指针选择输入的条目,然后按new numeric entries时,我希望新的numeric entries替换所选条目。目前这种行为没有发生。相反,新的数字条目将出现在所选数字的左侧。如何获得所需的替换行为?

    import tkinter as tk  # python 3.x
    import tkinter.ttk as ttk  # python 3.x
    
    class Example(ttk.Frame):
    
        def __init__(self, parent):
            super().__init__(parent)
    
            # %P = value of the entry if the edit is allowed
            # %S = the text string being inserted or deleted, if any    
            vcmd = (self.register(self.onValidate),'%P', '%S')
            self.entry = ttk.Entry(self, validate="key", validatecommand=vcmd)
            self.entry.pack(side="top", fill="x")
    
        def onValidate(self, P, S):
            # Disallow anything but '0123456789.+-'
            if S in '0123456789.+-':
                try:
                    float(P)
                    print('float(P) True')
                    return True
                except ValueError:
                    print('float(P) False')
                    return False
            else:
                print('S in 0123456789.+- False')
                return False
    
    if __name__ == "__main__":
        root = tk.Tk()
        Example(root).pack(fill="both", expand=True)
        root.mainloop()
    

    更新: 使用 .bind '<ButtonRelease>' ttk.入口 小部件可以通过 .selection_get()

    把这些句子附加在句子的末尾 __init__()

            self.entry.bind( '<ButtonRelease>', self.selecttext )
    
        def selecttext(self, event):
            try:
                print( 'selection = ', self.entry.selection_get() )
            except tk.TclError:
                pass
    
    2 回复  |  直到 8 年前
        1
  •  1
  •   Bryan Oakley    8 年前

    所发生的事情是这样的:当您选择一个文本范围,然后按下一个键,tkinter必须做两件事:它必须删除选定的文本,然后它必须插入新的文本。

    首先为delete调用处理程序。因为delete导致entry小部件完全为空,并且不能将空字符串转换为float,所以处理程序返回 False . 这样可以防止发生删除操作。

    最简单的解决方案是允许空字符串。然后,您可以简单地验证非空字符串是否可以转换为float。

    import tkinter as tk  # python 3.x
    import tkinter.ttk as ttk  # python 3.x
    
    class Example(ttk.Frame):
    
        def __init__(self, parent):
            super().__init__(parent)
    
            # %P = value of the entry if the edit is allowed
            vcmd = (self.register(self.onValidate),'%P')
            self.entry = ttk.Entry(self, validate="key", validatecommand=vcmd)
            self.entry.pack(side="top", fill="x")
    
        def onValidate(self, P):
            if P.strip() == "":
                # allow blank string
                return True
            try:
                float(P)
                return True
            except:
                return False
    
    if __name__ == "__main__":
        root = tk.Tk()
        Example(root).pack(fill="both", expand=True)
        root.mainloop()
    
        2
  •  1
  •   Sun Bear    8 年前

    我想分享我在下面得出的解决方案。我发现当 ttk.Entry validatecommand 会打两次电话。我还在脚本中添加了一些注释。

    import tkinter as tk  # python 3.x
    import tkinter.ttk as ttk  # python 3.x
    
    class Example(ttk.Frame):
    
        def __init__(self, parent):
            super().__init__(parent)
    
            # %P = value of the entry if the edit is allowed
            # %S = the text string being inserted or deleted, if any
            # %s = value of entry prior to editing
            vcmd = (self.register(self.onValidate),'%P', '%S', '%s')
            self.text = tk.StringVar()
            self.entry = ttk.Entry(self, validate="key", validatecommand=vcmd)
            self.entry.pack(side="top", fill="x")
    
        def onValidate(self, P, S, s):
            # Disallow anything but '0123456789.+-'
            selected = None
            print('\nP={}, S={}, s={}'.format(P, S, s) )
    
            try:
    
                if S in '0123456789.+-' or float(S):
                    if self.entry.selection_present():
                        print('With Selection')
                        selected = self.entry.selection_get()
                        print('selected = ', selected )
                        # Notes:
                        # - When .selection_present()=True, I discovered that 
                        #   tkinter will return by default:
                        #    P = s w/o 'selected'
                        #    S = 'selected' and not the keypressed
                        #    s = value of entry prior to editing.
                        # - I should "return True" so that tkinter will trigger method
                        #   self.onValidate() again. This time,
                        #    P = value of the entry if the keypress edit is allowed.
                        #    S = the key pressed
                        #    s = P from previous attempt.
                        #   As such satisfy .selection_present()=False.
                        return True
                    else:
                        print('No Selection')
                        try:
                            float(P); print('True')
                            return True
                        except ValueError:
                            print(ValueError, 'float({}) False'.format(P))
                            return False
                else:
                    print('S in 0123456789.+- False')
                    return False
    
            except ValueError:
                print('Try with Exception')
                print(ValueError, 'float({}) False'.format(P))
                return False
    
    
    if __name__ == "__main__":
        root = tk.Tk()
        Example(root).pack(fill="both", expand=True)
        root.mainloop()
    

    下面的脚本显示 一种只允许浮点型条目(包括指数型条目)的改进算法 在一辆卡车里 Entry

    优势:

    1. 此算法允许将浮点型数字及其指数输入tkinter Entry小部件。
    2. 该算法避免了 .selection_present() 方法 它使用 %d 它是 %d级 具有值以指示与删除(或选择)、插入和其他相关的场景(即“焦点输入”、“焦点输出”或“文本变量值更改”)。
    3. 此算法中考虑的场景比我的第一个算法更全面。 .

    改进算法:

    import tkinter as tk  # python 3.x
    import tkinter.ttk as ttk  # python 3.x
    
    class Example(ttk.Frame):
    
        def __init__(self, parent):
            super().__init__(parent)
    
            # %d = Type of action (1=insert, 0=delete, -1 for others)
            # %P = value of the entry if the edit is allowed
            # %S = the text string being inserted or deleted, if any
            vcmd = (self.register(self.onValidate_Float), '%d','%P','%S')
            self.entry = ttk.Entry(self, validate="key", validatecommand=vcmd)
            self.entry.pack(side="top", fill="x")
    
        def onValidate_Float(self, d, P, S):
            '''Allow only float type insertions (including exponents).
    
            Notes: 1. The culminated insertions can fail to convert to a float.
                      This scenario occurs ONLY when the exponent entry is not 
                      completed, i.e. when 'e-' and 'e+' are supplied only.
                   2. User of this method should remember that when they bind a
                      handle and '<KeyPress-Return>' to the tkinter Entry widget,
                      the handle can encounter error associated with processing
                      "float(numeric)" when numeric=1.4e- or 1.4e+ (as example).
                   3. I discovered that validatecommand uses %d to determine
                      the type of actions it take. As such, it is useful to
                      structure validatecommand instructions according to scenarios
                      d='0', d='1' and d='-1'. 
            '''
    
            def _checkDecimal(P):
                '''Return True when decimal does not occur in exponent.'''
                decimal_index = P.find('.')
                exponent_index = P.find('e')
                if exponent_index > decimal_index:
                    return True
                else:
                    return False
    
            print('\nd={}, P={}, S={}'.format(d, P, S) )
    
            if d == '0': #Delete Selection or keypress "Delete"/"Backspace"
                print('Allow delete action regardless of keypress.')
                return True
    
            elif d == '1': #Insert keypress
                print('d==1, Insert keypress.')
                try:
                    if S in '0123456789e.+-':
                        float(P); print('True')
                        return True
                    else:
                        print('False')
                        return False
                except ValueError:
                    print('float({}) ValueError.'.format(P))
                    if P.count('e')>1: return False
                    if P.count('e.')>0: return False
                    if P.count('-e')>0: return False
                    if P.count('+e')>0: return False
                    if P.find('e') == 0: return False
                    if P.count('.')>1: return False
    
                    if P[0]=="-":
                        print('P[0]=="-"')
                        if P.count("e-")>=1:
                            print('P.count("e-")>=1')
                            if P.count("-")>2: return False
                            if P.count("+")>0: return False
                            if not _checkDecimal(P): return False
                        elif P.count("e+")>=1:
                            print('P.count("e+")>=1')
                            if P.count("+")>1: return False
                            if P.count("-")>1: return False
                            if not _checkDecimal(P): return False
                        else:
                            print('no e- or e+')
                            if P.find('.') == 1: return False #disallow '-.'
                            if P.find('+') >= 1: return False #disallow '-+'
                            if P.find('-') >= 1: return False #disallow '--'
                            if P.count("-")>1: return False
                            if P.count("+")>1: return False
    
                    elif P[0]=="+":
                        print('P[0]=="+"')
                        if P.count("e-")>=1:
                            print('P.count("e-")>=1')
                            if P.count("-")>1: return False
                            if P.count("+")>1: return False
                            if not _checkDecimal(P): return False
                        elif P.count("e+")>=1:
                            print('P.count("e+")>=1')
                            if P.count("+")>2: return False
                            if P.count("-")>0: return False
                            if not _checkDecimal(P): return False
                        else:
                            print('no e- or e+')
                            if P.find('.') == 1: return False #disallow '+.'
                            if P.find('+') >= 1: return False #disallow '++'
                            if P.find('-') >= 1: return False #disallow '+-'
                            if P.count("-")>1: return False
                            if P.count("+")>1: return False
    
                    else:
                        print('P[0] is a number') 
                        if P.count("e-")>=1:
                            print('P.count("e-")>=1')
                            if P.count("-")>1: return False
                            if P.count("+")>0 : return False
                            if not _checkDecimal(P): return False
                        elif P.count("e+")>=1:
                            print('P.count("e+")>=1')
                            if P.count("+")>1: return False
                            if P.count("-")>0: return False
                            if not _checkDecimal(P): return False
                        else:
                            print('no e- or e+')
                            if P.count("-")>0: return False
                            if P.count("+")>0: return False
    
                    return True #True for all other insertion exceptions.
    
            elif d == '-1': #During focus in, focus out, or textvariable changes
                print('d==-1, During focus in, focus out, or textvariable changes')
                return True
    
    
    if __name__ == "__main__":
        root = tk.Tk()
        Example(root).pack(fill="both", expand=True)
        root.mainloop()