Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Starting with Tk

93 views
Skip to first unread message

Cecil Westerhof

unread,
Jun 26, 2018, 11:28:05 AM6/26/18
to
In another thread I 'was told' I should use Tk. I am not used to
programming GUI's, but that does not mean I should not start with it.
A GUI can certainly be an advantage.

I made the following simple skeleton:
#!/usr/bin/env tclsh

package require Tk


proc commandSelected {listbox} {
puts "Command: [$listbox get [$listbox curselect]]"
}

set commands {
"Brew Tea"
"Teas In Stock"
"Teas Out Stock"
"All Teas"
"Free Containers"
"Latest Teas"
}


listbox .com -listvariable commands
bind .com <<ListboxSelect>> [list commandSelected .com]
pack .com


This works, but I have a few questions.
- When executing I get a listbox with commands, but also below them
four empty line. How do I get rid of those?
- When executing a command (instead of just printing it) I should
display another window. But while executing this command I should
not be able to start another command. How would I circumvent that?

--
Cecil Westerhof
Senior Software Engineer
LinkedIn: http://www.linkedin.com/in/cecilwesterhof

Rich

unread,
Jun 26, 2018, 11:34:50 AM6/26/18
to
Cecil Westerhof <Ce...@decebal.nl> wrote:
> In another thread I 'was told' I should use Tk.

Well, in all fairness, it was more of a suggestion....

> This works, but I have a few questions.
> - When executing I get a listbox with commands, but also below them
> four empty line. How do I get rid of those?

man listbox:

[-height height]
Specifies the desired height for the window, in lines. If zero
or less, then the desired height for the window is made just
large enough to hold all the elements in the listbox.

> - When executing a command (instead of just printing it) I should
> display another window. But while executing this command I should
> not be able to start another command. How would I circumvent that?

If your 'command' does not reenter the event loop, then this is
automatic.

Otherwise you need a semaphore variable (simple boolean in this case)
to indicate "already in a command" and ignore further requests to
"enter" until the semaphore is reset.

Rich

unread,
Jun 26, 2018, 11:36:05 AM6/26/18
to
Cecil Westerhof <Ce...@decebal.nl> wrote:
> - When executing a command (instead of just printing it) I should
> display another window. But while executing this command I should
> not be able to start another command. How would I circumvent that?

Also, if you have Tcl/Tk 8.6+, you can also use the "tk busy" feature
to make the window "mute" to clicks/pokes with the mouse, which will
achieve the same purpose.

Cecil Westerhof

unread,
Jun 26, 2018, 12:44:04 PM6/26/18
to
Rich <ri...@example.invalid> writes:

> Cecil Westerhof <Ce...@decebal.nl> wrote:
>> In another thread I 'was told' I should use Tk.
>
> Well, in all fairness, it was more of a suggestion....

That is why I put it between quotes.


>> This works, but I have a few questions.
>> - When executing I get a listbox with commands, but also below them
>> four empty line. How do I get rid of those?
>
> man listbox:
>
> [-height height]
> Specifies the desired height for the window, in lines. If zero
> or less, then the desired height for the window is made just
> large enough to hold all the elements in the listbox.

Aah, OK. I thought that pack made it as small as possible, but I was
wrong. I will just use '-height 0' then.
It works like a charm.


>> - When executing a command (instead of just printing it) I should
>> display another window. But while executing this command I should
>> not be able to start another command. How would I circumvent that?
>
> If your 'command' does not reenter the event loop, then this is
> automatic.
>
> Otherwise you need a semaphore variable (simple boolean in this case)
> to indicate "already in a command" and ignore further requests to
> "enter" until the semaphore is reset.

I will be in an event loop because of the other window I think.
So I probably should do something like:
proc commandSelected {listbox} {
global executingCommand

puts "Entering commandSelected"
if {!${executingCommand}} {
set executingCommand True
puts "Command: [$listbox get [$listbox curselect]]"
set executingCommand False
}
puts "Leaving commandSelected"
}

And initialise executingCommand with False.

Maybe it is also a good idea to deselect the command at the end of the proc.

Rich

