TK: question about "-command" usage with "{}" and double quoter

37 views
Skip to first unread message

Hans

unread,
Mar 28, 2006, 2:46:31 AM3/28/06
to
Hi,
I have some codes listed below:
entry $entry1 -width 20 -relief sunken -textvariable entry1
entry $entry2 -width 50 -relief sunken -textvariable entry2
button $confirm -text Confirm -command {
set commit [proc_to_run_other_thing $entry1 $entry2];
puts "entry1:$entry1, entry2:$entry2" }
button $w.command.cancel -text Cancel -command { set commit 0 }
pack ......
vwait commit
I found the proc_to_run_other_thing proc cannot use updated value for
entry1 and entry2, that's because I'm using "{}" to specify command,
then I think I should use double quoter . but I already have the puts
command which is using double quoter, so I cannot use double quoter for
the two command again.

How to solve the problem? Thanks.

Artur

unread,
Mar 28, 2006, 3:21:32 AM3/28/06
to
Command callbacks are evaluated in global context
so {set commit 0} will concern global variable commit.

try
global commit
vwait commit
or even better
vwait ::commit
perhaps own namespace or OO-Tcl to avoid some unexpected side effects
in global context

See implemtation of Tk dialog for good example
in file dialog.tcl

Ralf Fassel

unread,
Mar 28, 2006, 5:58:20 AM3/28/06
to
* "Hans" <han...@gmail.com>

| I found the proc_to_run_other_thing proc cannot use updated value for
| entry1 and entry2, that's because I'm using "{}" to specify command,
| then I think I should use double quoter . but I already have the puts
| command which is using double quoter, so I cannot use double quoter for
| the two command again.
|
| How to solve the problem? Thanks.

- use a helper proc if -command gets lengthy
- use list to build up the command

proc button_helper {e1 e2} {
set ::commit [proc_to_run_other_thing $e1 $e2]
puts "entry1:$e1, entry2:$e"
}

button $confirm -text Confirm \
-command [list button_helper $entry1 $entry2]

See also
http://wiki.tcl.tk/9330

HTH
R'

Bryan Oakley

unread,
Mar 28, 2006, 7:26:55 AM3/28/06
to
Ralf Fassel wrote:
> * "Hans" <han...@gmail.com>
> | I found the proc_to_run_other_thing proc cannot use updated value for
> | entry1 and entry2, that's because I'm using "{}" to specify command,
> | then I think I should use double quoter . but I already have the puts
> | command which is using double quoter, so I cannot use double quoter for
> | the two command again.
> |
> | How to solve the problem? Thanks.
>
> - use a helper proc if -command gets lengthy
> - use list to build up the command

Excellent advice. Personally, I define "lengthy" as "more than one
command". It almost always makes all quoting problems vanish WRT
-command and for bindings.

Hans

unread,
Mar 28, 2006, 4:08:30 PM3/28/06
to
Hi, Thanks for response.
I tried "list" command but it seems I only can get the original value
for variable entry1 and entry2, but I hope I can get the updated value
because maybe user input some new value for those entries. when I click
"confirm" I hope I can get the updated value to do something.

and those entry variable are not global value, so I cannot use them
with {}.

Bryan Oakley

unread,
Mar 28, 2006, 6:39:42 PM3/28/06
to

Create a helper proc and your quoting problems should vanish. If they
don't, show us what you did and we can help.

As for your comment "those entry variable are not global", I think
you're mistaken. If they are a -textvariable, they are global.

Your original code was this:

entry $entry1 -width 20 -relief sunken -textvariable entry1
entry $entry2 -width 50 -relief sunken -textvariable entry2
button $confirm -text Confirm -command {
set commit [proc_to_run_other_thing $entry1 $entry2];
puts "entry1:$entry1, entry2:$entry2" }
button $w.command.cancel -text Cancel -command { set commit 0 }

