# TkEntry validation?

**URL:** <https://www.ruby-forum.com/t/tkentry-validation/75641>\
**Category:** Ruby\
**Created:** [November 11, 2006, 8:22pm UTC](https://www.ruby-forum.com/t/tkentry-validation/75641 "2006-11-11T20:22:23Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![Matt\_Maycock](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/m/e9a140/32.png) [@Matt\_Maycock](https://www.ruby-forum.com/u/Matt_Maycock)\
**Post date:** [November 11, 2006, 8:22pm UTC](https://www.ruby-forum.com/t/tkentry-validation/75641/1 "2006-11-11T20:22:23Z")

</div>

Hi.

I wrote the following to make some of my input validation from  
TkEntry’s a bit easier. I thought some others might be interested  
(still has some parts that could use work)

# 

# Usage:

# num = TkEntry.new(…)

# num.filter.integer.nosign # 123, 625, 86434, etc

# 

# year = TkEntry.new(…)

# year.filter.integer.nosign.left\_digit(4) # any number up to four

digits - no sign

# 

# float = TkEntry.new(…)

# float.filter.real.right\_digit(3) # any real number with at most 3

places past the decimal

# 

# month = TkEntry.new(…)

# month.filter.month.letters(3) # A month - can be either 1-12 or

names (up to 3 letters)

# 

require ‘tk’

class TkEntry  
def acceptable\_keys  
# This part is probably what needs more info in it…  
%w{Tab ISO\_Left\_Tab Left Right Shift Shift\_L Shift\_R}  
end

def removal\_keys  
%w{BackSpace Delete}  
end

def month\_names  
%w{january february march april may june july august september  
october november december}  
end

def generic\_remove(key)  
s = self.value.dup  
if self.selection\_present then  
s[(self.index(‘sel.first’))…(self.index(‘sel.last’))] = “”  
elsif key == “BackSpace” && self.cursor \> 0 then  
s[self.cursor - 1, 1] = “”  
elsif key == “Delete” && self.cursor \< s.length  
s[self.cursor, 1] = “”  
end  
s  
end

def generic\_insert(key)  
return generic\_remove(key) if removal\_keys.include?(key)  
s = self.value.dup  
if self.selection\_present then  
s[(self.index(‘sel.first’))…(self.index(‘sel.last’))] = key  
else  
s.insert(self.cursor, key)  
end  
s  
end

def generic\_number\_insert(key)  
return generic\_remove(key) if removal\_keys.include?(key)  
s = self.value.dup  
if key =~ /^\d$/ then  
if self.selection\_present then  
s[(self.index(‘sel.first’))…(self.index(‘sel.last’))] = key  
else  
s.insert(self.cursor, key)  
end  
elsif %w{minus plus}.include?(key) then  
v = {‘minus’ =\> ‘-’, ‘plus’ =\> ‘+’}[key]  
if self.selection\_present then  
s[(self.index(‘sel.first’))…(self.index(‘sel.last’))] = v  
else  
s.insert(self.cursor, v)  
end  
elsif key == ‘period’  
if self.selection\_present then  
s[(self.index(‘sel.first’))…(self.index(‘sel.last’))] = ‘.’  
else  
s.insert(self.cursor, ‘.’)  
end  
else  
Tk.callback\_break # Not something to be used with numbers?  
end  
s  
end

def filter  
me, f = self, Object.new

```
f.instance_eval {
  @filter_target = me;
}

def f.method_missing(sym, *args, &block)
  if [:positive, :negative, :nosign, :integer, :real, :left_digit,

```

:right\_digit, :range, :month, :letters, :alphanumeric].include?(sym)  
then  
@filter\_target.key\_filter([sym, \*args])  
self  
else  
super(sym, \*args, &block)  
end  
end

```
f

```

end

def key\_filter\_proc(type, _args)  
case type  
when :positive  
Proc.new {|key|  
next if self.acceptable\_keys.include?(key)  
s = self.generic\_number\_insert(key)  
Tk.callback\_break if s =~ /^.++/ || s =~ /+._+/  
Tk.callback\_break if s =~ /-/  
Tk.callback\_break if s =~ /^+?0/  
}  
when :negative  
Proc.new {|key|  
next if self.acceptable\_keys.include?(key)  
s = self.generic\_number\_insert(key)  
Tk.callback\_break if s =~ /^.±/ || s =~ /-._-/  
Tk.callback\_break if s =~ /+/  
Tk.callback\_break unless s.length == 0 || s =~ /^-/  
Tk.callback\_break if s =~ /^-0/  
}  
when :nosign  
Proc.new {|key|  
next if self.acceptable\_keys.include?(key)  
Tk.callback\_break if %w{plus minus}.include?(key)  
}  
when :integer  
Proc.new {|key|  
next if self.acceptable\_keys.include?(key)  
s = self.generic\_number\_insert(key)  
Tk.callback\_break if s =~ /^(-|+)?0\d/  
Tk.callback\_break if s =~ /^.+(-|+)/  
Tk.callback\_break if s =~ /./  
}  
when :real  
Proc.new {|key|  
next if self.acceptable\_keys.include?(key)  
s = self.generic\_number\_insert(key)  
Tk.callback\_break if s =~ /^(-|+)?0\d/  
Tk.callback\_break if s =~ /^.+(-|+)/  
Tk.callback\_break if s.scan(/./).length \> 1  
}  
when :left\_digit  
raise(ArgumentError, “Need a number of digits to allow on the  
left of the decimal”) if args.empty?  
n = args[0].to\_i  
Proc.new {|key|  
next if acceptable\_keys.include?(key)  
s = self.generic\_number\_insert(key)  
Tk.callback\_break if s =~ /^(-|+)?(\d_)/ && $2.length \> n  
}  
when :right\_digit  
raise(ArgumentError, “Need a number of digits to allow on the  
left of the decimal”) if args.empty?  
n = args[0].to\_i  
Proc.new {|key|  
next if acceptable\_keys.include?(key)  
s = self.generic\_number\_insert(key)  
Tk.callback\_break if s =~ /.(\d+)$/ && $1.length \> n  
}  
when :range  
raise(ArgumentError, “Need a range to test against”) if  
args.length \< 1  
r = (args[0].to\_i)…(args[1].to\_i)  
Proc.new {|key|  
next if acceptable\_keys.include?(key)  
s = self.generic\_number\_insert(key)  
Tk.callback\_break if s.to\_f \< r.begin || s.to\_f \> r.end  
}  
when :month  
Proc.new {|key|  
next if acceptable\_keys.include?(key)  
Tk.callback\_break unless key =~ /^(\d|\w)$/  
s = self.generic\_insert(key)  
Tk.callback\_break if s =~ /\d/ && s =~ /[A-Za-z]/

```
      if s =~ /^\d+$/ then
        Tk.callback_break unless (1..12).include?(s.to_i)
      else
        match = Regexp.compile("^#{s}", true)
        Tk.callback_break unless self.month_names.any? {|i| i =~ 

```

match}  
end  
}  
when :letters  
if args.empty? then  
Proc.new {|key|  
next if acceptable\_keys.include?(key)  
Tk.callback\_break unless key =~ /^[A-Za-z]$/  
}  
else  
n = args[0].to\_i  
Proc.new {|key|  
next if acceptable\_keys.include?(key)  
Tk.callback\_break unless key =~ /^[A-Za-z]$/ &&  
generic\_insert(key).length \<= n  
}  
end  
when :alphanumeric  
if args.empty? then  
Proc.new {|key|  
next if acceptable\_keys.include?(key)  
Tk.callback\_break unless key =~ /^\w$/  
}  
else  
Proc.new {|key|  
next if acceptable\_keys.include?(key)  
next if key =~ /^\w$/  
next if args.include?(key)  
Tk.callback\_break  
}  
end  
else  
raise(ArgumentError, “Could not determine type of filter.”)  
end  
end

