focus .editFr1.edit1 ;# put the cursor and focus to the first edit box
focus -force . ;# force focus back to the main window of the GUI
bind .editFr1.edit1 <Return> {set retKey 0; validate_sys_part(data)};
-- here I need to pass the data entered in the field. How do I
accomplish this?
Thanks
krithiga
You seem to be doing lots of unnecessary stuff in your example
focus / binding should not normally be necessary for simple gui
stuff, the window manager and Tk will take care of it for you.
The first way is the entry get command
e.g.
entry .edit
pack .edit
.edit get
Another way is to use a text variable:
(this only trick is that the text variable MUST
be a variable with global scope, i.e. a global)
e.g.
entry .edit1 -textvariable ::edit1_value
pack .edit1
Now whatever you enter in the .edit1 is automatically stored into
::edit1_value
If you want to perform validation on the user entered data,
(which your example seems to indicate).
then the entry provides a -vcmd option to do this for you.
See http://wiki.tcl.tk/768 for more information and examples
regards
Paul
Using a global variable:
entry .editFr1.edit1 -textvariable whatever
bind .editFr1.edit1 <Return> validate_sys_part
....
proc validate_sys_part {} {
global whatever
if {$whatever == ...} {
....
}
}
Avoiding the use of global variables:
entry .e
bind .e <Return> [list validate_sys_part .e]
....
proc validate_sys_part {widget} {
set whatever [$widget get]
if {$whatever == ...} {
....
}
}
Start with that, and let us know how it works for you. There are other
ways to accomplish similar goals but those are the two most
straight-forward.
I have no idea what you were trying to accomplish with retKey so I left
that out of the examples.
proc validate_sys_part {data} {
global dat sys_part a field_name PRODMOD1 PRODMOD2
puts $dat
set RPC [read_product_config]
validate_entry (data,sys_part)
if {[validate_entry (data,sys_part)] == 1 } {
return -code error
}
set modelNum [string range data 5 end]
set model_name $PRODMOD1($modelNum)
set prod_type $PRODMOD2($modelNum)
set prod_status $PRODMOD($modelNum)
regexp (.*)- $model_name match MN1
set prod_code $PRODCODE($MN1)
if {[$PRODMOD($modelNum) ! = modelNum]} {
#beep
tk_messageBox -title "Invalid System part Number:model #nnnn not
found"
return -code -error
}
set CurrentItem [list sys_part modelNum model_name prod_type
prod_status prod_code]
return 0
}
In this proc I call another proc that this data I entered stored in
variable is passed to proc validate_entry where I pass the data and
sys_part. Though I declare these as global variables data is seen in
the proc validate_sys_part but not in validate_entry.
the proc validate entry is proc validate_entry {data,field_name} {
}
In this procedure I pass the field_name as sys_part from validate_entry
proc but it gives an error "cannot read variable name field_name".
Please let me know how we can do this
Thanks
krithiga
This code is passing the literal string "(dat)" (without the quotes) to
the procedure validate_sys_part.
Tcl and Tk don't use parenthesis for function calls; looking through
your code it appears you think it does. Did you get that impression from
some documentation or a book? If so, let us know and we can help you
contact the author to correct that information.
If you want to call a procedure with the value stored in a variable
named "dat", you must pass it like so:
validate_sys_part $dat
Back to your original question. You apparently have a proc named
validate_sys_part that requires a value to be passed in to it. You also
have an entry field that the user can use to specify that value. To hook
the two together you'll need an intermediate procedure since you can't
change the API of validate_sys_part. No harm; procs are very cheap.
entry .editFr1.edit1 -textvariable dat
bind .editFr1.edit1 <Return> validate_input
proc validate_input {} {
global dat
validate_sys_part $dat
}
There are more general ways to solve the same problem, but lets stick
with that until you get it working for this particular case.
>
> proc validate_sys_part {data} {
> global dat sys_part a field_name PRODMOD1 PRODMOD2
>
> puts $dat
> set RPC [read_product_config]
> validate_entry (data,sys_part)
> if {[validate_entry (data,sys_part)] == 1 } {
I'm not sure why you are calling validate_entry more than once. As
mentioned before, this passes the literal string "(data,sys_part)"
(without the quotes) to the procedure validate_entry. You probably need
something like:
if {[validate_entry $data $sys_part] == 1} {...}
> return -code error
>
> }
> set modelNum [string range data 5 end]
Again, that needs to be $data
> set model_name $PRODMOD1($modelNum)
> set prod_type $PRODMOD2($modelNum)
> set prod_status $PRODMOD($modelNum)
> regexp (.*)- $model_name match MN1
It's generally a good idea to get in the habit of putting your
expressions in curly braces:
regexp {(.*)-} $model_name match MN1
> set prod_code $PRODCODE($MN1)
>
> if {[$PRODMOD($modelNum) ! = modelNum]}
The extra []'s in the above statment causes Tcl to look for a procedure
name that is stored in the variable $PRODMOD($modelNum). That is almost
certainly not what you want. Also, because modelNum has no preceeding $,
you are comparing to the literal string "modelNum" (again, without the
quotes).
Probably what you meant was:
if {$PRODMOD($modelNum) != $modelNum} {...}
{
> #beep
> tk_messageBox -title "Invalid System part Number:model #nnnn not
> found"
> return -code -error
> }
>
>
> set CurrentItem [list sys_part modelNum model_name prod_type
> prod_status prod_code]
The above creates a list with the literal strings "sys_part",
"modelNum", etc. You need dollar signs if you want the values of the
variables.
You seem to think that Tcl is some other language. In Tcl, function
calls do not use parenthesis, and to dereference a variable name you
need a preceeding dollar sign (there are other ways to dereference a
variable but I don't want to confuse matters even more for you...)
validate_entry($data,$sys_part) -
proc validate_entry {data2,field_name} {
global data2 dat field_name lookup DX2 sys_part data
puts "I am in validate_entry"
puts "Data is $data2"
puts data2
puts $field_name
}
In this proc it gives an Error cannot read data2 . How do I pass this
value $data from validate_sys_part to validate_entry. I used the $
sign.
So may be as you suggested I do validate_entry before I call
validate_sys_part. I can try this. The reason I was calling
validate_entry twice is to check if it returned an error. The first
time it is called to perform the procedure and then checked if it
returned an error.
This data is not passed to the proc validate_entry.
Thanks
krithiga
That is not how you call procedures in Tcl. Tcl doesn't use parenthesis,
as I described in my last message. Nor does Tcl use commas. It needs to
be this:
validate_entry $data $sys_part
>
>
> proc validate_entry {data2,field_name} {
You cannot (*) use commas in a proc definition. It must be:
proc validate_entry {data2 field_name} {...}
You should re-read the Tcl man page [1] where it describes how tcl
breaks up commands into words, and the proc man page [2] where it talks
about formal arguments.
(*) You can use commas in a proc definition, it's just that they don't
work the way they do in many other languages. While learning Tcl
fundamentals it's easier to simply learn you can't use commas.
1: http://www.tcl.tk/man/tcl8.4/TclCmd/Tcl.htm
2: http://www.tcl.tk/man/tcl8.4/TclCmd/proc.htm
This is in correct syntax, do:
bind .editFr1.edit1 <Return> [list validate_sys_part dat]
>
> proc validate_sys_part {data} {
> global dat sys_part a field_name PRODMOD1 PRODMOD2
>
> puts $dat
> set RPC [read_product_config]
> validate_entry (data,sys_part)
This is not how we call procedures in Tcl, we do:
validate_entry data sys_part
or if you want the conents (which I think is what you want):
validate_entry $data $sys_part
> if {[validate_entry (data,sys_part)] == 1 } {
> return -code error
>
> }
> set modelNum [string range data 5 end]
> set model_name $PRODMOD1($modelNum)
> set prod_type $PRODMOD2($modelNum)
> set prod_status $PRODMOD($modelNum)
> regexp (.*)- $model_name match MN1
> set prod_code $PRODCODE($MN1)
>
> if {[$PRODMOD($modelNum) ! = modelNum]} {
> #beep
> tk_messageBox -title "Invalid System part Number:model #nnnn not
> found"
> return -code -error
> }
>
>
> set CurrentItem [list sys_part modelNum model_name prod_type
> prod_status prod_code]
> return 0
> }
>
> In this proc I call another proc that this data I entered stored in
> variable is passed to proc validate_entry where I pass the data and
> sys_part. Though I declare these as global variables data is seen in
> the proc validate_sys_part but not in validate_entry.
>
> the proc validate entry is proc validate_entry {data,field_name} {
This is not how you declare a procedure with two arguements in Tcl, we do:
proc validate_entry {data field_name} {
>
> }
>
> In this procedure I pass the field_name as sys_part from validate_entry
> proc but it gives an error "cannot read variable name field_name".
> Please let me know how we can do this
You need to learn Tcl syntax a little better and stop using C/Java syntax
when writing Tcl.
set CurrentItem [list sys_part $modelNum $model_name $prod_type
$prod_status $prod_code]
I need each of these items in outside the procedure. I have defined
CurrentItem to be global but cannot get its items as each of them is a
variable . I have defined these as global. Do you have any suggestions?
Thanks
krithiga
It's not a syntax error, which is why you didn't get a syntax error :-)
A string like foo,bar appears to Tcl as a single "word" of 7 bytes
rather than two "words" separated by a comma. As long as what you're
calling expects only a single word you won't get a runtime error.
So, calling a procedure with "foo,bar" isn't an error, it just doesn't
do what you naively expect.
> I am reading the man pages and it seems straightforward to
> set arrays. But when I use them I am ruuning into issues. I also have
> to set an array with entries
When you say "I also have to set an array with entries" do you mean "I
have a homework assignment and this is what I'm supposed to do"? It is
beginnign to sound suspiciously like a homework assignment.
While we're happy to help, we tend not to do people's homework for them.
This forum is for helping solve technical issues, not do your work for you.
If you're having issues with arrays, show us the exact code and the
exact error message and we can tell you what you're doing wrong.
>
> set CurrentItem [list sys_part $modelNum $model_name $prod_type
> $prod_status $prod_code]
>
> I need each of these items in outside the procedure. I have defined
> CurrentItem to be global but cannot get its items as each of them is a
> variable .
What you say doesn't make much sense. Once you set it, and define it as
global, it will be available wherever else you declare it as global.
CurrentItem will contain the data that is in the variables at the time
you set CurrentItem. Once you set CurrentItem, if you change a variable
it won't change CurrentItem.
For example:
set modelNum 12
set model_name "Nimbus 2000"
set prod_type "broom"
set prod_status "imaginary"
set prod_code "xyzzy"
set CurrentItem [list sys_part $modelNum $model_name $prod_type \
$prod_status $prod_code]
After doing the above, CurrentItem will be {sys_part 12 {Nimbus 2000}
broom imaginary xyzzy}. If it is declared global, it can be accessed
anywhere.
proc valid_sys_part {data} {
.. code..here
set CurrentItem [list sys_part $modelNum $model_name $prod_type
$prod_status
$prod_code]
set MODNAME [lindex $CurrentItem 2]
set MODELNUM [lindex $CurrentItem 1]
All these varaibles in the array are computed in the above procedure
based on data passed. I used lindex to access all these variables.
Inside this procedure it is fine.
}
Outside the procedure I call MODNAME and MODELNUM and use it to do a
string compare it says
"Error can't read MODELNUM no such variable."
If I call parray CurrentItem outside the procedure - it says
CurrentItem is not an array though it is declared as global
When I use a procedure and pass these values in the array then I can
access it. Without using a procedure how do I access the array values
of CurrentItem.
I never meant for anyone to do my homework, I am new to TCL and hence I
am running into these issues.
Thanks
krithiga
What array? I don't see the use of an array in the above code.
> ...
> Outside the procedure I call MODNAME and MODELNUM and use it to do a
> string compare it says
> "Error can't read MODELNUM no such variable."
If you get that error it means either a) you typed the variable wrong,
or b) you didn't declare it global.
Show us the exact code and we can find the problem. Otherwise we have to
keep playing a guessing game.
> If I call parray CurrentItem outside the procedure - it says
> CurrentItem is not an array though it is declared as global
Based on your earlier code samples, CurrentItem is _not_ an array - it
is a list. Those are two separate data types.
This is an array:
set CurrentItem(a) "this is element a"
set CurrentItem(b) "this is element b"
parray CurrentItem
This is a list:
set CurrentItem [list "this is element a" "this is element b"]
puts $CurrentItem
I think you are confusing the two.
}
set modelNum [string range $data 5 end]
set model_name $PRODMOD1($modelNum)
set prod_type $PRODMOD2($modelNum)
set prod_status $PRODMOD($modelNum)
regexp (.*)- $model_name match MN1
set prod_code $PRODCODE($MN1)
if {[string compare $PRODMOD0($modelNum) $modelNum]!= 0} {
#beep
tk_messageBox -title "Invalid System part Number:model #nnnn not
found"
return -code -error
}
# set CurrentItem [list sys_part $modelNum $model_name $prod_type
$prod_status $prod_code]
#lappend CurrentItem sys_part $modelNum $model_name $prod_type
$prod_status $prod_code
#puts $CurrentItem
set SP [lindex $CurrentItem 0]
set MODNAME [lindex $CurrentItem 2]
set MODELNUM [lindex $CurrentItem 1]
set PRODTYPE [lindex $CurrentItem 3]
set PRODSTAT [lindex $CurrentItem 4]
set PROD_CODE [lindex $CurrentItem 5]
set CurrentItem1($modelNum) $modelNum
parray CurrentItem1
puts $CurrentItem1($modelNum)
}
parray CurrentItem1
If I use it outside the proc it says CurrentItem1 is not an array
What I am trying to accomplish is set Current Item with the
entries(nodel_name, modelNum...) and access these entries outside the
procedure. Do not know if a list or an array is best to do this. I
declared a list and then used lindex to access each element. When I use
these outside the procedure(like MODNAME and MODELNUM) it gives Error
varaible not found. I do not have a key by which I access these. It is
like a lookup table. What am I doing wrong?
Thanks
krithiga
Nowhere in the code that you posted do you call validate_sys_part; until
that procedure is called CurrentItem1 isn't set to anything. If
CurrentItem1 has never been initialized, you'll get the error that you
are seeing.
Besides that one fundamental mistake, you have an error with this block
of code:
# tk_messageBox -title "Invalid System part Number:model #nnnn not
found"
> return -code -error
That needs to be "return -code error".
Also, in the preceeding statement you set the title to something really
large; typically the title is something like "Invalid part number", with
the longer text as part of the -message.
Even with that statement added to the code you posted earlier, you still
aren't calling validate_sys_part before calling parray at the end of the
script. The way you've designed your code, you must call
validate_sys_part to initialize CurrentItem1.
Here's a suggestion: add a button so you can do the parray after you've
tried entering some data and validating it:
pack [button .debug -text Debug -command debug]
proc debug {} {
global CurrentItem1
parray CurrentItem1
}
Now, type in some text, press return to do the validation, then click on
the button to see that CurrentItem1 does indeed have data in it
set SCR1 $SCRDEF0(startup)
} else {
set SCR1 $SCRDEF($DefaultScreen)
}
set SCR2 [lindex $SCR1 0 ]
set SCR3 [split $SCR2 , ]
foreach FN $SCR3 {
set FN [string trimleft $FN]
puts $FN
set FN1 [string trimleft [lindex $lookup0($FN) 0]]
frame .editFr$FN -borderwidth 10
pack .editFr$FN -side top -fill x
label .editFr$FN.label$FN -text $FN1: -padx 0 -font {ariel 14 bold}
entry .editFr$FN.edit$FN -width 30 -relief sunken -textvariable NOS
-font {ariel 14 bold}
pack .editFr$FN.label$FN -side left -anchor w
pack .editFr$FN.edit$FN -side right -anchor e
.editFr$FN.edit$FN configure -background lightgray
focus .editFr$FN.edit$FN
bind .editFr$FN.edit$FN <Return> {set retKey 0; validate_entry $NOS
$FN ; CurrItem}
#}
}
Now where I set NOS for textvariable and use this variable when I bind
is not working corrrectly. What happens is when I type on second field
on screen all fields are getting updated with the value. But I change
NOS to $FN it does not do that? Why does this happen?
I don't understand that statement. There's no magic in it not seeing the
value. If a proc doesn't see a value it's because you are doing
something wrong, not because it can't be done.
> I call Validate_sys_part only once. In my code
> it cannot be called mor than once as I am calling it to validate
> entries.
> Based on my original question of capturing the data in a variable if
> I do this for more fields the focus gets set on all fields instead of
> second field.
What you say is incorrect, but perhaps it is just a case of using the
wrong terminology. Focus can only be on a single widget at any one time,
so you must mean something other than focus.
> Here is the code
> ...
> Now where I set NOS for textvariable and use this variable when I bind
> is not working corrrectly. What happens is when I type on second field
> on screen all fields are getting updated with the value. But I change
> NOS to $FN it does not do that? Why does this happen?
You are telling every entry widget to use the same textvariable when you
use NOS for every one. So, when you change the value in that
textvariable, it changes every widget associated with that variable.
What else would you expect it to do? When you use $FN, since $FN is
unique for each field they don't all change at the same time.
Now that you've shown us your real problem, might I suggest trying the
following, which involves using an array to store your data.
Put this code in a file, run it with wish, enter some data in each field
and press return. On stdout you'll see that it's correctly seeing the
proper values.
# beginning of the example code
proc display_screen {} {
variable data
set SCR3 {Test1 Test2 Test3}
foreach FN $SCR3 {
set FN1 "Field $FN"
frame .editFr$FN -borderwidth 10
pack .editFr$FN -side top -fill x
label .editFr$FN.label$FN -text $FN1: -padx 0 -font {ariel 14 bold}
# notice the use of an array for the textvariable
entry .editFr$FN.edit$FN -width 30 -relief sunken \
-textvariable data($FN) \
-font {ariel 14 bold}
pack .editFr$FN.label$FN -side left -anchor w
pack .editFr$FN.edit$FN -side right -anchor e
.editFr$FN.edit$FN configure -background lightgray
# this serves no real purpose since it's in a loop and only
# one widget can have focus at a time
# focus .editFr$FN.edit$FN
bind .editFr$FN.edit$FN <Return> [list validate_entry $FN]
}
set FN [lindex $SCR3 0]
set firstentry .editFr$FN.edit$FN
after idle [list focus $firstentry]
}
proc validate_entry {FN} {
variable data
puts "validating field $FN; value is '$data($FN)'"
}
display_screen
# end of the example code
By the way, instead of binding to <Return>, the text widget has some
options specfically for validating input. The main drawback to binding
on <Return> doesn't handle other cases such as when the user uses the
tab or shift-tab key to go to a different field, or clicks in another
field, etc.
proc display_screen1 {} {
global lookup SCRDEF0 SCRDEF lookup0 dat sys_part DefaultScreen
address serial_num FN SNM
if {[string compare $DefaultScreen startup] == 0} {
set SCR1 $SCRDEF0(startup)
} else {
set SCR1 $SCRDEF($DefaultScreen)
}
set SCR2 [lindex $SCR1 0 ]
set SCR3 [split $SCR2 , ]
foreach FN $SCR3 {
puts $FN
set FN [string trimleft $FN]
puts $FN
set FN1 [string trimleft [lindex $lookup0($FN) 0]]
puts "FN1 is $FN1"
frame .editFr$FN -borderwidth 10
pack .editFr$FN -side top -fill x
label .editFr$FN.label$FN -text $FN1: -padx 0 -font {ariel 14 bold}
entry .editFr$FN.edit$FN -width 30 -textvariable address($FN) -relief
sunken -font {ariel 14 bold}
pack .editFr$FN.label$FN -side left -anchor w
pack .editFr$FN.edit$FN -side right -anchor e
.editFr$FN.edit$FN configure -background lightgray
bind .editFr$FN.edit$FN <Return> {set retKey 0;
set SNM $address($FN)
puts "SNM is $SNM"
validate_entry $SNM $FN
}
puts "I am here after foreach"
set FN [string trimleft [lindex $SCR3 0]]
set firstentry .editFr$FN.edit$FN
after idle [list focus $firstentry]
}
proc validate_entry {data field_name} {
global dat lookup DX2 sys_part DAT3 address serial_num FN SNM
puts $data
puts $field_name
set field_name [string trimleft $field_name]
set DX2 $lookup($field_name)
set data [string toupper $data]
regsub -all "\[ \t\n\]+" $data {} $data
set data [string trim $data]
if {[regexp DX2 $data] != 1} { # test data against correct format
#tk_messageBox -title "Electronic Traveler Entry Error" -type ok -icon
error \
set $data "" ;# clear the entry box text
#return 1 ;# invalid input
}
# return 0 ; OK
puts "I am done with valid entry"
}
I removed the list and the data is entered but is not progessing to the
next field as when I hit return Key it stays in the same field as it
executes the same thing. it does not come out of the foreach loop. I
put in the code for validate_entry. What am I missing?
Thanks
krithiga
Why? Is this a requirement placed on you, or an assumption you are making?
> When
> I use the list it says " invalid command name validate_sys_part
> 0206061234 sys_ser. So I took the list out
Can you show the code you used before taking the list out, and also
after? Certainly the code I posted didn't have this error, so you're
doing something wrong in translating it to your code.
> ...
> bind .editFr$FN.edit$FN <Return> {set retKey 0;
>
> set SNM $address($FN)
> puts "SNM is $SNM"
> validate_entry $SNM $FN
> }
>
I strongly recommend you use a proc rather than a bunch of inline code.
There's simply no reason not to. procs are cheap.
bind .editFr$FN.edit$FN <Return> [list validate $FN]
proc validate {FN} {
global address retKey
set retKey 0
set SNM $address($FN)
puts "SNM is $SNM"
validate_entry $SNM $FN
}
> ...
> I removed the list and the data is entered but is not progessing to the
> next field as when I hit return Key it stays in the same field as it
> executes the same thing.
You do not tell it to progress to the next field. Generally speaking,
you need to know what window the cursor is in, then use something like
"focus [tk_focusNext $window]"
You can include the window parameter as part of the return binding:
bind .editFr$FN.edit$FN <Return> \
[list validate $FN .editFr$FN.edit$FN]
proc validate {FN w} {
if {[validate_entry ...]} {
focus [tk_focusNext $w]
}
}
> it does not come out of the foreach loop. I
> put in the code for validate_entry. What am I missing?
There is no foreach loop in the code that gets executed when you press
return, so I don't understand what you mean when you say "it does not
come out of the foreach loop".
There are other errors in your code. For one thing, you are missing a
curly brace. Your code looks like this:
proc display_screen1 {} {
...
proc validate_entry {data field_name} {
...
}
My guess is, you need a closing curly brace on the line before "proc
validate_entry". While it's possible to define a proc inside another
proc, it is unusual. Since you are just now learning Tcl it's best to
stick to the standard practices.
Also, this code has a bug:
proc validate_entry {data field_name} {
...
error \
set $data "" ;# clear the entry box text
That is wrong in at least two ways. For one, the error command doesn't
take tcl code to execute.
Second, data is a local variable that contains whatever is in
$address($FN). That is, what the user entered.
Let's assume for the moment that the user entered "bogus". When you do
'set $data ""' that is equivalent to 'set bogus ""' which has no effect
in your code.
Remember that the -textvariable for each entry is address($FN); data is
just a local variable that has the string passed in. Thus, to reset the
entry you need to do 'set address($field_name) ""'. I bet users will be
really surprised to see their input vanish if it's incorrect; you might
want to rethink that.
FWIW, your code would be a bit more easy to read if you were consistent
with variable names; if you use FN for field name in one place, you
should use FN as the field name in other places; it makes it easier to
mentally tie the sections of code together.
Thanks
krithiga
Bryan Oakley wrote:
Using the tab key to move between fields is controlled by class
bindings. To inhibit class bindings include a break in your widget
specific binding.
Here's an example:
bind $entryWidget <Tab> {if {![valid ...]} break}
You can't really prevent users from clicking in other fields though, so
this is likely going to give you a false sense of security. Of course,
when focus goes to another window you can always force it back to where
you want, but that is decidedly non-user-friendly.
Instead of forcing focus to a widget, I recommend simply letting the
user know if the field is valid or not so they can make the choice to
move focus or not. For example, if the field is invalid you can change
the color of the widget or the label.
Thanks
krithiga
Bryan Oakley wrote:
Exactly the way I said in my earlier reply. If you create a binding on
<Tab> for the widget, and that binding does a "break", the normal
behavior of <Tab> will be interrupted.
Try this experiment:
entry .e1
entry .e2
pack .e1 .e2
bind .e1 <Tab> {break}
bind .e2 <Tab> {break}
You should see that pressing <Tab> does not move you from one field to
the other.
Thanks
krithiga
bind .editFr$FN.edit$FN <Return> [list validate $FN
.editFr$FN.edit$FN]
proc validate {FN w} {
global address retKey
set CurrentItem2 [list]
set retKey 0
set SNM $address($FN)
puts "SNM is $SNM"
validate_entry $SNM $FN
validate_special $SNM $FN
if {[validate_entry $SNM $FN] == 1 && [validate_special $SNM
$FN] == 1 } {
break
} else {
focus [tk_focusNext $w]
As I said in the earlier message, the break must be part of the bind,
not part of the procedure.
However, instead of doing that, in your validate code you can do "return
-code break" to get the same effect.
So, either do it this way:
bind $widget <Tab> {if {![validate]} break}
or:
bind $widget <Tab> {validate}
proc valid {} {
if {<data isn't valid>} {
return -code break
}
}
This is all documented in the bind and break man pages. I get the
impression you haven't read those yet; perhaps now would be a good time
to do that.
bind .editFr$FN.edit$FN <Return> [list validate $FN
.editFr$FN.edit$FN]
proc validate {FN w} {
global address retKey Errflag SNM
set retKey 0
set SNM $address($FN)
puts "SNM is $SNM"
validate_entry $SNM $FN
puts "Errflag in display_screen1 is $Errflag"
if {$Errflag == 1} {
puts "I am here because of error"
return -code break
}
focus [tk_focusNext $w]
}
It seems that you aren't taking the time to understand the examples I've
given to see why they work. You are cutting and pasting code without
understanding, which is why you keep making the same type of mistakes
over and over.
For this specific problem, the problem is in your logic somewhere. Tk
has no defaults for moving focus on anything but <Tab> and <Shift-Tab>
and you are having problems with <Return>. What that means is, focus
will only move when you tell it to move. Thus, you have problems in your
logic where you are telling it to move at the wrong times.
The "return -code break" absolutely does nothing as part of the code
executed when the user presses <Return>. My example was specifically for
<Tab> because that's what I thought your earlier post was about.
The bottom line is, you haven't taken the time to understand some very
fundamental things about Tk -- things you need to know before doing
something of an intermediate level such as creating dynamic forms.
Lets look at a few basic pieces of information:
"return -code break" or adding ";break" to a binding means "don't
continue with evaluating binding scripts". If there are no other binding
scripts (such as the case with <Return>) it has no effect. This is
documented in the bind man page.
Focus changes are defined in the "all" bindtag for <Tab> and
<Shift-Tab>. "all" bindings fire after instance bindings. You can read
about this in the bindtags man page. This is why break works when you
put a binding on <Tab> -- if you do a break in your binding, it prevents
the "all" binding from being executed and focus therefore doesn't change.
If you put curly braces around a script attached to a binding, variable
substitution happens when the binding fires. When you use [list ...],
substitution happens when the binding is defined. As a general rule you
almost always want to use list. In your specific case, you want to use list.
Thus, if you do this:
bind ... <Return> [list validate $FN .editFr$FN.edit$FN]
Then the procedure validate will get passed whatever FN was at the time
the bind command was run. Since you are calling this in a loop and want
the field name associated with the widget, this is what you want.
If, however, you use curly braces instead, as in:
bind ... <Return> {validate $FN .editFr$FN.edit$FN}
whatever value is in the global variable FN *at the time the binding
fires* is what is passed to the validate proc. Rarely, this is a good
thing. Best practices says to not rely on this because you can't control
when the user is going to click on something.
I'm curious -- is this project just so you can learn Tcl, is it a
homework assignment, or is it a requirement of your job?
Thanks
krithiga
Bryan Oakley wrote:
Can you show me an example of how I can clear the field name
Thanks
krithiga
The above shows you attempting redefine the widget, this is not required --
just set the value of the variable to {} to "clear" it.
SYS_PART nnnnn
the following fields come up based on nnnn
SYS_SER
MB_SER
PS_SER
When I press Clear ALL all data in fields are cleared. Now I come in
and type another number for SYS_PART and depending on the number the
rest of the fields can stay or bcome more or become less. So I did set
the varaible associated with field to "" at the beginning but stilll
says the error.
Thanks
krithiga
This example shows that you are trying to create a widget with a name
that is already in use by a widget.
Your "Clear_ALL" procedure probably needs to delete the widgets. That,
or before creating a widget you check for its existence:
if {![winfo exists .editFr$FN...]} {
# create the widget; it doesn't exist
frame ...
}
Here's a hint: create all the widgets that need to be "cleared" in a
frame. When you want to delete the widgets can can do something like:
eval destroy [winfo childiren $frame]
How many different types of parts do you have? If it's a small number of
distinct types, create all the forms ahead of time. You can create an
empty frame that serves as a placeholder. You can then use "place" to
put each frame on top of this placeholder. When the user picks a part,
you simply raise the appropriate frame. It's as if each form is a piece
of paper in a stack, and you take out the appropriate page and place it
on the top of the stack.
I suspect none of that makes sense to you, and I'm not going to type out
a full example. Just be aware there are other ways to handle a dynamic
set of forms other than to constantly destroy and recreate the widgets.
proc button_ClearAll {} {
reset_current_item
display_screen
update idletasks
focus .editFr1.edit1 ;# set focus to first entry box
startup
bind .editFr1.edit1 <Return> {validate_sys_part $dat; display_screen1}
}
proc refresh {} {
global keepVars
eval destroy [winfo children .]
foreach var [info globals] {
if {[lsearch $keepVars $var] == -1} {
global $var
unset $var
}
But the requirement is when we press clear ALL is to clear all the
varaibles and leave the screen as it is and when I type the part no for
the first field the rest of the screen needs to adjust. How do I do
this without destroying the widgets in the above procedures. Checking
for its existence?
Thanks
krithiga
> But the requirement is when we press clear ALL is to clear all the
> varaibles and leave the screen as it is and when I type the part no for
> the first field the rest of the screen needs to adjust. How do I do
> this without destroying the widgets in the above procedures. Checking
> for its existence?
I've been hesitating to do this but I think the best thing to do is to
give you a complete working example.
In this example, all form data is stored in the global array named
'data'. Typing in a part number and pressing return will show a form
based on the part number. I define two different types of parts: widget
and gadgets. Widgets have part numbers ending in '1'; all other part
numbers are gadgets.Part numbers are converted to all uppercase when you
press return.
It's not perfect, and I would do things differently if I had the time.
This is not production quality code -- it's just a quick hack. Yet, I
think it illustrates all the things you are trying to accomplish.
Save the following code to a file and run it with wish. If you enter a
part number that ends in "1" you'll get one type of form; all other
part numbers give you a different type of form.
----- save everything below this line -----
set fields(widget) {name color}
set fields(gadget) {name length width height}
set data(partnum) "" ;# current part number
set data(type) "" ;# current part type
proc main {} {
# This defines the place for the part number
#
frame .partnum
label .partnum.l -text "Part number:"
entry .partnum.e -textvariable data(partnum) -width 32
pack .partnum.l -side left
pack .partnum.e -side left
# this defines the buttons
frame .buttons
button .buttons.clear_form -text "Clear form" \
-command [list clear_form]
button .buttons.clear_all -text "Clear all" \
-command [list clear_all]
pack .buttons.clear_form .buttons.clear_all -side left
# this defines the part-specific form; the contents
# of the form are different for different part numbers
# so it will be filled in later
frame .form -borderwidth 1 -relief sunken
pack .partnum -side top -fill x
pack .form -side top -fill both -expand 1
pack .buttons -side bottom -fill x
# when the user enters a part number and presses return
# we are to display the form for that part; a tab merely
# goes to the top of the current form
bind .partnum.e <Return> new_partnum
bind .partnum.e <Tab> "focus_to_form;break"
wm geometry . 600x400
}
proc new_partnum {} {
global data
set data(partnum) [string toupper $data(partnum)]
show_form
}
proc show_form {} {
global fields data
# remove any existing form
reset_form
# widgets have a part number ending in "1"; gadgets are all others
set partnum $data(partnum)
if {[string match {*1} [string trim $partnum]]} {
set type "widget"
} else {
set type "gadget"
}
set frame .form-$type
set data(type) $type
# Only create the form if it doesn't already exist
if {![winfo exists $frame]} {
frame $frame -borderwidth 2 -relief groove
foreach field $fields($type) {
label $frame.l-$field -text "$field:"
entry $frame.e-$field -textvariable data($field)
grid $frame.l-$field $frame.e-$field
grid configure $frame.l-$field -sticky e
grid configure $frame.e-$field -sticky ew
# have <Return> perform the same behavior of <Tab>
bind $frame.e-$field <Return> [bind all <Tab>]
}
set lastrow [lindex [grid size $frame] 1]
grid rowconfigure $frame $lastrow -weight 1
grid columnconfigure $frame 1 -weight 1
}
# clear the data in the form
clear_form
# make sure this form is the one that is visible
pack $frame -in .form -side top -fill both -expand y
raise $frame
update idletasks
focus_to_form
}
proc focus_to_form {} {
global data fields
set type $data(type)
if {$type == ""} return
set frame .form-$type
focus $frame.e-[lindex $fields($type) 0]
}
proc reset_form {} {
if {![winfo exists .form]} return
if {[pack slaves .form] == {}} return
foreach child [pack slaves .form] {
pack forget $child
}
}
proc clear_form {} {
global fields data
set type $data(type)
if {![info exists fields($type)]} return
foreach field $fields($type) {
set data($field) ""
}
focus_to_form
}
proc clear_all {} {
global data
array unset data
set data(partnum) "" ;# current part number
set data(type) "" ;# current part type
reset_form
}
main
Also how to set the cursor to the Done Button after the last field i.e
how to check if last field?
>From the data entered in the entries I created a text file and then to
load the text file into the widget fields - I am stuck
Here is the code. We need to store the data from the file to the array
and the first element has to got the first field. How do I do this?
proc LoadTextFileData {} {
global address SCRDEF SCR1 FN
set fileName [tk_getOpenFile -initialdir "" -title "Open Text Data
File" -defaultextension ".txt"]
puts $fileName
if {$fileName != ""} {
set LTF [open $fileName r]
}
while {![eof $LTF]} {
set FD [gets $LTF]
puts $FD
set $address(partnum) [lindex $FD 1]
set SCR1 $address(SCR1)
set scr2 [lindex $SCR1 0 ]
set scr3 [split $scr2 , ]
foreach field $scr3 {
Assuming you mean "move the mouse pointer to the Done button", you
can't. Well, you can, but it's really bad UI design. If you want to do
that you'll have to figure it out for yourself. The concept is called
"warping the mouse". Don't do it; your users will hate you.
If you mean "move the keyboard focus", it's no different than setting
the focus to an entry widget -- use the "focus" command.
What people normally do is bind <Return> to the done button and <Escape>
to the cancel button. However, you're using <Return> for something
else, so simply move keyboard focus to the Done button with the focus
command and that will be good enough.
> i.e
> how to check if last field?
It's trivial, and this really is something you need to figure out for
yourself. You're the one creating the fields so just use some logic. We
try not to do other peoples jobs here. If you come up with a solution
that isn't working for a technical reason let us know and we'll be glad
to help.
>
>>From the data entered in the entries I created a text file and then to
> load the text file into the widget fields - I am stuck
>
You aren't having a Tcl problem, you are having a logic problem. You
should be able to figure it out. I don't mean to be rude, but you're
supposed to be a professional programmer yet you keep asking very
fundamental questions that any programmer should be able to figure out.
Again, we're not here to do your job. We try to solve technical problems
but this isn't a technical problem.
To read a file you use open and either gets or read. It appears you've
discovered that already. Your fields are associated with the 'address'
array so it surely must be obvious what to do.
I will give one hint, though... Your code contains this:
> set FD [gets $LTF]
> puts $FD
>
> set $address(partnum) [lindex $FD 1]
Unless you are absolutely certain the data in the file is a Tcl list
(because you wrote it out as a Tcl list and are certain it wasn't
modified in any way), don't use lindex. lindex requires you give it a
list, and will throw an error if the line of data isn't a list. A very
large percentage of all real world data does not meet the criteria of
being a list.
So, follow this rule: *never* use list commands on raw data.
If, after applying all those bits of advice you are still stuck, tell us
specifically how Tcl isn't working the way you think it should. If, for
example, you are getting a Tcl error you don't understand, show us the
exact error and the lines of code causing the error and we can help.
set AS [array size address]
for { set i 0 } {$i < $AS} {incr i} {
validate_entry $SNM $FN
if {$Errflag == 1} {
puts "I am here because of error"
return -code break
} else {
puts "I am here before focus next"
focus [tk_focusNext $w]
}
}
focus -lastfor .buttonFr.xml
}
So for the last element it should come out of the loop and then move
focus to Done button.
Is ths focus command correct?
This is how Done button is
button .buttonFr.xml -text "Done" -command button_Done -padx 20 -font
{ariel 14 bold}
Thanks
krithiga
Bryan Oakley wrote:
> }
>
> So for the last element it should come out of the loop and then move
> focus to Done button.
> Is ths focus command correct?
>
No.
By including "-lastfor" you are requesting that the focus most to the
most recent widget that had focus rather than specifically to
.buttonFr.xml. This behavior is documented in the focus man page.
Remove the "-lastfor" option.
Thanks
krithiga
Bryan Oakley wrote:
I don't keep copies of your code laying around and you don't post enough
to show all the logic so the best I can do is give generic advice.
your code needs to look something like this:
# code where you create the entry fields
foreach field ... {
...
entry $entry ...
bind $entry <Return> [list xxx $field ...]
...
}
...
# code that is called when user presses <Return> in
# an entry widget
proc xxx {field args} {
....
if {[is_last_field $field]} {
focus $done_button
} else {
# send focus to next entry field
...
}
}
It's then just a simple matter of defining the proc is_last_field to
either return 1 (yes, this is the last field) or 0 (no, this isn't the
last field). Focus will then either go to the done button or the next
entry field.
Thanks
krithiga
Tcl, all by itself, has no commands for making sounds.
You can include the "snack" extension which has commands for playing
sound files. Depending on what version of tcl you're using, you may
already have snack. Search using google for the keywords "tcl" and "snack".
Depending on your platform, you can also exec a command that plays a
sound file.
set partnum $address(partnum)
if {[string compare $DefaultScreen startup] == 0} {
puts " I am in display screen1"
set SCR1 startup
} else {
set SCR1 $DefaultScreen
}
set frame .form-$SCR1
set SCR2 [lindex $SCRDEF($SCR1) 0 ]
puts $SCR2
set SCR3 [split $SCR2 , ]
set address(SCR3) $SCR3
If I do a foreach FN $SCRDEF($SCR1) - It is returning a whole string
and FN is returned as {sys_ser, mb_ser...}
Then I do foreach FN $SCR3 {
..set up frame. etc
}
In the Clear_ALL procedure
proc button_ClearAll {} {
global SCRDEF address SCR1 partnum Errflag SCR3 FN
array unset address
set address(partnum) "" ;# current part number
set address(SCR3) "" ;# current part type
}
When I do this the fields in the address(SCR3) are not getting cleared
unless I do a foreach field . What I am doing inorrectly here?
Thanks
krithiga
If your data is truly {sys_ser, mb_ser..} Then FN will have the value
"sys_ser," "mb_ser," etc (note the trailing commas as part of the data).
So, you create array elements like address(sys_ser,), etc. but in your
clear_all function you're trying to reset addrss(sys_ser), etc.
Tcl has no way of knowing that you use commas and spaces to separate
your list items, so when you do a foreach over a string tcl only splits
on spaces and the commas become part of each element.
Since your items are separated by both a comma and space, you either
need to use the textutil::split function in tcllib to convert the string
to a list, or do some other processing of some sort.
Thanks
kc
As I said in my other post, because of bugs in your data, the fields are
being created as "sys_ser,", "mb_sr," etc *with* the commas. That is why
the foreach works (you replicate the bug in two places) but directly
setting the variables with, for example, 'set address(sys_ser) ""' fails
-- address(sys_ser) does not exist but address(sys_ser,) does exist.
As for not being able to read the SCR3 variable until you press return ,
that is a design bug you'll have to work out for yourself.
> Also how do I make the Clear Button and Done button appear only in
> the second form only? This was another thing I thought about. Do I use
> winfo exist on Done button Frame? Or is there a way to dull out the
> Clear Button for the first form and make active only when all fields
> appear? If so please let me know how.
If you read the button man page you'll see that there is a "-state
disbled" option. Most often, you always want the button there and simply
change the state to normal or disabled as appropriate.
label $frame.l-$FN -text "$FN1:" -padx 0 -font {ariel 14 bold}
Is there a way to double this field with putting a different text? (eg
-text "REPAIR $FN1" )
Thanks
kc
I have no idea what you mean by "double this field". Can't you just
create a second label?
label $frame.l1-$FN ...
label $frame.l2-$FN ...
--
Bryan Oakley
http://www.tclscripting.com
MB_SER:
REPAIR MB_SER:
So Each field should appear twice, the second one with a different
text.
Also sone fields need to grayed out meaning they cannot enter info
I tried the label label $frame.l2-$FN and it accepts this.
Thanks
krithiga
Also I am trying to create another form instead of doubling which will
have all the fields like
ORIG MB_SER
ORIG PS_SER
and then store these entries and after these are entered and user hits
DONE, create another form with
REPLACED MB_SER
REPLACED PS_SER
The Form with ORIG will remain on the screen. Is it possible to do this
and if you can please provide an example that would be helpful
Thanks
krithiga
Thanks
kc
It's hard to understand what you are asking. Yes, you can put a default
value into an entry widget:
entry .e -textvariable foo
set ::foo "this is the default value"
You could also do:
.e insert 0 "this is the default value"
Does that answer your question?
> Also I am trying to create another form instead of doubling which will
> have all the fields like
I don't understand what you mean by "instead of doubling".
You can certanly create multiple forms in multiple frames and pack/place
or grid whichever one the user is supposed to see.
What I meant by doubling is
MB_SER
ORIG MB_SER
I am creating 2 filelds ( 2 labels, entries) for the same fileld but
with different text. This is what I meant by doubling
Is there an example of a multiple form and how to pack/place or grid?
Thanks
kc
Now, since the users can make errors we are required to have 2 forms
which are the same
One form will have
ORIG MB_SER
ORIG PS_SER
After user enters data in these fields and hits DONE, it goes to
another form which has same fields but different text
REPLACE MB_SER
REPLACE PS_SER
I used the same procedure to use this but since the form exists with
the frame it is not creating another form. I do not want to destroy the
original form but add to the form with the same fields.
How do I accomplish this?
Thanks
kc
I suggest you try it and see.
>
> What I meant by doubling is
>
> MB_SER
>
> ORIG MB_SER
>
> I am creating 2 filelds ( 2 labels, entries) for the same fileld but
> with different text.
What text is different? The label? Why create two fields for the same
"fileld" (field?)?
I'm sorry, your requirements just aren't clear.
I would suggest first trying to create two forms that are both visible
at the same time. Get that working, then we can show you how to modify
your code so only one form is visible at a time.
Put the forms in different frames.
proc create_form {frame} {
frame $frame
label $frame.label1 ...
entry $frame.entry1 ...
label $frame.label2 ...
entry $frame.entry2 ...
}
create_form .form1
create_form .form2
if {[show_form_1?]} {
pack forget .form2
pack .form1
} else {
pack forget .form1
pack .form2
}
There are many different ways to do it. For example, you could pack one
form then use place to place the other form directly on top. Then you
would use "raise" and "lower" to pick which one is visible.
> krith...@yahoo.com wrote:
> > I need to use the word duplicate. I am sorry. I am duplicating the
> > fields and using a diffierent text name
> > MB_SER
> > ORIG MB_SER
>
> Put the forms in different frames.
>
> proc create_form {frame} {
> frame $frame
> label $frame.label1 ...
> entry $frame.entry1 ...
> label $frame.label2 ...
> entry $frame.entry2 ...
> }
And filling in a bit of the "...", we'd probably want another parameter
or two. Let's assume the parameters are in three arrays, to facilitate
block-copies. Then it makes sense to name the arrays by the identifier
"ORIG", "REPLACE",
proc create_form {frame ident} {
set arr "[string trim $ident]values"
set txtid [string trim $ident]
if { [string length $txtid] } {
append txtid " "
}
frame $frame
label $frame.label1 -text "${txtid}MB_SER" ...
entry $frame.entry1 -textvariable $arr(MB_SER) ...
label $frame.label2 -text "${txtid}PS_SER" ...
entry $frame.entry2 -textvariable $arr(PS_SER) ...
}
create_form .form1 ""
create_form .form2 "ORIG"
create_form .form3 "REPLACE"
Copy values like
array set values [array get ORIGvalues]
... edit ... edit ...
array set REPLACEvalues [array get values]
--
Donald Arseneau as...@triumf.ca
You're making too much work for yourself. Just use a single array for
the textvariables, and another one for the labels:
label $frame.label1 -text $text($ident,MB_SER) ...
entry $frame.entry1 -textvariable $data($ident,MB_SER)
Rarely, if ever, does it make sense to use dynamically created variable
names; it just makes the code harder to read.
When I have one form and hit DONE button it creates the other form with
REPLACED but ORIG goes away. How does the orig form still stay on the
screen and how to use place to place in the above example?
Thanks
krithiga
if { $ident == "ORIGINAL" } {
puts "ident is $ident"
button .buttonFr.orig -text "ORIGINAL SERIAL NOS FINISH" -command
Orig_Done -padx 20 -font {ariel 14 bold}
pack .buttonFr.orig -side left -padx 60 -anchor center
.buttonFr.orig configure -background lightgreen
} else {
pack forget .buttonFr.orig
button .buttonFr.xml -text "DONE" -command button_Done -padx 20 -font
{ariel 14 bold}
pack .buttonFr.xml -side left -padx 60 -anchor center
.buttonFr.xml configure -background lightgreen
}
and then use
if { $ident == "ORIGINAL" } {
#pack $frame -in .form -side top -fill both -expand y
pack .form1 -side top -fill both -expand y
# raise $frame
update idletasks
} else {
#pack $frame -in .form -side bottom -fill both -expand y
pack .form3 -side top -fill both -expand y
# raise $frame
update idletasks
}
Then the ORIG and REPLACE both appear but what happens is the ORIG and
REPLACE serial nos show up below the Clear and Done button. the clear
and DONE button should be below. If use pack $frame this does not
happen meaning the Clear and DONE button is below but the REPLACED
screen overrides the ORIG screen
Can you explain what pack $frame does and pack .form does and what I am
doing wrong?
Thanks
krithiga