acdr
acdr

Reputation: 4726

Preventing a tkinter Entry from gaining focus when its associated StringVar changes

I have a tkinter Entry with a validate command, to be executed when the entry gets focus ("focusin"). This Entry is associated with a StringVar. It seems that whenever the StringVar changes value, the Entry gets focus, triggering the validation command. For example:

import Tkinter as tk

window = tk.Tk()
var = tk.StringVar()
def validate(*args):
    print("Validation took place")
    return True
entry = tk.Entry(validate="focusin", validatecommand=validate)
print("Entry created. Associating textvariable")
entry.config(textvariable=var)
print("textvariable associated. Changing value")
var.set("Text")
print("Value changed")
entry.pack()
tk.mainloop()

This code generates the following output:

Entry created. Associating textvariable
textvariable associated. Changing value
Validation took place
Value changed

Note that the validation command was executed, caused by a call to var.set. Is there a way for me to change the value of the StringVar without causing its associated Entry to gain focus? I can't temporarily disassociate the StringVar from the Entry, because when re-associating them, the Entry also gains focus.

Upvotes: 2

Views: 930

Answers (2)

Bryan Oakley
Bryan Oakley

Reputation: 386342

I think your observation is incorrect: the focus does not change when you set the value of the StringVar. This is likely just an edge case that only happens when your application first starts up. The widget probably gets focus when it is initially created. Once the GUI is up and running, setting the variable won't change the focus.

The official tk documentation discourages the use of both validation and the use of a variable. From the documentation:

In general, the textVariable and validateCommand can be dangerous to mix. Any problems have been overcome so that using the validateCommand will not interfere with the traditional behavior of the entry widget.

There's no reason to use a StringVar in this case. My recommendation is to remove it. The use of a StringVar is generally only useful if you use the same variable for more than one widget, or you're putting a trace on the variable. You are doing neither so just stop using the StringVar. It adds an extra object to manage without providing much extra benefit.

Upvotes: 2

Kevin
Kevin

Reputation: 76254

One possible workaround is to disable the validation event before setting the stringvar, and re-enable it afterwards.

import Tkinter as tk

window = tk.Tk()
var = tk.StringVar()
def validate(*args):
    print("Validation took place")
    return True
entry = tk.Entry(validate="focusin", validatecommand=validate)
print("Entry created. Associating textvariable")
entry.config(textvariable=var)
print("textvariable associated. Changing value")
entry.config(validate="none")
var.set("Text")
entry.config(validate="focusin")
print("Value changed")
entry.pack()
tk.mainloop()

If you expect to do this a lot, you could even write a context manager so you don't accidentally forget to re-enable validation after you're finished altering the text.

import Tkinter as tk
from contextlib import contextmanager

@contextmanager
def temp_change(widget, **kargs):
    old_values = {key:widget.cget(key) for key in kargs}
    widget.config(**kargs)
    try:
        yield #wait for `with` block to finish executing...
    finally:
        widget.config(**old_values)


window = tk.Tk()
var = tk.StringVar()
def validate(*args):
    print("Validation took place")
    return True
entry = tk.Entry(validate="focusin", validatecommand=validate)
print("Entry created. Associating textvariable")
entry.config(textvariable=var)
print("textvariable associated. Changing value")

with temp_change(entry, validate="none"):
    var.set("Text")

print("Value changed")
entry.pack()
tk.mainloop()

Upvotes: 1

Related Questions