In the above, you use or create two different variables with the same
name. You have local variables named "entry1" and "entry2" which
contains the path of the widgets. You also create two global variables
named "entry1" and "entry2" which are the textvariables associated with
the widget.

That is very confusing. I strongly recommend against using the same
variable name in two different ways like that. Tk can handle it but it
is obviously causing you (and us) some confusion.

In the above code, what do you expect to pass to
proc_to_run_other_thing? Is is the name of the widgets or the variable
name associated with the entry widgets or the value associated with the
widgets?

In either case, a helper proc would really help. There's simply no
reason not to do it, especially if you are just learning tcl.

Here's one way to solve your problem:

entry $entry1 -width 20 -relief sunken

entry $entry2 -width 20 -relief sunken
button .confirm -text Confirm \
-command [list _do_confirm $entry1 $entry2]
proc _do_confirm {w1 w2} {
puts "widget $w1 has this value: [$w1 get]"
puts "widget $w2 has this value: [$w2 get]"
# if proc_to_run_other_thing expects widgets:
# proc_to_run_other_thing $w1 $w2
# if proc_to_run_other_thing expects values:
# proc_to_run_other_thing [$w1 get] [$w2 get]
}

The above isn't necessarily the best way to solve your problem but I
think it might be the most straight-forward. Once you can understand why
the above works and your code doesn't we can move on to other solutions.

Hans

unread,
Mar 29, 2006, 2:47:36 AM3/29/06
to
Thanks a lot.
I'm sorry for I made a mistake in my code example, it should be:
entry $entry1_name -width 20 -relief sunken -textvariable
entry1_variable
entry $entry2_name -width 50 -relief sunken -textvariable
entry2_variable

button $confirm -text Confirm -command {
set commit [proc_to_run_other_thing $entry1_variable
$entry2_variable];
puts "entry1:$entry1_variable,
entry2:$entry2_variable" }

button $w.command.cancel -text Cancel -command { set commit 0
}
your code definitly works, thanks again. Can I have two more questions?
1. Is there a way to not use global variable in -textvariable for
entry? I just want to use the variable to get some inputing value from
user, then do sth in another proc(send those updated value to SQL
database). I don't like create too many global variables
2. I hope pass the updated variable value to my proc_to_run_other_thing
directly by "-command" in one line, can I do it? I don't like to create
too many procedures.
Thanks again.

Bryan Oakley

unread,
Mar 29, 2006, 7:37:08 AM3/29/06
to
Hans wrote:
> Thanks a lot.

> 1. Is there a way to not use global variable in -textvariable for
> entry? I just want to use the variable to get some inputing value from
> user, then do sth in another proc(send those updated value to SQL
> database). I don't like create too many global variables

As my example showed, you don't need to use -textvariable at all. If you
do, it must represent a variable accessible from the global scope. That
means either a global variable or a fully qualified namespace variable.
There is no way to have it use a local variable.

> 2. I hope pass the updated variable value to my proc_to_run_other_thing
> directly by "-command" in one line, can I do it? I don't like to create
> too many procedures.

Why don't you like to "create too many procedures"? Procedures are a
tool to get your job done, and in this case will make your job easier.

However, you can certainly do what you want without using a proc. It
requires all quoting tricks that will make your code hard to read and
hard to maintain.

Here's how to do it. It is untested, but I think I got it right. That's
the problem with this approach however: it's _really_ hard to know just
by looking at it if it's correct or not, and it's difficult to modify
the code later:

button $confirm -text Confirm -command "
set commit \[proc_to_run_other_thing \$entry1_variable \
\$entry2_variable\];

puts \"entry1:\$entry1_variable, entry2:\$entry2_variable\""

This will, BTW, set the global variable "commit". Commands, like
bindings, execute in the global level.

If I were reviewing that code for use in a production environment I
would reject it.

Donal K. Fellows

unread,
Mar 29, 2006, 8:35:43 AM3/29/06
to
Bryan Oakley wrote:
> However, you can certainly do what you want without using a proc. It
> requires all quoting tricks that will make your code hard to read and
> hard to maintain.
[...]

> button $confirm -text Confirm -command "
> set commit \[proc_to_run_other_thing \$entry1_variable \
> \$entry2_variable\];
> puts \"entry1:\$entry1_variable, entry2:\$entry2_variable\""

It strikes me that now is the time to point out that Tcl 8.5a4 has a new
trick up its sleeve that will allow a bridge between these two ways of
operating: the [apply] command, as described in TIP#194:

button $confirm -text Confirm -command [list apply {{x} {
global entry1_variable entry2_variable
set commit [proc_to_blah $entry1_variable $entry2_variable]


puts "entry1:$entry1_variable, entry2:$entry2_variable"

puts "Gratuitously, x = $x"
}} "gratuitous extra argument"]

At the moment, I suspect that using a helper procedure will continue to
be clearer, but the "too many procs" argument will be moot... :-)