unread,
Jun 26, 2018, 12:55:58 PM6/26/18
to
Cecil Westerhof <Ce...@decebal.nl> wrote:
> Rich <ri...@example.invalid> writes:
>
>> Cecil Westerhof <Ce...@decebal.nl> wrote:
>>> In another thread I 'was told' I should use Tk.
>>
>> Well, in all fairness, it was more of a suggestion....
>
> That is why I put it between quotes.
>
>
>>> This works, but I have a few questions.
>>> - When executing I get a listbox with commands, but also below them
>>> four empty line. How do I get rid of those?
>>
>> man listbox:
>>
>> [-height height]
>> Specifies the desired height for the window, in lines. If zero
>> or less, then the desired height for the window is made just
>> large enough to hold all the elements in the listbox.
>
> Aah, OK. I thought that pack made it as small as possible, but I was
> wrong. I will just use '-height 0' then. It works like a charm.

Yes, but 'pack' gets the size to use from the widget. And in this case
the widget's default is to reserve enough space for eight lines of
text, even if they are empty. So the widget told pack a size, pack
used that size, but the size was not what you were expecting to see.

>>> - When executing a command (instead of just printing it) I should
>>> display another window. But while executing this command I should
>>> not be able to start another command. How would I circumvent
>>> that?
>>
>> If your 'command' does not reenter the event loop, then this is
>> automatic.
>>
>> Otherwise you need a semaphore variable (simple boolean in this
>> case) to indicate "already in a command" and ignore further requests
>> to "enter" until the semaphore is reset.
>
> I will be in an event loop because of the other window I think.

Yes, but while your 'command' script is running, you are "out" of the
event loop. You have to return from the 'command' script to reenter
the event loop.

But if you want to pop up a new window and interact with the new
window, then you do need to reenter the event loop, at which point if
you want to block things you do have to handle it yourself.

> So I probably should do something like:
> proc commandSelected {listbox} {
> global executingCommand
>
> puts "Entering commandSelected"
> if {!${executingCommand}} {
> set executingCommand True
> puts "Command: [$listbox get [$listbox curselect]]"
> set executingCommand False
> }
> puts "Leaving commandSelected"
> }
>
> And initialise executingCommand with False.

That will work.

> Maybe it is also a good idea to deselect the command at the end of
> the proc.

Two other alternatives are:

1) If the second window always has the same name, you can check for
it's existence at the start of the 'command' and return if it already
exists. You then do need to destroy it when the first invocation
completes for this to work right.

2) At the start of the command, remove the 'bind' script that triggers
the command, then at the end of the command, reinsert the 'bind' script
that triggers the command.

Cecil Westerhof

unread,
Jun 26, 2018, 12:59:04 PM6/26/18
to
That is even better.

Cecil Westerhof

unread,
Jun 26, 2018, 1:28:06 PM6/26/18
to
Rich <ri...@example.invalid> writes:

>>>> - When executing a command (instead of just printing it) I should
>>>> display another window. But while executing this command I should
>>>> not be able to start another command. How would I circumvent
>>>> that?
>>>
>>> If your 'command' does not reenter the event loop, then this is
>>> automatic.
>>>
>>> Otherwise you need a semaphore variable (simple boolean in this
>>> case) to indicate "already in a command" and ignore further requests
>>> to "enter" until the semaphore is reset.
>>
>> I will be in an event loop because of the other window I think.
>
> Yes, but while your 'command' script is running, you are "out" of the
> event loop. You have to return from the 'command' script to reenter
> the event loop.
>
> But if you want to pop up a new window and interact with the new
> window, then you do need to reenter the event loop, at which point if
> you want to block things you do have to handle it yourself.

And how do I reenter the event loop? For example when I select
'Brew Tea' I want to display a window with teas to choose from.


> Two other alternatives are:
>
> 1) If the second window always has the same name, you can check for
> it's existence at the start of the 'command' and return if it already
> exists. You then do need to destroy it when the first invocation
> completes for this to work right.

No, there are several commands and I think it best to have a seperate
window for each.


> 2) At the start of the command, remove the 'bind' script that triggers
> the command, then at the end of the command, reinsert the 'bind' script
> that triggers the command.

That would also be neat.

Robert Heller

unread,
Jun 26, 2018, 2:54:34 PM6/26/18
to
At Tue, 26 Jun 2018 19:20:03 +0200 Cecil Westerhof <Ce...@decebal.nl> wrote:

