我想分享我在下面得出的解决方案。我发现当
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
优势:
-
此算法允许将浮点型数字及其指数输入tkinter Entry小部件。
-
该算法避免了
.selection_present()
方法
它使用
%d
它是
%d级
具有值以指示与删除(或选择)、插入和其他相关的场景(即“焦点输入”、“焦点输出”或“文本变量值更改”)。
-
此算法中考虑的场景比我的第一个算法更全面。
.
改进算法:
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()