以下脚本创建一个ttk.Entry
小部件,该小部件仅接受可以转换为浮点类型的条目。当我使用鼠标指针选择键入的条目,然后按新的数字条目时,我希望新的数字条目替换所选的条目。目前,该行为不会发生。而是,新的数字条目将出现在所选数字的左侧。如何获得所需的替换行为?
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.Entry
小部件中的选定类型条目可以通过{{ 1}}方法。但是,我还没有弄清楚如何链接这些方法以获得所需的行为。
将这些句子附加到.selection_get()
方法的末尾。
__init__()
答案 0 :(得分:1)
我想分享一下我在下面提出的解决方案。我发现,在ttk.Entry
文本字段中选择一个字符串/子字符串时,tkinter将默认执行两步验证。 (1)将选择的字符串/子字符串视为要进行的第一次编辑。 (2)将按键输入作为要进行的第二次编辑。因此,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()
更新:
下面的脚本显示了一种改进的算法,该算法仅允许在tkinter Entry
小部件中使用浮点类型的条目(包括具有指数的条目)。请使用这个。
优势:
.selection_present()
方法
它使用%d
,它是
validatecommand
。 %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()
答案 1 :(得分:1)
这是怎么回事:当您选择一定范围的文本然后按一个键时,tkinter必须做两件事:必须删除所选的文本,然后必须插入新的文本。
首先调用处理程序进行删除。因为删除操作导致条目窗口小部件完全为空,并且您不能将空字符串转换为浮点型,所以处理程序将返回False
。这样可以防止删除。
接下来,将调用您的处理程序进行插入。旧文本仍然存在。您允许插入,因此最终结果是未删除所选文本,而是在其之前插入了新文本。
最简单的解决方案是允许使用空字符串。然后,您可以简单地验证可以将非空字符串转换为浮点数。
示例:
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()