Discussion:
[Tkinter-discuss] StringVar() .trace(), .trace_vdelete() use
Bob Greschke
2013-03-07 00:49:49 UTC
Permalink
Hi!

I'm messing with the .trace() function of StringVar()s to, in this case, check to see that the user has not entered more than an allowable number of characters (ckW() below does that). I'm replacing a monstrous <KeyRelease> .bind method.

I'm using the line below and it seems to work fine, but I'm not sure I understand it. lambdas really make my head hurt. Var is a global StringVar() (the line below is in a function of mine that I call to create a Label():Entry() pair of widgets, like "Barcode: ___________"). A form (Toplevel with Entry() fields on it) with an Entry() associated with a StringVar() may be destroyed and brought up multiple times over the course of using the program, so the .trace() may be applied to a given StringVar() multiple times. Is that a problem? Do these calls "stack up", or does one identical .trace() cancel the previous one? In case they do stack up I wanted to do a trace_vdelete() right before this .trace line, but I can't get it right. Do I need to do it? If so, how?

...
[Var.trace_vdelete here?]
Var.trace("w", lambda nm, idx, mode, var = Var: ckW(var, Max))
...

Thanks!

Bob
Bryan Oakley
2013-03-07 01:18:57 UTC
Permalink
If you're doing this for input validation you should consider using
the built-in validation features of the entry widget instead of using
a trace. Here's a description of how to use it:

