Правильный ответ - используйте validatecommand
атрибут виджета. К сожалению, эта функция плохо документирована в мире Tkinter, хотя в мире Tk она достаточно документирована. Несмотря на то, что он плохо документирован, в нем есть все, что вам нужно для проверки, не прибегая к привязкам или отслеживанию переменных или изменению виджета изнутри процедуры проверки.
Хитрость заключается в том, чтобы знать, что вы можете заставить Tkinter передавать специальные значения вашей команде проверки. Эти значения предоставляют вам всю информацию, которую вам нужно знать, чтобы решить, действительны ли данные или нет: значение до редактирования, значение после редактирования, если редактирование допустимо, и несколько других битов информации. Однако, чтобы использовать их, вам нужно немного поработать, чтобы передать эту информацию вашей команде проверки.
Примечание: важно, чтобы команда проверки возвращала либо, True
либоFalse
. Все остальное приведет к отключению проверки для виджета.
Вот пример, который допускает только строчные буквы (и выводит все эти забавные значения):
import tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
vcmd = (self.register(self.onValidate),
'%d', '%i', '%P', '%s', '%S', '%v', '%V', '%W')
self.entry = tk.Entry(self, validate="key", validatecommand=vcmd)
self.text = tk.Text(self, height=10, width=40)
self.entry.pack(side="top", fill="x")
self.text.pack(side="bottom", fill="both", expand=True)
def onValidate(self, d, i, P, s, S, v, V, W):
self.text.delete("1.0", "end")
self.text.insert("end","OnValidate:\n")
self.text.insert("end","d='%s'\n" % d)
self.text.insert("end","i='%s'\n" % i)
self.text.insert("end","P='%s'\n" % P)
self.text.insert("end","s='%s'\n" % s)
self.text.insert("end","S='%s'\n" % S)
self.text.insert("end","v='%s'\n" % v)
self.text.insert("end","V='%s'\n" % V)
self.text.insert("end","W='%s'\n" % W)
if S == S.lower():
return True
else:
self.bell()
return False
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
Дополнительные сведения о том, что происходит под капотом при вызове register
метода, см. В разделе Проверка ввода tkinter.
После изучения и экспериментов с кодом Брайана я создал минимальную версию проверки ввода. В следующем коде появится поле ввода и будут приниматься только числовые цифры.
from tkinter import * root = Tk() def testVal(inStr,acttyp): if acttyp == '1': #insert if not inStr.isdigit(): return False return True entry = Entry(root, validate="key") entry['validatecommand'] = (entry.register(testVal),'%P','%d') entry.pack() root.mainloop()
Возможно, я должен добавить, что я все еще изучаю Python, и я с радостью приму любые комментарии / предложения.
источник
entry.configure(validatecommand=...)
и пишутtest_val
вместоtestVal
, но это хороший и простой пример.Используйте
Tkinter.StringVar
для отслеживания значения виджета Entry. Вы можете проверить значениеStringVar
, установивtrace
на нем.Вот небольшая рабочая программа, которая принимает только допустимые числа с плавающей запятой в виджете Entry.
from Tkinter import * root = Tk() sv = StringVar() def validate_float(var): new_value = var.get() try: new_value == '' or float(new_value) validate.old_value = new_value except: var.set(validate.old_value) validate.old_value = '' # trace wants a callback with nearly useless parameters, fixing with lambda. sv.trace('w', lambda nm, idx, mode, var=sv: validate_float(var)) ent = Entry(root, textvariable=sv) ent.pack() root.mainloop()
источник
Изучая ответ Брайана Окли , что-то подсказало мне, что можно разработать гораздо более общее решение. В следующем примере представлены перечисление режимов, словарь типов и функция настройки для целей проверки. См. Строку 48 для примера использования и демонстрации его простоты.
#! /usr/bin/env python3 # /programming/4140437 import enum import inspect import tkinter from tkinter.constants import * Mode = enum.Enum('Mode', 'none key focus focusin focusout all') CAST = dict(d=int, i=int, P=str, s=str, S=str, v=Mode.__getitem__, V=Mode.__getitem__, W=str) def on_validate(widget, mode, validator): # http://www.tcl.tk/man/tcl/TkCmd/ttk_entry.htm#M39 if mode not in Mode: raise ValueError('mode not recognized') parameters = inspect.signature(validator).parameters if not set(parameters).issubset(CAST): raise ValueError('validator arguments not recognized') casts = tuple(map(CAST.__getitem__, parameters)) widget.configure(validate=mode.name, validatecommand=[widget.register( lambda *args: bool(validator(*(cast(arg) for cast, arg in zip( casts, args)))))]+['%' + parameter for parameter in parameters]) class Example(tkinter.Frame): @classmethod def main(cls): tkinter.NoDefaultRoot() root = tkinter.Tk() root.title('Validation Example') cls(root).grid(sticky=NSEW) root.grid_rowconfigure(0, weight=1) root.grid_columnconfigure(0, weight=1) root.mainloop() def __init__(self, master, **kw): super().__init__(master, **kw) self.entry = tkinter.Entry(self) self.text = tkinter.Text(self, height=15, width=50, wrap=WORD, state=DISABLED) self.entry.grid(row=0, column=0, sticky=NSEW) self.text.grid(row=1, column=0, sticky=NSEW) self.grid_rowconfigure(1, weight=1) self.grid_columnconfigure(0, weight=1) on_validate(self.entry, Mode.key, self.validator) def validator(self, d, i, P, s, S, v, V, W): self.text['state'] = NORMAL self.text.delete(1.0, END) self.text.insert(END, 'd = {!r}\ni = {!r}\nP = {!r}\ns = {!r}\n' 'S = {!r}\nv = {!r}\nV = {!r}\nW = {!r}' .format(d, i, P, s, S, v, V, W)) self.text['state'] = DISABLED return not S.isupper() if __name__ == '__main__': Example.main()
источник
Ответ Брайана правильный, однако никто не упомянул атрибут «invalidcommand» виджета tkinter.
Хорошее объяснение здесь: http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/entry-validation.html
Копирование / вставка текста в случае неработающей ссылки
Виджет Entry также поддерживает параметр invalidcommand, который указывает функцию обратного вызова, которая вызывается всякий раз, когда validatecommand возвращает False. Эта команда может изменять текст в виджете, используя метод .set () для связанной текстовой переменной виджета. Настройка этого параметра работает так же, как настройка команды validate. Вы должны использовать метод .register (), чтобы обернуть вашу функцию Python; этот метод возвращает имя обернутой функции в виде строки. Затем вы передадите в качестве значения параметра invalidcommand либо эту строку, либо как первый элемент кортежа, содержащего коды подстановки.
Примечание: есть только одна вещь, которую я не могу понять, как это сделать: если вы добавляете проверку к записи, а пользователь выбирает часть текста и вводит новое значение, нет возможности захватить исходное значение и сбросить Вход. Вот пример
источник
Вот простой способ проверить значение ввода, который позволяет пользователю вводить только цифры:
import tkinter # imports Tkinter module root = tkinter.Tk() # creates a root window to place an entry with validation there def only_numeric_input(P): # checks if entry's value is an integer or empty and returns an appropriate boolean if P.isdigit() or P == "": # if a digit was entered or nothing was entered return True return False my_entry = tkinter.Entry(root) # creates an entry my_entry.grid(row=0, column=0) # shows it in the root window using grid geometry manager callback = root.register(only_numeric_input) # registers a Tcl to Python callback my_entry.configure(validate="key", validatecommand=(callback, "%P")) # enables validation root.mainloop() # enters to Tkinter main event loop
PS: Этот пример может быть очень полезен для создания такого приложения, как calc.
источник
import tkinter tk=tkinter.Tk() def only_numeric_input(e): #this is allowing all numeric input if e.isdigit(): return True #this will allow backspace to work elif e=="": return True else: return False #this will make the entry widget on root window e1=tkinter.Entry(tk) #arranging entry widget on screen e1.grid(row=0,column=0) c=tk.register(only_numeric_input) e1.configure(validate="key",validatecommand=(c,'%P')) tk.mainloop() #very usefull for making app like calci
источник
Ответ на проблему орионроберта простой проверки при текста посредством выделения вместо отдельных удалений или вставок:
Замена выделенного текста обрабатывается как удаление с последующей вставкой. Это может привести к проблемам, например, когда при удалении курсор должен перемещаться влево, а при подстановке - вправо. К счастью, эти два процесса выполняются сразу один за другим. Следовательно, мы можем различать удаление само по себе и удаление, за которым непосредственно следует вставка из-за замены, поскольку последняя не меняет флаг простоя между удалением и вставкой.
Это используется с помощью substitutionFlag и файла
Widget.after_idle()
.after_idle()
выполняет лямбда-функцию в конце очереди событий:class ValidatedEntry(Entry): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.tclValidate = (self.register(self.validate), '%d', '%i', '%P', '%s', '%S', '%v', '%V', '%W') # attach the registered validation function to this spinbox self.config(validate = "all", validatecommand = self.tclValidate) def validate(self, type, index, result, prior, indelText, currentValidationMode, reason, widgetName): if typeOfAction == "0": # set a flag that can be checked by the insertion validation for being part of the substitution self.substitutionFlag = True # store desired data self.priorBeforeDeletion = prior self.indexBeforeDeletion = index # reset the flag after idle self.after_idle(lambda: setattr(self, "substitutionFlag", False)) # normal deletion validation pass elif typeOfAction == "1": # if this is a substitution, everything is shifted left by a deletion, so undo this by using the previous prior if self.substitutionFlag: # restore desired data to what it was during validation of the deletion prior = self.priorBeforeDeletion index = self.indexBeforeDeletion # optional (often not required) additional behavior upon substitution pass else: # normal insertion validation pass return True
Конечно, после замены, при проверке удаляемой части, никто все равно не знает, последует ли вставка. К счастью , однако, с:
.set()
,.icursor()
,.index(SEL_FIRST)
,.index(SEL_LAST)
,.index(INSERT)
, мы можем достичь наиболее желаемого поведения ретроспективно (так как сочетание нашего нового substitutionFlag с прошивкой является новым уникальным и завершающим событием.источник