Donal.

Bryan Oakley

unread,
Mar 29, 2006, 11:08:26 AM3/29/06
to
Donal K. Fellows wrote:

> It strikes me that now is the time to point out that Tcl 8.5a4 has a new
> trick up its sleeve that will allow a bridge between these two ways of
> operating: the [apply] command, as described in TIP#194:
>
> button $confirm -text Confirm -command [list apply {{x} {

> ...
> }} "gratuitous extra argument"]
>

I wish we could remove one or two layers of list-ness. I haven't thought
about it much; it's just a knee jerk reaction, but isn't this a bit cleaner:

button .b ... -command [apply {x} {body} arg arg ...]

I'm not sure I see the benefits of all the nesting. Can't the usage be:

apply arglist body ?-namespace namespace? ?--? ?arg ...?

That to me looks a lot cleaner than

apply {arglist body ?namespace?} ?arg ...?

miguel sofer

unread,
Mar 29, 2006, 11:28:58 AM3/29/06
to

Those lists are there for one particular purpose: the whole anonymous
function is one string - internally, one Tcl_Obj. This allows for the
bytecompiled version to be cached and reused - huge performance difference.

The syntax is rather
apply anonFunction ?arg ...?
where an anonymous function has a structure
argList body ?namespace?

So you can do
set anon {{x y} {expr {$y+$x}}}
apply $anon 1 2
apply $anon 3 4
and avoid having to recompile that body at each call.

In your proposed version, keeping the anon function in a variable would
require the use of {expand} or [eval]. Not good, quite apart from
performance considerations. IMO.

Miguel

Cameron Laird

unread,
Mar 29, 2006, 2:08:02 PM3/29/06
to
In article <UbvWf.61428$Jd.5...@newssvr25.news.prodigy.net>,
Bryan Oakley <oak...@bardo.clearlight.com> wrote:
>Hans wrote:
.
.
.

>As my example showed, you don't need to use -textvariable at all. If you
>do, it must represent a variable accessible from the global scope. That
>means either a global variable or a fully qualified namespace variable.
>There is no way to have it use a local variable.
.
.
.
It *could* like in a non-default namespace, though,
and I know that makes a huge difference to some
stylists. Hans, you might consider

... -textvariable ::widgets::entry::my_variable

or something similiar.

Hans

unread,
Mar 29, 2006, 11:34:54 PM3/29/06
to
Thanks. Just as you suggedsted, I tried to use helper-proc and delete
-textvariables, it works. then I got another problem, I hope to return
a value(commit) from my proc , can I do it? I tried:
-command [set commit [list proc $entry1 $entry2]]
-command [set commit [[list proc $entry1 $entry2]]]
both do not work. Thanks .


another little question, Can I set a default value for my entry? before
, I used a textvariable, then I can set the default value by the
variable, now, I don't use textvariable any more. Is there another way
to set a default value for entry widget?

thanks.

Ralf Fassel

unread,
Mar 30, 2006, 2:20:59 AM3/30/06
to
* "Hans" <han...@gmail.com>

