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?

3voto

Noctis Skytower Points 5137

En étudiant la réponse de Bryan Oakley, quelque chose m'a dit qu'une solution bien plus générale pouvait être développée. L'exemple suivant introduit une énumération de mode, un dictionnaire de type et une fonction de configuration à des fins de validation. Voir la ligne 48 pour un exemple d'utilisation et une démonstration de sa simplicité.

#! /usr/bin/env python3
# https://stackoverflow.com/questions/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 non reconnu')
    parameters = inspect.signature(validator).parameters
    if not set(parameters).issubset(CAST):
        raise ValueError('arguments du validateur non reconnus')
    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('Exemple de Validation')
        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()

2voto

Mohammad Omar Points 21
import tkinter
tk=tkinter.Tk()
def only_numeric_input(e):
    #cela permet de n'autoriser que les entrées numériques
    if e.isdigit():
        return True
    #cela permet d'autoriser la touche de retour pour fonctionner
    elif e=="":
        return True
    else:
        return False
#cela va placer le widget d'entrée sur la fenêtre principale
e1=tkinter.Entry(tk)
#placement du widget d'entrée sur l'écran
e1.grid(row=0,column=0)
c=tk.register(only_numeric_input)
e1.configure(validate="key",validatecommand=(c,'%P'))
tk.mainloop()
#très utile pour créer des applications comme un calculateur

1voto

Stendert Points 87

En réponse au problème d'orionrobert concernant la gestion de la validation simple lors de substitutions de texte par sélection, au lieu de suppressions ou insertions distinctes :

Une substitution de texte sélectionné est traitée comme une suppression suivie d'une insertion. Cela peut entraîner des problèmes, par exemple, lorsque la suppression doit déplacer le curseur vers la gauche, alors qu'une substitution doit déplacer le curseur vers la droite. Heureusement, ces deux processus sont exécutés immédiatement l'un après l'autre. Ainsi, nous pouvons différencier entre une suppression seule et une suppression suivie immédiatement d'une insertion en raison d'une substitution car cette dernière ne change pas le drapeau inactif entre la suppression et l'insertion.

Ceci est exploité en utilisant un substitutionFlag et un Widget.after_idle(). after_idle() exécute la fonction lambda à la fin de la file d'événements :

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')
        # attache la fonction de validation enregistrée à cette boîte d'entrée
        self.config(validate = "all", validatecommand = self.tclValidate)

    def validate(self, type, index, result, prior, indelText, currentValidationMode, reason, widgetName):

        if typeOfAction == "0":
            # définir un drapeau pouvant être vérifié par la validation d'insertion pour faire partie de la substitution
            self.substitutionFlag = True
            # stocker les données souhaitées
            self.priorBeforeDeletion = prior
            self.indexBeforeDeletion = index
            # réinitialiser le drapeau après l'inactivité
            self.after_idle(lambda: setattr(self, "substitutionFlag", False))

            # validation normale de la suppression
            pass

        elif typeOfAction == "1":

            # s'il s'agit d'une substitution, tout est décalé vers la gauche par une suppression, donc annulez cela en utilisant le prior précédent
            if self.substitutionFlag:
                # rétablir les données souhaitées telles qu'elles étaient lors de la validation de la suppression
                prior = self.priorBeforeDeletion
                index = self.indexBeforeDeletion

                # comportement additionnel facultatif (souvent non requis) lors de la substitution
                pass

            else:
                # validation normale de l'insertion
                pass

        return True

Bien sûr, après une substitution, lors de la validation de la partie suppression, on ne saura toujours pas si une insertion suivra. Heureusement cependant, avec : .set(), .icursor(), .index(SEL_FIRST), .index(SEL_LAST), .index(INSERT), nous pouvons obtenir le comportement souhaité rétrospectivement (puisque la combinaison de notre nouveau substitutionFlag avec une insertion est un nouvel événement unique et final.

1voto

Ce code peut vous aider si vous souhaitez définir à la fois des chiffres et un nombre maximal de caractères.

from tkinter import *

root = Tk()

def validate(P):
    if len(P) == 0 or len(P) <= 10 and P.isdigit():  # 10 caractères
        return True
    else:
        return False

ent = Entry(root, validate="key", validatecommand=(root.register(validate), '%P'))
ent.pack()

root.mainloop()

1voto

martineau Points 21665

Voici une version améliorée de la réponse de @Steven Rumbalski sur la validation de la valeur des widgets Entry en traçant les modifications d'une StringVar — que j'ai déjà déboguée et améliorée dans une certaine mesure en l'éditant directement.

La version ci-dessous met tout dans une StringVar sous-classe pour mieux encapsuler ce qui se passe et, plus important encore, permettre à plusieurs instances indépendantes d'exister en même temps sans interférer les unes avec les autres — un problème potentiel avec son implémentation car elle utilise des attributs de fonction au lieu d'attributs d'instance, qui sont essentiellement les mêmes choses que des variables globales et peuvent entraîner des problèmes dans un tel scénario.

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

class ValidateFloatVar(StringVar):
    """Sous-classe de StringVar qui n'autorise que des valeurs flottantes valides à y être placées."""

    def __init__(self, master=None, value=None, name=None):
        StringVar.__init__(self, master, value, name)
        self._old_value = self.get()
        self.trace('w', self._validate)

    def _validate(self, *_):
        new_value = self.get()
        try:
            new_value == '' or float(new_value)
            self._old_value = new_value
        except ValueError:
            StringVar.set(self, self._old_value)

root = Tk()
ent = Entry(root, textvariable=ValidateFloatVar(value=42.0))
ent.pack()
ent.focus_set()
ent.icursor(END)

root.mainloop()

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