>
> Rich <ri...@example.invalid> writes:
>
> >>>> - When executing a command (instead of just printing it) I should
> >>>> display another window. But while executing this command I should
> >>>> not be able to start another command. How would I circumvent
> >>>> that?
> >>>
> >>> If your 'command' does not reenter the event loop, then this is
> >>> automatic.
> >>>
> >>> Otherwise you need a semaphore variable (simple boolean in this
> >>> case) to indicate "already in a command" and ignore further requests
> >>> to "enter" until the semaphore is reset.
> >>
> >> I will be in an event loop because of the other window I think.
> >
> > Yes, but while your 'command' script is running, you are "out" of the
> > event loop. You have to return from the 'command' script to reenter
> > the event loop.
> >
> > But if you want to pop up a new window and interact with the new
> > window, then you do need to reenter the event loop, at which point if
> > you want to block things you do have to handle it yourself.
>
> And how do I reenter the event loop? For example when I select
> 'Brew Tea' I want to display a window with teas to choose from.

The most trivial way is for the event processing function to simply return
(possibly setting a flag saying "command foo is working now" -- eg your
busy-wait flag). Otherwise calling any of the event-based "sleep" functions,
like after or tkwait. tkwait in partitular if you want to regain control in
the event processing function once whatever processing has completed:

proc somefunctionboundtoanevent {} {
toplevel .command
# put stuff in the toplevel
tkwait window .command;# wait for the .command window to be destroyed
# do some cleanup, etc.
}


OR

proc somefunctionboundtoanevent {} {
toplevel .command
wm protocol .command WM_DELETE_WINDOW {};# Make .command undeletable with
# the "x" WM button -- this prevents the window from being deleted without
# anybody ever setting the done flag -- the program will basically hang.
#
# put stuff in the toplevel
# (just putting in a couple of buttons)
button .command.ok -command {set ::done 1} -text "OK"
pack .command.ok
button .command.cancel -command {set ::done -1} -text "Cancel"
pack .command.cancel
set ::done 0
tkwait variable ::done
if {$::done == 1} {
## Handle OK
} elseif {$::done == -1} {
## Handle Cancel
}
destroy .command
}

Note: *important!* the variable waited on is a global, here explicitly
specificed with the empty namespace prefix (::).

>
>
> > Two other alternatives are:
> >
> > 1) If the second window always has the same name, you can check for
> > it's existence at the start of the 'command' and return if it already
> > exists. You then do need to destroy it when the first invocation
> > completes for this to work right.
>
> No, there are several commands and I think it best to have a seperate
> window for each.
>
>
> > 2) At the start of the command, remove the 'bind' script that triggers
> > the command, then at the end of the command, reinsert the 'bind' script
> > that triggers the command.
>
> That would also be neat.
>

--
Robert Heller -- 978-544-6933
Deepwoods Software -- Custom Software Services
http://www.deepsoft.com/ -- Linux Administration Services
hel...@deepsoft.com -- Webhosting Services

Rich

unread,
Jun 26, 2018, 2:56:33 PM6/26/18
to
Cecil Westerhof <Ce...@decebal.nl> wrote:
> Rich <ri...@example.invalid> writes:
>
>>>>> - When executing a command (instead of just printing it) I should
>>>>> display another window. But while executing this command I should
>>>>> not be able to start another command. How would I circumvent
>>>>> that?
>>>>
>>>> If your 'command' does not reenter the event loop, then this is
>>>> automatic.
>>>>
>>>> Otherwise you need a semaphore variable (simple boolean in this
>>>> case) to indicate "already in a command" and ignore further requests
>>>> to "enter" until the semaphore is reset.
>>>
>>> I will be in an event loop because of the other window I think.
>>
>> Yes, but while your 'command' script is running, you are "out" of the
>> event loop. You have to return from the 'command' script to reenter
>> the event loop.
>>
>> But if you want to pop up a new window and interact with the new
>> window, then you do need to reenter the event loop, at which point if
>> you want to block things you do have to handle it yourself.
>
> And how do I reenter the event loop? For example when I select
> 'Brew Tea' I want to display a window with teas to choose from.

The easiest, and least likely to cause other issues, is to simply
"return" from the command that was called by the event loop.

You often end up (if you create a typical event driven system) with a bunch
of little procs for each bit of your flow, with your control flow
spread out among the procs.

If you want a linear looking control flow, then you need 8.6, and you
can use coroutines to be able to jump out of the middle of a proc but
hop right back in later when a button is pushed or something else
happens in the GUI as if you had never left.

Search the wiki for the articles on coroutines and GUI's and the event
loop if you want to look in this direction.

