76 votes

Interactivement validation du contenu du widget Entry dans tkinter

Quelle est la technique recommandée pour valider de manière interactive le contenu dans un widget Entry tkinter?

J'ai lu des publications sur l'utilisation de validate=True et validatecommand=command, et il semble que ces fonctionnalités sont limitées par le fait qu'elles sont effacées si la commande validatecommand met à jour la valeur du widget Entry.

Compte tenu de ce comportement, devrions-nous lier les événements KeyPress, Cut et Paste et surveiller/mettre à jour la valeur de notre widget Entry à travers ces événements? (Et d'autres événements connexes que j'aurais pu manquer?)

Ou devrions-nous oublier la validation interactive et ne valider que sur les événements FocusOut?

189voto

Bryan Oakley Points 63365

La réponse correcte est d'utiliser l'attribut validatecommand du widget. Malheureusement, cette fonctionnalité est très peu documentée dans le monde de Tkinter, bien qu'elle soit assez bien documentée dans le monde de Tk. Même si ce n'est pas bien documenté, cela vous donne tout ce dont vous avez besoin pour faire une validation sans recourir à des liaisons ou au suivi des variables, ou en modifiant le widget depuis la procédure de validation.

Le truc est de savoir que vous pouvez faire passer à Tkinter des valeurs spéciales à votre commande de validation. Ces valeurs vous fournissent toutes les informations nécessaires pour décider si les données sont valides ou non : la valeur avant l'édition, la valeur après l'édition si l'édition est valide, et plusieurs autres informations. Pour les utiliser, cependant, vous devez faire un peu de magie pour que ces informations soient transmises à votre commande de validation.

Remarque : il est important que la commande de validation renvoie soit True soit False. Tout autre chose désactivera la validation pour le widget.

Voici un exemple qui ne permet que les minuscules. Il affiche également les valeurs de toutes les valeurs spéciales à des fins illustratives. Elles ne sont pas toutes nécessaires ; vous avez rarement besoin de plus d'une ou deux.

import tkinter as tk  # python 3.x
# import Tkinter as tk # python 2.x

class Example(tk.Frame):

    def __init__(self, parent):
        tk.Frame.__init__(self, parent)

        # valid percent substitutions (from the Tk entry man page)
        # note: you only have to register the ones you need; this
        # example registers them all for illustrative purposes
        #
        # %d = Type d'action (1=insertion, 0=suppression, -1 pour les autres)
        # %i = indice de la chaîne de caractères à insérer/supprimer, ou -1
        # %P = valeur de l'entrée si l'édition est autorisée
        # %s = valeur de l'entrée avant l'édition
        # %S = la chaîne de texte en cours d'insertion ou de suppression, le cas échéant
        # %v = le type de validation actuellement défini
        # %V = le type de validation qui a déclenché le rappel
        #      (key, focusin, focusout, forced)
        # %W = le nom tk du widget

        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)

        # Interdire tout sauf les lettres minuscules
        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()

Pour plus d'informations sur ce qui se passe en interne lorsque vous appelez la méthode register, consultez Pourquoi appeler register() est-il nécessaire pour la validation de saisie tkinter ?

Pour la documentation canonique, consultez la section Validation de la page man de Tk/Tcl Entry

26voto

user1683793 Points 51

Après avoir étudié et expérimenté le code de Bryan, j'ai produit une version minimale de validation d'entrée. Le code suivant affichera une boîte d'entrée et n'acceptera que des chiffres numériques.

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()

Peut-être devrais-je ajouter que je suis encore en train d'apprendre Python et je suis ouvert à tous les commentaires/suggestions.

9voto

Steven Rumbalski Points 16838

Utilisez un Tkinter.StringVar pour suivre la valeur du widget Entry. Vous pouvez valider la valeur du StringVar en définissant une trace dessus.

Voici un petit programme fonctionnel qui n'accepte que des float valides dans le widget Entry.

try:
    from tkinter import *
except ImportError:
    from Tkinter import *  # Python 2

root = Tk()
sv = StringVar()

def validate_float(var):
    new_value = var.get()
    try:
        new_value == '' or float(new_value)
        validate_float.old_value = new_value
    except:
        var.set(validate_float.old_value)

validate_float.old_value = ''  # Définir l'attribut de fonction.

# trace veut un rappel avec des paramètres presque inutiles, en corrigeant avec lambda.
sv.trace('w', lambda nm, idx, mode, var=sv: validate_float(var))
ent = Entry(root, textvariable=sv)
ent.pack()
ent.focus_set()

root.mainloop()

7voto

orionrobert Points 91

La réponse de Bryan est correcte, cependant personne n'a mentionné l'attribut 'invalidcommand' du widget tkinter.

Une bonne explication se trouve ici: http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/entry-validation.html

Texte copié/collé en cas de lien rompu

Le widget Entry prend également en charge une option 'invalidcommand' qui spécifie une fonction de rappel appelée chaque fois que validatecommand renvoie False. Cette commande peut modifier le texte dans le widget en utilisant la méthode .set() sur la textvariable associée au widget. La configuration de cette option fonctionne de la même manière que la configuration de validatecommand. Vous devez utiliser la méthode .register() pour encapsuler votre fonction Python ; cette méthode renvoie le nom de la fonction encapsulée sous forme de chaîne. Ensuite, vous passerez en tant que valeur de l'option invalidcommand soit cette chaîne, soit le premier élément d'un tuple contenant des codes de substitution.

Remarque : Il y a une seule chose que je ne parviens pas à comprendre comment faire : si vous ajoutez une validation à une entrée et que l'utilisateur sélectionne une partie du texte et tape une nouvelle valeur, il n'y a aucun moyen de capturer la valeur d'origine et de réinitialiser l'entrée. Voici un exemple

  1. L'entrée est conçue pour n'accepter que des entiers en implémentant 'validatecommand'
  2. L'utilisateur entre 1234567
  3. L'utilisateur sélectionne '345' et appuye sur 'j'. Cela est enregistré comme deux actions : suppression de '345' et insertion de 'j'. Tkinter ignore la suppression et agit uniquement sur l'insertion de 'j'. 'validatecommand' renvoie False, et les valeurs transmises à la fonction 'invalidcommand' sont les suivantes : %d=1, %i=2, %P=12j67, %s=1267, %S=j
  4. Si le code n'implémente pas de fonction 'invalidcommand', la fonction 'validatecommand' rejettera le 'j' et le résultat sera 1267. Si le code implémente une fonction 'invalidcommand', il n'y a aucun moyen de récupérer le 1234567 d'origine.

4voto

Demian Wolf Points 1288

Définissez une fonction qui renvoie un booléen indiquant si l'entrée est valide.
Enregistrez-la en tant que rappel Tcl et transmettez le nom du rappel au widget en tant que validatecommand.

Par exemple:

import tkinter as tk

def validateur(P):
    """Valide l'entrée.

    Args:
        P (int): la valeur que le texte aurait après le changement.

    Returns:
        bool: True si l'entrée ne contient que des chiffres ou est vide, et False sinon.
    """

    return P.isdigit() or P == ""

root = tk.Tk()

entry = tk.Entry(root)
entry.configure(
    validate="key",
    validatecommand=(
        root.register(validateur),
        "%P",
    ),
)
entry.grid()

root.mainloop()

Référence.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X