Search code examples
pythonvalidationtkintercustomtkinter

How to validate customtkinter entry with pattern if invalidcommand is not implemented?


I am currently working on translating my classic tkinter app to customtkinter for GUI design improvement reasons.

While most of the original app is translated without major problems I now face a problem with entry validation. The issue is that customtkinter does not provide the invalidcommand argument for the CTkEntry. Below are some excerpts of my old logic put togeter in a reproduciple sample:

from tkinter import Tk, Entry, Button, StringVar
from re import compile


class App(Tk):
    def __init__(self):
        super().__init__()
        self.var = StringVar()
        # The entry to be validated:
        self.entry = Entry(self, textvariable=self.var, width=20, validate='focusout',
                           validatecommand=(self.register(validate_ap), '%P'),
                           invalidcommand=(self.register(self.reset_ap)))
        self.entry.pack()
        # The Button to start the processing of the entry information
        Button(self, text='process', command=self.print_if_valid).pack()

    # The function to change the entry if input is not allowed:
    def reset_ap(self):
        self.var.set('02200000')
        self.entry.after_idle(lambda: self.entry.config(validate='focusout'))

    # The function that handles the correct input further
    def print_if_valid(self):
        if self.var.get() != '02200000':
            print(self.var.get())


# The validation function:
def validate_ap(inp):
    pattern = compile(r'^(?!0+$)022\d{5}$')
    if pattern.match(inp):
        return True
    else:
        return False


if __name__ == '__main__':
    app = App()
    app.mainloop()

Now if I change to customtkinter how can I incorporate this behavior without a invalidcommand argument for the Entry? I like to put 02200000 inside the Entry because it shows the user what format is required. Also without the invalidcommand the current implemented validation does nothing.

I am thinking of implementing the invalidcommand logic into the processing function (here the print_if_valid function) but my real usecase is an editor with several inputs with this validation logic, so I do not want to change multiple entries after the user is finished with the wohle form...

Further I am wondering if there is a better way to check in print_if_valid? Should I use a boolean variable which is toggled in the validation instead of the placeholder value I set or is there even a property of the Entry widget that is changed by the validatecommand?


Solution

  • While writing this question I discovered a really simple solution by simply incorporating the logic of reset_ap into the validate_ap validation function:

    from tkinter import Tk, Entry, Button, StringVar
    from re import compile
    
    
    class App(Tk):
        def __init__(self):
            super().__init__()
            self.var = StringVar()
            # The entry to be validated:
            self.entry = Entry(self, textvariable=self.var, width=20, validate='focusout',
                               validatecommand=(self.register(self.validate_ap), '%P'))
            self.entry.pack()
            # The Button to start the processing of the entry information
            Button(self, text='process', command=self.print_if_valid).pack()
    
        # The function that handles the correct input further
        def print_if_valid(self):
            if self.var.get() != '02200000':
                print(self.var.get())
    
        # The validation function is now a classfunction and changes the entry directly instead
        # of returning True or False:
        def validate_ap(self, inp):
            pattern = compile(r'^(?!0+$)022\d{5}$')
            if not pattern.match(inp):
                self.var.set('02200000')
                self.entry.after_idle(lambda: self.entry.config(validate='focusout'))
    
    
    if __name__ == '__main__':
        app = App()
        app.mainloop()
    
    

    However, I am still thinking on how to best check if self.var is valid for the print_if_valid function...