>> Two other alternatives are:
>>
>> 1) If the second window always has the same name, you can check for
>> it's existence at the start of the 'command' and return if it already
>> exists. You then do need to destroy it when the first invocation
>> completes for this to work right.
>
> No, there are several commands and I think it best to have a seperate
> window for each.

In which case if you want any one to block the others then either the
global boolean or the #2 item below is likely going to work out better.

>> 2) At the start of the command, remove the 'bind' script that triggers
>> the command, then at the end of the command, reinsert the 'bind' script
>> that triggers the command.
>
> That would also be neat.

You'd do something like this (partial psudeo-code below):

proc called-command {...} {
set old_binding [bind .window_name <<ListboxSelect>>]
bind .window_name <<ListboxSelect>> ""
... body goes here
bind .window_name <<ListboxSelect>> $old_binding
}

Rich

unread,
Jun 26, 2018, 2:58:22 PM6/26/18
to
Cecil Westerhof <Ce...@decebal.nl> wrote:
> Rich <ri...@example.invalid> writes:
>
>> Cecil Westerhof <Ce...@decebal.nl> wrote:
>>> - When executing a command (instead of just printing it) I should
>>> display another window. But while executing this command I should
>>> not be able to start another command. How would I circumvent that?
>>
>> Also, if you have Tcl/Tk 8.6+, you can also use the "tk busy" feature
>> to make the window "mute" to clicks/pokes with the mouse, which will
>> achieve the same purpose.
>
> That is even better.

Yes, it works really well for creating 'modality' without the hassle of
having to actually create it manually.

You do have to remember to "unbusy" the window when done, lest you be
left with a "mute" GUI. But that's no different than remembering to
set a globalLock variable to false when done.

Christian Gollwitzer

unread,
Jun 27, 2018, 1:38:33 AM6/27/18
to
Hi Cecil,

Am 26.06.18 um 17:17 schrieb Cecil Westerhof:
> In another thread I 'was told' I should use Tk. I am not used to
> programming GUI's, but that does not mean I should not start with it.
> A GUI can certainly be an advantage.
>
> - When executing a command (instead of just printing it) I should
> display another window. But while executing this command I should
> not be able to start another command. How would I circumvent that?

nice to see that you are investigating GUIs. I don't really know what
the whole program is doing, but I have the feeling you might rethink
your overall design when it comes to GUI.

The stereotypical programs you have shown earlier (ask for command, then
ask for which tea, then, ...) is one of the worst possible interfaces
especially because the user has no way to go back, revise a choice, if
they make a mistake, they need to start over and type everything afresh.

Therefore, in a GUI, it usually works differently. Mostly you have all
the settings on the screen at once, in a single window. You can freely
adjust the entries and then there is a button "Run", "Do it" or whatever
to actually start the action and another button to abort the action (or
clicking the x in the window bar).

Since I haven't the whole program, the following advice might be
misguided, but my feeling is: YOu should have one window, where you can
select the "command" and then set the options. This is typically done
such that the left side features a listbox and the right side displays a
varying panel of other entries etc. which are packed/unpacked when you
click the "command". CLicking on a different command than the current
one means "abort". If you want to do such a widget as lazily as
possible, you could use a ttk::notebook, which pages other
widgets/frames in and out.

You were given answers how to wait on a window (so called "modal dialog"
in GUI speak), which is fine, but honestly in several years experience
designing GUIs we always tried to *avoid* modal dialogs. Only for very
standard stuff like prompting for a filename it makes sense. Otherwise
it linearises the program flow too much IMHO.

Christian




Cecil Westerhof

unread,
Jun 27, 2018, 3:28:05 AM6/27/18
to
Thanks. Is good advice. I think I go for the notebook version. The
current program does not much: choose tea to brew, show teas in stock,
show teas out of stock, show all teas, free containers and the latest
drank teas.
Is a good start to for GUI's I think. And after this I can start
making other programs with a GUI.

Donald Arseneau

unread,
Jul 6, 2018, 2:39:43 AM7/6/18
to
Cecil Westerhof <Ce...@decebal.nl> writes:

> - When executing a command (instead of just printing it) I should
> display another window. But while executing this command I should
> not be able to start another command. How would I circumvent that?

I would probably put

.com configure -state disabled

in the proc that displays the other "execution" window.
Also make sure to do

.com configure -state normal

when the command processing is finished, or its window is closed.

There are other options too, like making the execution window be
"transient" with

toplevel .exec
wm transient .exec .com
.com configure -state disabled



--
Donald Arseneau as...@triumf.ca
0 new messages