Salvatore
Salvatore

Reputation: 93

Invoke tkinter trace callback AFTER a certain variable delay has been achieved

I have a tkinter application that searches through a list of about 100000 wordlist when user types into the Entry widget (using trace with write callback to capture change in Entry variable).

I want to implement sort of a delay in order to NOT invoke the trace callback (to search the entire 100k wordlist) at EVERY keystroke (as the user might still be typing and it can become rather jerky/slow to invoke the callback function for each keystroke), rather I want to employ some sort of a min time to wait for additional input/keystroke AND/OR a max time since the first key was pressed BEFORE invoking the trace callback function.

I tried implementing a sleep but that is just a blocking call and does not achieve the desired affect. Here is some sample code where entering the string 'password' will invoke the callback (since this is literally just checking against the string 'password', it is super fast, yet in my app I loop over 100k word list for each keystroke which becomes slow). Thank You!

import tkinter as tk
from tkinter import ttk

class App(tk.Tk):
    SUCCESS = 'Success.TLabel'

    def __init__(self):
        super().__init__()
        self.title('Enter <password>')
        self.geometry("200x120")
        self.passwordVariable = tk.StringVar()
        self.passwordVariable.trace('w', self.validate)
        password_entry = ttk.Entry(
            self, textvariable=self.passwordVariable) #, show='*'
        password_entry.grid(column=0, row=1)
        password_entry.focus()
        self.message_label = ttk.Label(self)
        self.message_label.grid(column=0, row=0)

    def set_message(self, message, type=None):
        self.message_label['text'] = message
        if type:
            self.message_label['style'] = type

    def validate(self, *args):
        confirm_password = self.passwordVariable.get()
        if confirm_password == "password":
            self.set_message(
                "Success: The new password looks good!", self.SUCCESS)
            return
        if confirm_password.startswith("pas"):
            self.set_message('Warning: Keep entering the password')

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

Upvotes: 0

Views: 330

Answers (1)

Delrius Euphoria
Delrius Euphoria

Reputation: 15098

I tried to understand what your current code does so I can implement the function here, but I've had no luck. Hopefully you being the author can implement this example onto your code.

The idea here is to schedule a callback to run after x seconds and if it is already scheduled, then cancel it. Sort of like a timer, if you think about it.

from tkinter import *

root = Tk()
SECONDS_TO_WAIT = 1
rep = None

def typing(*args):
    global rep
    if rep is None:
        writing.config(text='Typing...')
    else:
        root.after_cancel(rep) # if already scheduled, then cancel it
    
    rep = root.after(SECONDS_TO_WAIT*1000, caller) 

def caller(*args):
    global rep
    writing.config(text='Not typing')
    rep = None # Set it to None if `caller` GETS executed

var = StringVar()
entry = Entry(root,textvariable=var)
entry.pack(padx=10,pady=10)
entry.focus_force()

writing = Label(root,text='Not typing')
writing.pack()
var.trace('w',typing)

root.mainloop()

This will execute typing each time the entry widget is edited/written to. And according to the conditions inside the function, caller gets executed.

Upvotes: 1

Related Questions