def key\_filter(\*constraints)  
constraints.each {|c|  
self.bind\_append(‘Key’, case c  
# Allow only positive numbers (but only checks that we have  
only plus sign and only one in the front  
when Symbol then  
key\_filter\_proc©  
when Array # Special Cases  
raise(ArgumentError, “Array based filters cannot be empty.”)  
if c.empty?  
raise(ArgumentError, “Array based filters must start with a  
symbol.”) unless Symbol === c[0]  
key\_filter\_proc(\*c)  
end, ‘%K’)  
}  
end  
end

def Tk.datetime\_filter  
[fyr, fdy, fhr, fmi, fsc].each {|i|  
i.key\_filter(:integer, :nosign)  
}  
fyr.key\_filter([:left\_digit, 4])  
fmo.key\_filter(:month, [:letters, 3])  
fdy.key\_filter([:range, 1, 31])  
fhr.key\_filter([:range, 0, 23])  
[fmi, fsc].each {|i|  
i.key\_filter([:range, 0, 59])  
}  
nil  
end

---

<div class="post-metadata">

**Author:** ![Matt\_Maycock](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/m/e9a140/32.png) [@Matt\_Maycock](https://www.ruby-forum.com/u/Matt_Maycock)\
**Post date:** [November 11, 2006, 8:28pm UTC](https://www.ruby-forum.com/t/tkentry-validation/75641/2 "2006-11-11T20:28:24Z")

</div>

actually, update ~ line 154 from

```
Tk.callback_break unless key =~ /^(\d|\w)$/

```

to  
Tk.callback\_break unless removal\_keys.include?(key) || key =~  
/^(\d|\w)$/

(Sorry, I just added the functionality of dealing with backspace /  
delete correctly, and forgot that part).

~Matthew Maycock!

---

<div class="post-metadata">

**Author:** ![Matt\_Maycock](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/m/e9a140/32.png) [@Matt\_Maycock](https://www.ruby-forum.com/u/Matt_Maycock)\
**Post date:** [November 11, 2006, 8:36pm UTC](https://www.ruby-forum.com/t/tkentry-validation/75641/3 "2006-11-11T20:36:26Z")

</div>

and…

I also forgot to add that part back into the letters / alphanumerics  
(before this, it just passively ignored backspace/delete as an  
`acceptable\_key’ instead of testing the new result, sorry for posting  
buggyness).

`semi-final’ version of the code

require ‘tk’

class TkEntry  
def acceptable\_keys  
%w{Tab ISO\_Left\_Tab Left Right Shift Shift\_L Shift\_R}  
end

def removal\_keys  
%w{BackSpace Delete}  
end

def month\_names  
%w{january february march april may june july august september  
october november december}  
end

def generic\_remove(key)  
s = self.value.dup  
if self.selection\_present then  
s[(self.index(‘sel.first’))…(self.index(‘sel.last’))] = “”  
elsif key == “BackSpace” && self.cursor \> 0 then  
s[self.cursor - 1, 1] = “”  
elsif key == “Delete” && self.cursor \< s.length  
s[self.cursor, 1] = “”  
end  
s  
end

def generic\_insert(key)  
return generic\_remove(key) if removal\_keys.include?(key)  
s = self.value.dup  
if self.selection\_present then  
s[(self.index(‘sel.first’))…(self.index(‘sel.last’))] = key  
else  
s.insert(self.cursor, key)  
end  
s  
end

def generic\_number\_insert(key)  
return generic\_remove(key) if removal\_keys.include?(key)  
s = self.value.dup  
if key =~ /^\d$/ then  
if self.selection\_present then  
s[(self.index(‘sel.first’))…(self.index(‘sel.last’))] = key  
else  
s.insert(self.cursor, key)  
end  
elsif %w{minus plus}.include?(key) then  
v = {‘minus’ =\> ‘-’, ‘plus’ =\> ‘+’}[key]  
if self.selection\_present then  
s[(self.index(‘sel.first’))…(self.index(‘sel.last’))] = v  
else  
s.insert(self.cursor, v)  
end  
elsif key == ‘period’  
if self.selection\_present then  
s[(self.index(‘sel.first’))…(self.index(‘sel.last’))] = ‘.’  
else  
s.insert(self.cursor, ‘.’)  
end  
else  
Tk.callback\_break # Not something to be used with numbers?  
end  
s  
end

def filter  
me, f = self, Object.new

```
f.instance_eval {
  @filter_target = me;
}

def f.method_missing(sym, *args, &block)
  if [:positive, :negative, :nosign, :integer, :real, :left_digit,

```

:right\_digit, :range, :month, :letters, :alphanumeric].include?(sym)  
then  
@filter\_target.key\_filter([sym, \*args])  
self  
else  
super(sym, \*args, &block)  
end  
end

```
f

```

end

def key\_filter\_proc(type, _args)  
case type  
when :positive  
Proc.new {|key|  
next if self.acceptable\_keys.include?(key)  
s = self.generic\_number\_insert(key)  
Tk.callback\_break if s =~ /^.++/ || s =~ /+._+/  
Tk.callback\_break if s =~ /-/  
Tk.callback\_break if s =~ /^+?0/  
}  
when :negative  
Proc.new {|key|  
next if self.acceptable\_keys.include?(key)  
s = self.generic\_number\_insert(key)  
Tk.callback\_break if s =~ /^.±/ || s =~ /-._-/  
Tk.callback\_break if s =~ /+/  
Tk.callback\_break unless s.length == 0 || s =~ /^-/  
Tk.callback\_break if s =~ /^-0/  
}  
when :nosign  
Proc.new {|key|  
next if self.acceptable\_keys.include?(key)  
Tk.callback\_break if %w{plus minus}.include?(key)  
}  
when :integer  
Proc.new {|key|  
next if self.acceptable\_keys.include?(key)  
s = self.generic\_number\_insert(key)  
Tk.callback\_break if s =~ /^(-|+)?0\d/  
Tk.callback\_break if s =~ /^.+(-|+)/  
Tk.callback\_break if s =~ /./  
}  
when :real  
Proc.new {|key|  
next if self.acceptable\_keys.include?(key)  
s = self.generic\_number\_insert(key)  
Tk.callback\_break if s =~ /^(-|+)?0\d/  
Tk.callback\_break if s =~ /^.+(-|+)/  
Tk.callback\_break if s.scan(/./).length \> 1  
}  
when :left\_digit  
raise(ArgumentError, “Need a number of digits to allow on the  
left of the decimal”) if args.empty?  
n = args[0].to\_i  
Proc.new {|key|  
next if acceptable\_keys.include?(key)  
s = self.generic\_number\_insert(key)  
Tk.callback\_break if s =~ /^(-|+)?(\d_)/ && $2.length \> n  
}  
when :right\_digit  
raise(ArgumentError, “Need a number of digits to allow on the  
left of the decimal”) if args.empty?  
n = args[0].to\_i  
Proc.new {|key|  
next if acceptable\_keys.include?(key)  
s = self.generic\_number\_insert(key)  
Tk.callback\_break if s =~ /.(\d+)$/ && $1.length \> n  
}  
when :range  
raise(ArgumentError, “Need a range to test against”) if  
args.length \< 1  
r = (args[0].to\_i)…(args[1].to\_i)  
Proc.new {|key|  
next if acceptable\_keys.include?(key)  
s = self.generic\_number\_insert(key)  
Tk.callback\_break if s.to\_f \< r.begin || s.to\_f \> r.end  
}  
when :month  
Proc.new {|key|  
next if acceptable\_keys.include?(key)  
Tk.callback\_break unless removal\_keys.include?(key) || key  
=~ /^(\d|\w)$/  
s = self.generic\_insert(key)  
Tk.callback\_break if s =~ /\d/ && s =~ /[A-Za-z]/

```
      if s =~ /^\d+$/ then
        Tk.callback_break unless (1..12).include?(s.to_i)
      else
        match = Regexp.compile("^#{s}", true)
        Tk.callback_break unless self.month_names.any? {|i| i =~ 

```

match}  
end  
}  
when :letters  
if args.empty? then  
Proc.new {|key|  
next if acceptable\_keys.include?(key)  
next if removal\_keys.include?(key)  
Tk.callback\_break unless key =~ /^[A-Za-z]$/  
}  
else  
n = args[0].to\_i  
Proc.new {|key|  
next if acceptable\_keys.include?(key)  
next if removal\_keys.include?(key)  
Tk.callback\_break unless key =~ /^[A-Za-z]$/ &&  
generic\_insert(key).length \<= n  
}  
end  
when :alphanumeric  
if args.empty? then  
Proc.new {|key|  
next if acceptable\_keys.include?(key) ||  
removal\_keys.include?(key)  
Tk.callback\_break unless key =~ /^\w$/  
}  
else  
Proc.new {|key|  
next if acceptable\_keys.include?(key)  
next if key =~ /^\w$/  
next if args.include?(key)  
Tk.callback\_break  
}  
end  
else  
raise(ArgumentError, “Could not determine type of filter.”)  
end  
end

def key\_filter(\*constraints)  
constraints.each {|c|  
self.bind\_append(‘Key’, case c  
# Allow only positive numbers (but only checks that we have  
only plus sign and only one in the front  
when Symbol then  
key\_filter\_proc©  
when Array # Special Cases  
raise(ArgumentError, “Array based filters cannot be empty.”)  
if c.empty?  
raise(ArgumentError, “Array based filters must start with a  
symbol.”) unless Symbol === c[0]  
key\_filter\_proc(\*c)  
end, ‘%K’)  
}  
end  
end

def Tk.datetime\_filter  
[fyr, fdy, fhr, fmi, fsc].each {|i|  
i.key\_filter(:integer, :nosign)  
}  
fyr.key\_filter([:left\_digit, 4])  
fmo.key\_filter(:month, [:letters, 3])  
fdy.key\_filter([:range, 1, 31])  
fhr.key\_filter([:range, 0, 23])  
[fmi, fsc].each {|i|  
i.key\_filter([:range, 0, 59])  
}  
nil  
end

# Note - unless other people post, this is going to look really

pathetic with me having 3 posts in a row, 2 showing how trigger happy  
I am to post… 🙂

---

<div class="post-metadata">

**Author:** ![Matt\_Maycock](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/m/e9a140/32.png) [@Matt\_Maycock](https://www.ruby-forum.com/u/Matt_Maycock)\
**Post date:** [November 13, 2006, 3:21am UTC](https://www.ruby-forum.com/t/tkentry-validation/75641/4 "2006-11-13T03:21:47Z")

</div>

> Why don’t you use ‘validatecommand’ (or ‘vcmd’) option and on?  
> Of course I know that API of validation of entry widgets is very  
> basic. But I think that you can make some part of your code simple.
> 
> If you don’t see the option, please read ‘VALIDATION’ part of the  
> Tcl/Tk’s manual of entry widgets. You’ll be able to see examples of  
> its usage in “ext/tk/sample/demos-en/entry3.rb” on Ruby’s source tree.

When I originally went looking into validate, I found it hard to find  
documentation - but that example code was very helpful. Thank you.

~Matthew Maycock!

---

<div class="post-metadata">

**Author:** ![Matt\_Maycock](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/m/e9a140/32.png) [@Matt\_Maycock](https://www.ruby-forum.com/u/Matt_Maycock)\
**Post date:** [November 12, 2006, 2:31am UTC](https://www.ruby-forum.com/t/tkentry-validation/75641/5 "2006-11-12T02:31:03Z")

</div>

From: “Matt Maycock” [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid)  
Subject: TkEntry validation?  
Date: Sun, 12 Nov 2006 04:20:49 +0900  
Message-ID:  
[removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid)

> I wrote the following to make some of my input validation from  
> TkEntry’s a bit easier. I thought some others might be interested  
> (still has some parts that could use work)

Why don’t you use ‘validatecommand’ (or ‘vcmd’) option and on?  
Of course I know that API of validation of entry widgets is very  
basic. But I think that you can make some part of your code simple.

If you don’t see the option, please read ‘VALIDATION’ part of the  
Tcl/Tk’s manual of entry widgets. You’ll be able to see examples of  
its usage in “ext/tk/sample/demos-en/entry3.rb” on Ruby’s source tree.