http://stackoverflow.com/a/4140988/7432
Post by Bob Greschke
Hi!
I'm messing with the .trace() function of StringVar()s to, in this case, check to see that the user has not entered more than an allowable number of characters (ckW() below does that). I'm replacing a monstrous <KeyRelease> .bind method.
Bob Greschke
2013-03-07 03:11:39 UTC
Permalink
I saw that earlier in the day (and a couple of weeks ago too). I understand it just about as well as lambdas. :) Do all of those values get passed each time a key is pressed? Also, each Entry() field has a different max length. Where do I pass that in all of this? It would need to be an arg to vcmd in the Entry(). I couldn't see how to do that. I have an old chunk of code that allows you to pass parameters to things like the functions associated with a Button widget's 'command=' callback, but I didn't see how that would fit in here. I don't want to create a different vcmd for every possible allowed length. (there's dozens in the whole program).
Post by Bryan Oakley
If you're doing this for input validation you should consider using
the built-in validation features of the entry widget instead of using
http://stackoverflow.com/a/4140988/7432
Post by Bob Greschke
Hi!
I'm messing with the .trace() function of StringVar()s to, in this case, check to see that the user has not entered more than an allowable number of characters (ckW() below does that). I'm replacing a monstrous <KeyRelease> .bind method.
_______________________________________________
Tkinter-discuss mailing list
Tkinter-discuss at python.org
http://mail.python.org/mailman/listinfo/tkinter-discuss
Michael Lange
2013-03-07 09:01:16 UTC
Permalink
Hi,

On Wed, 6 Mar 2013 20:11:39 -0700
Post by Bob Greschke
I saw that earlier in the day (and a couple of weeks ago too). I
understand it just about as well as lambdas. :) Do all of those values
get passed each time a key is pressed?
Yes, when you register a function as validation callback, all the
registered values will be passed each time a validation occurs (which may
be every key press when validate='key', focus in/out events if
validate='focus(in/out)' or all of these if validate='all' .
Post by Bob Greschke
Also, each Entry() field has a
different max length. Where do I pass that in all of this? It would
need to be an arg to vcmd in the Entry().
This sounds like a job for a custom entry class. I wrote a
quick example to illustrate the usage of the Entry's vcmd that might be a
starting point for you:

#######################################################################
from Tkinter import *

class VEntry(Entry):
def __init__(self, master=None, maxlength=5, value='foo', **kw):
Entry.__init__(self, master, **kw)

self._maxlength = maxlength
self.insert('end', value)
vcmd = (self.register(self._vcmd), '%s', '%P')
self.configure(vcmd=vcmd, validate='all')

def _vcmd(self, old, new):
print old, new
if len(new) > self._maxlength:
return False
return True

root = Tk()
e = VEntry(root, maxlength=7, value='bar')
e.pack(padx=100, pady=100)
e.focus_set()
root.mainloop()
#######################################################################

You see, the `%s' ("old value") parameter in this example is not really
needed, I added it just to illustrate how to pass multiple arguments to
the vcmd. See http://www.tcl.tk/man/tcl8.4/TkCmd/entry.htm#M16 for a list
of all possible percent substitutions and their meanings.
You could also try if validate='key' is sufficient for your needs.

The one thing one has to be very careful to keep in mind when writing a
validation command callback is that it must always return a boolean value
in any case, otherwise Tk will be "confused" and simply and silently turn
off validation altogether. This may especially easily happen when the vcmd
callback happens to throw an error, so one has to take care that all
possible exceptions are properly caught by try...except statements.


Regards

Michael


.-.. .. ...- . .-.. --- -. --. .- -. -.. .--. .-. --- ... .--. . .-.

Compassion -- that's the one things no machine ever had. Maybe it's
the one thing that keeps men ahead of them.
-- McCoy, "The Ultimate Computer", stardate 4731.3
Bob Greschke
2013-03-07 21:56:18 UTC
Permalink
Now I understand where "bloatware" comes from. :)
This will make your eye's water:

###################################
PROG_NAME = "Headache"

from Tkinter import *
PROG_SEARCHMODS = "><=#*~_"
PROGFw = {}
PROGFw["Qty"] = 7
PROGBar = {}

Root = Tk()
ROOTNameVar = StringVar()
ROOTQtyVar = StringVar()
ROOTQtyVar.set("HI")

Clr = {"R":"#FF0000", "D":Button().cget("bg")}



####################
class vEntry(Entry):
def __init__(self, master=None, bar = "", **kw):
Entry.__init__(self, master, **kw)
VCmd = (self.register(self.VCmd), '%s', '%P')
self.configure(vcmd = VCmd, validate='key')
self.MaxLen = self.cget("width")-1
self.icursor(END)
self.VarSet = bar
def VCmd(self, Old, New):
Max = self.MaxLen
if len(New) > 0:
# If the first character of the field's text is one of the search modifiers
# then allow one more character to be entered.
if New[0] in PROG_SEARCHMODS:
Max += 1
if len(New) > Max:
Root.bell()
return False
if self.VarSet != "":
PROGBar[self.VarSet].configure(bg = Clr["R"])
eval("%sBarSVar"%self.VarSet).set(1)
return True



###########################################
def chgPROGChgBar(VarSet, State, e = None):
if State == 1:
# This may be called upon to clear the change bar of a form that doesn't have
# a change bar, so try.
try:
PROGBar[VarSet].configure(bg = Clr["R"])
eval("%sBarSVar"%VarSet).set(1)
except:
pass
elif State == 0:
try:
PROGBar[VarSet].configure(bg = Clr["D"])
eval("%sBarSVar"%VarSet).set(0)
except:
pass
# For some routines to use so they don't have to constantly check to see if
# the change bar state should be changed. The call gets made, but nothing
# happens.
elif State == -1:
return
return



##################################################
def labelEntry(Sub, LabTx, TheVar, Max, Bar = ""):
Label(Sub, text = LabTx).pack(side = LEFT)
if isinstance(Max, basestring) or isinstance(Max, str):
Max = PROGFw[Max]
LEnt = vEntry(Sub, textvariable = TheVar, width = Max+1, bar = Bar)
LEnt.pack(side = LEFT)
return LEnt



######################
def rootDoSomething():
print "Working..."
# Clean all input fields (mostly .strip()'ing)
# Verify what was entered (.upper() and/or look for missing info and/or make
# sure an integer is entrered where only an integer can be entered, etc.)
try:
int(ROOTQtyVar.get())
except:
print "Qty: Must be an integer."
return
# Save to db, or use inputs to make an SQL command for a search, etc.
ROOTNameVar.set("")
ROOTQtyVar.set("")
chgPROGChgBar("ROOT", 0)
print "Done."
return



##################
LFrm = Frame(Root)
ROOTBarSVar = IntVar()
PROGBar["ROOT"] = Frame(LFrm, height = 5)
PROGBar["ROOT"].pack(side = TOP, fill = X, expand = YES, pady = 3)
Sub = Frame(LFrm)
labelEntry(Sub, "Name:", ROOTNameVar, 20)
Sub.pack(side = TOP, pady = 20)
Sub = Frame(LFrm)
labelEntry(Sub, "Qty:", ROOTQtyVar, "Qty", "ROOT")
Sub.pack(side = TOP, padx = 20, pady = 20)
Sub = Frame(LFrm)
Button(Sub, text = "Go", command = rootDoSomething).pack(side = TOP)
Sub.pack(side = TOP)
LFrm.pack(side = TOP)

Root.mainloop()

######################################

It's micro-mini-chunks of code from a 42,000 line inventory and shipping program.

The class does what it needs to do and dovetails in with the rest of the program. So we're telling a View object, the Entry() (you caught me -- trying to learn MVC stuff in iOS/OSX), to control the contents of a Model object, the StringVar(), instead of having model things, functions via .trace(), control the contents of a Model thing, the StringVar(), which would then automagically update the View. Right? Jobs would never approve. He probably wouldn't approve either way.

I might keep it if I can't figure out what happens when you StringVar.trace() on the same variable twice. Using .trace() and just looking at the contents of the StringVar() seems way more straightforward (read non-obfuscated) to me. The only thing that didn't make sense was the lambda. :)

Thanks!
Post by Michael Lange
Hi,
On Wed, 6 Mar 2013 20:11:39 -0700
Post by Bob Greschke
I saw that earlier in the day (and a couple of weeks ago too). I
understand it just about as well as lambdas. :) Do all of those values
get passed each time a key is pressed?
Yes, when you register a function as validation callback, all the
registered values will be passed each time a validation occurs (which may
be every key press when validate='key', focus in/out events if
validate='focus(in/out)' or all of these if validate='all' .
Post by Bob Greschke
Also, each Entry() field has a
different max length. Where do I pass that in all of this? It would
need to be an arg to vcmd in the Entry().
This sounds like a job for a custom entry class. I wrote a
quick example to illustrate the usage of the Entry's vcmd that might be a
#######################################################################
from Tkinter import *
Entry.__init__(self, master, **kw)
self._maxlength = maxlength
self.insert('end', value)
vcmd = (self.register(self._vcmd), '%s', '%P')
self.configure(vcmd=vcmd, validate='all')
print old, new
return False
return True
root = Tk()
e = VEntry(root, maxlength=7, value='bar')
e.pack(padx=100, pady=100)
e.focus_set()
root.mainloop()
#######################################################################
You see, the `%s' ("old value") parameter in this example is not really
needed, I added it just to illustrate how to pass multiple arguments to
the vcmd. See http://www.tcl.tk/man/tcl8.4/TkCmd/entry.htm#M16 for a list
of all possible percent substitutions and their meanings.
You could also try if validate='key' is sufficient for your needs.
The one thing one has to be very careful to keep in mind when writing a
validation command callback is that it must always return a boolean value
in any case, otherwise Tk will be "confused" and simply and silently turn
off validation altogether. This may especially easily happen when the vcmd
callback happens to throw an error, so one has to take care that all
possible exceptions are properly caught by try...except statements.
Loading...