A resposta correta é: use o validatecommand
atributo do widget. Infelizmente, esse recurso está seriamente sub documentado no mundo Tkinter, embora seja suficientemente documentado no mundo Tk. Mesmo que não seja bem documentado, ele tem tudo que você precisa para fazer a validação sem recorrer a ligações ou rastrear variáveis, ou modificar o widget de dentro do procedimento de validação.
O truque é saber que você pode fazer com que o Tkinter passe valores especiais para o seu comando de validação. Esses valores fornecem todas as informações que você precisa saber para decidir se os dados são válidos ou não: o valor antes da edição, o valor após a edição se a edição for válida e vários outros bits de informação. Para usá-los, no entanto, você precisa fazer um pequeno vodu para que essas informações sejam passadas para seu comando de validação.
Nota: é importante que o comando de validação retorne True
ou False
. Qualquer outra coisa fará com que a validação seja desativada para o widget.
Aqui está um exemplo que permite apenas letras minúsculas (e imprime todos os valores funky):
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()
Para obter mais informações sobre o que acontece nos bastidores quando você chama o register
método, consulte Validação de entrada tkinter
Depois de estudar e experimentar o código de Bryan, produzi uma versão mínima de validação de entrada. O código a seguir abrirá uma caixa de entrada e aceitará apenas dígitos numéricos.
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()
Talvez eu deva acrescentar que ainda estou aprendendo Python e terei prazer em aceitar todo e qualquer comentário / sugestão.
fonte
entry.configure(validatecommand=...)
e escrevem emtest_val
vez detestVal
, mas este é um exemplo bom e simples.Use a
Tkinter.StringVar
para rastrear o valor do widget de entrada. Você pode validar o valor deStringVar
definindo umtrace
nele.Aqui está um pequeno programa de trabalho que aceita apenas flutuações válidas no widget 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()
fonte
Enquanto estudava a resposta de Bryan Oakley , algo me disse que uma solução muito mais geral poderia ser desenvolvida. O exemplo a seguir apresenta uma enumeração de modo, um dicionário de tipo e uma função de configuração para fins de validação. Veja a linha 48 para um exemplo de uso e uma demonstração de sua simplicidade.
#! /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()
fonte
A resposta de Bryan está correta, no entanto, ninguém mencionou o atributo 'invalidcommand' do widget tkinter.
Uma boa explicação está aqui: http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/entry-validation.html
Texto copiado / colado em caso de link quebrado
O widget Entry também oferece suporte a uma opção de comando inválido que especifica uma função de retorno de chamada que é chamada sempre que o comando validat retorna False. Este comando pode modificar o texto no widget usando o método .set () na variável de texto associada ao widget. A configuração desta opção funciona da mesma forma que a configuração do comando validat. Você deve usar o método .register () para envolver sua função Python; este método retorna o nome da função agrupada como uma string. Em seguida, você passará como o valor da opção de comando inválido essa string ou o primeiro elemento de uma tupla contendo códigos de substituição.
Observação: há apenas uma coisa que não consigo descobrir como fazer: se você adicionar validação a uma entrada e o usuário selecionar uma parte do texto e digitar um novo valor, não há como capturar o valor original e redefinir a entrada. Aqui está um exemplo
fonte
Esta é uma maneira simples de validar o valor de entrada, que permite ao usuário inserir apenas dígitos:
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: Este exemplo pode ser muito útil para criar um aplicativo como o calc.
fonte
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
fonte
Respondendo ao problema de orionrobert de lidar com validação simples mediante substituições de texto por meio da seleção, em vez de exclusões ou inserções separadas:
Uma substituição do texto selecionado é processada como uma exclusão seguida por uma inserção. Isso pode levar a problemas, por exemplo, quando a exclusão deve mover o cursor para a esquerda, enquanto uma substituição deve mover o cursor para a direita. Felizmente, esses dois processos são executados imediatamente um após o outro. Portanto, podemos diferenciar entre uma exclusão por si só e uma exclusão seguida diretamente por uma inserção devido a uma substituição, porque a última não altera o sinalizador de inatividade entre exclusão e inserção.
Isso é explorado usando um substitutionFlag e um
Widget.after_idle()
.after_idle()
executa a função lambda no final da fila de eventos: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
É claro que, após uma substituição, ao validar a parte de exclusão, ainda não se saberá se uma inserção virá. Felizmente no entanto, com:
.set()
,.icursor()
,.index(SEL_FIRST)
,.index(SEL_LAST)
,.index(INSERT)
, nós podemos conseguir mais comportamento desejado retrospectivamente (já que a combinação de sua nova substitutionFlag com uma inserção é um novo evento único e final.fonte