| 1. Is there a way to not use global variable in -textvariable for
| entry? I just want to use the variable to get some inputing value
| from user, then do sth in another proc(send those updated value to
| SQL database). I don't like create too many global variables

As others have pointed out, a -textvariable is always global (or
namespace-global).

| 2. I hope pass the updated variable value to my proc_to_run_other_thing
| directly by "-command" in one line, can I do it?

Not reliably and directly. If you use quoting tricks some weird
contents of the entry *will* break the command.

So here's how to do it:

- using no -textvariable:
as Bryan has already shown in another post, you have to pass the
name of the entries to your proc and use the entry 'get' command to
get the contents:

set entry1 .e1
set entry2 .e2
set confirm .confirm
entry $entry1
entry $entry2
button $confirm -text Confirm \
-command [list proc_to_run_other_thing $entry1 $entry2]

proc proc_to_run_other_thing {entry1 entry2} {
# get the current contents of the entries
set contents1 [$entry1 get]
set contents2 [$entry2 get]
# now we can use $contents1 and $contents2
puts "entry1 has: $contents1"
puts "entry2 has: $contents2"
...
# set the global indicator _here_
set ::commit ...
}

Without a textvariable, you can set initial contents for the
entries via
$entry1 insert 0 "some initial text"


- using -textvariable:
you can pass the *name* of the variable to your proc and then use
'upvar' to look at the variable contents

set entry1 .e1
set entry2 .e2
set confirm .confirm
entry $entry1 -textvariable entry1_variable
entry $entry2 -textvariable entry2_variable
button $confirm -text Confirm \
-command [list proc_to_run_other_thing entry1_variable entry2_variable]

proc proc_to_run_other_thing {varname1 varname2} {
# get the current contents of the variables via upvar
upvar \#0 $varname1 contents1
upvar \#0 $varname2 contents2
# now we can use $contents1 and $contents2
puts "entry1 has: $contents1"
puts "entry2 has: $contents2"
...
# set the global indicator _here_
set ::commit ...
}

With a textvariable, you can set initial contents for the
entries either as above or by setting the textvariable:
set ::entry1_variable "some initial text"

HTH
R'

Donal K. Fellows

unread,
Mar 30, 2006, 3:12:46 AM3/30/06
to
Bryan Oakley wrote:
> I wish we could remove one or two layers of list-ness. I haven't thought
> about it much; it's just a knee jerk reaction, but isn't this a bit
> cleaner:
>
> button .b ... -command [apply {x} {body} arg arg ...]

Ah, a misunderstanding. The [apply] is interpreted later on, when the
callback is interpreted. What you really want is the following helper
procedure:

proc callback {arglist body args} {
set ns [uplevel 1 {namespace current}]
return [list apply [list $arglist $body $ns] {expand}$args]
}

Which then lets you write:

button .b -command [callback {x} {body} arg arg ...]

Donal.

Bryan Oakley

unread,
Mar 30, 2006, 9:40:16 AM3/30/06
to
Donal K. Fellows wrote:

> Ah, a misunderstanding. The [apply] is interpreted later on, when the
> callback is interpreted. What you really want is the following helper
> procedure:
>
> proc callback {arglist body args} {
> set ns [uplevel 1 {namespace current}]
> return [list apply [list $arglist $body $ns] {expand}$args]
> }
>
> Which then lets you write:
>
> button .b -command [callback {x} {body} arg arg ...]
>

Yeah, I realized a little later that I could create what I want from the
apply primitive.

It seems like a very common use for [apply ...] is this very situation
so I'm wondering if a [callback ...]-like command should be a built-in.
I betcha I won't be the only person with this misunderstanding.

I'm not complaining though! I can't wait to get my hands on 8.5 for real
work. We were talking about that just yesterday, if we should take the
plunge and upgrade now. I was the wet blanket saying to at least wait
until it goes beta. Fortunately for us (us being the tcl community at
large) that the betas are consistently of remarkably high quality.

Reply all
Reply to author
Forward
0 new messages