tinycobol/tcltk84/tk8.4/wcb3.1/doc/wcb.html

717 lines
26 KiB
HTML

<html>
<head>
<title>Wcb Programmer's Guide</title>
<meta name="Author" content="Csaba Nemethi">
<meta name="Keywords" content="callback, widget, Tk entry, tile entry,
BWidget Entry, spinbox, tile combobox, listbox, tablelist, text, ctext">
<style type="text/css">
<!--
pre {color: #006400}
-->
</style>
</head>
<body bgcolor=#ffffff>
<div align=center>
<h1>Wcb Programmer's Guide</h1>
<h3>by</h3>
<h2>Csaba Nemethi</h2>
<address>
<a href="mailto:csaba.nemethi@t-online.de">csaba.nemethi@t-online.de</a>
</address>
</div>
<p><hr>
<a name="contents"></a><h2>Contents</h2>
<h4><a href="#overview">Overview</a></h4>
<ul>
<li><a href="#ov_problems">Some common problems</a>
<li><a href="#ov_wcb">The Wcb package </a>
<li><a href="#ov_get">How to get it?</a>
<li><a href="#ov_install">How to install it?</a>
<li><a href="#ov_use">How to use it?</a>
</ul>
<h4><a href="#examples">Examples</a></h4>
<ul>
<li><a href="#ex_entry">Some before-<code>insert</code> callbacks for entry
widgets</a>
<li><a href="#ex_listbox1">A <code>selset</code> callback for a listbox</a>
<li><a href="#ex_listbox2">An <code>activate</code> callback for a listbox</a>
<li><a href="#ex_text">Seven callbacks for a text widget</a>
</ul>
<div align=center>
<p><a href="index.html">Start page</a>
</div>
<p><hr>
<h2><a name="overview"></a>Overview</h2>
<h3><a name="ov_problems"></a>Some common problems</h3>
<p>Many Tcl/Tk programmers are confronted with questions like the following:
<ul>
<li>How to restrict the set of characters that the user can type or paste
into a Tk entry, tile entry, BWidget Entry, spinbox, tile combobox, text
or ctext widget?
<li>How to manipulate the user input characters before they are inserted into
one of these widgets?&nbsp; In the case of a text or ctext widget:&nbsp;
How to change the font, colors, or other attributes of the input
characters?
<li>How to set a limit for the number of characters that can be typed or
pasted into a Tk entry, tile entry, BWidget Entry, spinbox, or tile
combobox widget?
<li>How to protect some parts of the text contained in a Tk entry, tile
entry, BWidget Entry, spinbox, tile combobox, text, or ctext widget from
being changed by the user?
<li>How to define notifications to be triggered automatically after text is
inserted into or deleted from one of these widgets?
<li>How to define some actions to be invoked automatically whenever the
insertion cursor in a Tk entry, tile entry, BWidget Entry, spinbox, tile
combobox, text, or ctext widget is moved?
<li>How to define a command to be called automatically when selecting a
listbox element, a tablelist row or cell, or a range of characters in a
text or ctext widget?
<li>How to protect any or all items of a listbox or a range of characters in
a text or ctext widget from being selected?
</ul>
<p>In most books, FAQs, newsgroup articles, and widget sets, you can find
<i>individual</i> solutions to some of the above problems by means of widget
bindings.&nbsp; This approach is based on adding new binding tags or modifying
some of the existing ones, which quite often proves to be incomplete.
<p>The Tk core addresses just a few of the above problems:&nbsp; In Tk 8.1 the
<code>&lt;&lt;ListboxSelect&gt;&gt;</code> virtual event for listbox widgets
was introduced, Tk versions 8.3 and higher support widget options for entry
validation, and the spinbox widget (introduced in Tk 8.4) provides the same
validation facility.&nbsp; Finally, Tk 8.4 supports the <code>disabled</code>
state for listbox widgets, as well as the <code>modified</code> flag, the
<code>&lt;&lt;Modified&gt;&gt;</code> and
<code>&lt;&lt;Selection&gt;&gt;</code> virtual events, and an undo/redo
mechanism for text widgets.&nbsp; However, also these improvements are of
<i>individual</i> nature.
<h3><a name="ov_wcb"></a>The Wcb package</h3>
<p>The <b>W</b>idget <b>c</b>all<b>b</b>ack package Wcb provides a completely
different, <i>general</i> solution to the above problems:&nbsp; Based on
redefining the Tcl command corresponding to a widget, the main Wcb procedure
<code><a href="wcbRef.html#callback">callback</a></code> enables you to
associate arbitrary commands with some Tk entry, tile entry, BWidget Entry,
spinbox, tile combobox, listbox, tablelist, text, and ctext widget
operations.&nbsp; These commands will be invoked automatically in the global
scope whenever the respective widget operation is executed.&nbsp; You can
request that these commands be called either before or after executing the
respective widget operation, i.e., you can define both <b>before-</b> and
<b>after-callbacks</b>.&nbsp; From within a before-callback, you can cancel the
respective widget command by invoking the procedure <code><a href=
"wcbRef.html#cancel">cancel</a></code>, or modify its arguments by calling
<code><a href="wcbRef.html#extend">extend</a></code> or <code><a href=
"wcbRef.html#replace">replace</a></code>.
<p>Besides these (and four other) general-purpose commands, the Wcb package
exports the utility procedures <code><a href=
"wcbRef.html#change">changeEntryText</a></code>, <code><a href=
"wcbRef.html#entrylen">postInsertEntryLen</a></code>, and <code><a href=
"wcbRef.html#entrytext">postInsertEntryText</a></code> for Tk entry, tile
entry, BWidget Entry, spinbox, and tile combobox widgets, as well as some
before-<code>insert</code> callbacks for <a href="wcbRef.html#entrycb">Tk
entry, tile entry, BWidget Entry, spinbox, tile combobox</a>, <a href=
"wcbRef.html#textcb">text, and ctext</a> widgets, which you can use directly
or modify to suit your needs.&nbsp; To learn how to do this, have a look at the
<a href="#examples">Examples</a> section below.
<p>The Wcb package is implemented in pure Tcl/Tk code, which makes it
completely platform-independent and very easy to install.&nbsp; It requires
version 8.0 or higher of both Tcl and Tk.
<h3><a name="ov_get"></a>How to get it?</h3>
<p>Wcb is available for free download from the Web page
<blockquote><address>
<a href="http://www.nemethi.de">http://www.nemethi.de</a>
</address></blockquote>
<p>The distribution file is <code>wcb3.1.tar.gz</code> for UNIX and
<code>wcb3_1.zip</code> for Windows.&nbsp; These files contain the same
information, except for the additional carriage return character preceding the
linefeed at the end of each line in the text files for Windows.
<h3><a name="ov_install"></a>How to install it?</h3>
<p>Install the package as a subdirectory of one of the directories given by the
<code>auto_path</code> variable.&nbsp; For example, you can install it as a
directory at the same level as the Tcl and Tk script libraries.&nbsp; The
locations of these library directories are given by the
<code>tcl_library</code> and <code>tk_library</code> variables, respectively.
<p>To install Wcb <i>on UNIX</i>, <code>cd</code> to the desired directory and
unpack the distribution file <code>wcb3.1.tar.gz</code>:
<blockquote><pre>
gunzip -c wcb3.1.tar.gz | tar -xf -
</pre></blockquote>
<p>This command will create a directory named <code>wcb3.1</code>, with the
subdirectories <code>demos</code>, <code>doc</code>, and <code>scripts</code>.
<p><i>On Windows</i>, use WinZip or some other program capable of unpacking the
distribution file <code>wcb3_1.zip</code> into the directory
<code>wcb3.1</code>, with the subdirectories <code>demos</code>,
<code>doc</code>, and <code>scripts</code>.
<h3><a name="ov_use"></a>How to use it?</h3>
<p>To be able to access the commands and variables defined in the package Wcb,
your scripts must contain one of the lines
<blockquote><pre>
package require Wcb
package require wcb
</pre></blockquote>
<p>You can use either one of the two statements above because the file
<code>wcb.tcl</code> contains both lines
<blockquote><pre>
package provide Wcb ...
package provide wcb ...
</pre></blockquote>
<p>You are free to remove one of these two lines from <code>wcb.tcl</code> if
you want to prevent the package from making itself known under two different
names.&nbsp; Of course, by doing so you restrict the argument of&nbsp;
<code>package require</code>&nbsp; to a single name.&nbsp; Notice that the
<a href="#examples">examples</a> below use the statement&nbsp; <code>package
require Wcb</code>.
<p>Since the package Wcb is implemented in its own namespace called
<code>wcb</code>, you must either invoke the
<blockquote><pre>
namespace import wcb::<i>pattern</i> ?wcb::<i>pattern ...</i>?
</pre></blockquote>
<p>command to import the <i>procedures</i> you need, or use qualified names
like <code>wcb::callback</code>.&nbsp; In the examples below we have chosen the
latter approach.
<p>To access Wcb <i>variables</i>, you <i>must</i> use qualified names.&nbsp;
There are only two Wcb variables that are designed to be accessed outside the
namespace <code>wcb</code>:
<ul>
<li>The variable <code>wcb::version</code> holds the current version number
of the Wcb package.
<li>The variable <code>wcb::library</code> holds the location of the Wcb
installation directory.
</ul>
<div align=center>
<p><a href="#contents">Contents</a>&nbsp;&nbsp;&nbsp;&nbsp;
<a href="index.html">Start page</a>
</div>
<p><hr>
<h2><a name="examples"></a>Examples</h2>
<h3><a name="ex_entry"></a>Some before-<code><font size=4>insert</font></code>
callbacks for entry widgets</h3>
<p>The script <code>entrytest.tcl</code> in the <code>demos</code> directory
creates three entry widgets with the constraints shown in the following figure:
<blockquote>
<p><img src="entrytest.gif" alt="entrytest - Please load this image!"
width=318 height=269>
</blockquote>
<p>For the topmost entry <code>.e1</code> we define two
before-<code>insert</code> callbacks contained in the Wcb package:
<blockquote><pre>
wcb::callback .e1 before insert wcb::checkStrForAlnum \
wcb::convStrToUpper
</pre></blockquote>
<p>To force the second entry <code>.e2</code> to accept only integers of
maximal length 10, we use again two before-<code>insert</code> callbacks from
Wcb:
<blockquote><pre>
wcb::callback .e2 before insert {wcb::checkEntryLen 10} \
wcb::checkEntryForInt
</pre></blockquote>
<p>And finally, here are the two callbacks for the third entry widget
<code>.e3</code>:
<blockquote><pre>
wcb::callback .e3 before insert {wcb::checkEntryLen 10} \
checkNumber
#
# Callback procedure checkNumber
#
proc checkNumber {w idx str} {
set newText [wcb::postInsertEntryText $w $idx $str]
if {![regexp {^[0-9]*\.?[0-9]?[0-9]?$} $newText]} {
wcb::cancel
}
}
</pre></blockquote>
<p>This last example also shows the arguments of the callbacks declared with
the <code><a href="wcbRef.html#callback">wcb::callback</a></code>
command:&nbsp; Whenever a callback is invoked, the name of the original Tcl
command for the widget as well as the arguments of the respective widget
operation are automatically appended to it as parameters.&nbsp; Since we
defined <code>checkNumber</code> as a before-callback for the
<code>insert</code> subcommand, its last three arguments must be: the name of
the original entry widget command (<code>w</code>), the index
(<code>idx</code>), and the string (<code>str</code>) to be inserted just
before the character indicated by the index.
<p>Notice that in the argument list of a Wcb callback, the name of the original
Tcl widget command can be be preceded by any number of additional
arguments.&nbsp; The procedure <code><a href=
"wcbRef.html#entrycb">wcb::checkEntryLen</a></code> is an example of such a
callback.
<p>The command <code><a href=
"wcbRef.html#entrytext">wcb::postInsertEntryText</a></code> invoked in the
procedure <code>checkNumber</code> returns the text that would be contained in
the entry widget <code>w</code> after inserting the string <code>str</code>
before the character indicated by the index <code>idx</code>.&nbsp; If this
text is not (the starting part of) an unsigned real number with at most two
digits after the decimal point, then we call the procedure <code><a href=
"wcbRef.html#cancel">wcb::cancel</a></code>, which aborts the
<code>insert</code> command.
<p>Without the constraint that the content of the third entry must not start
with a sign, we could have used the callback procedure <code><a href=
"wcbRef.html#entrycb">wcb::checkEntryForFixed</a></code> instead of
<code>checkNumber</code>:
<blockquote><pre>
wcb::callback .e3 before insert {wcb::checkEntryLen 10} \
{wcb::checkEntryForFixed 2}
</pre></blockquote>
<h3><a name="ex_listbox1"></a>A <code><font size=4>selset</font></code>
callback for a listbox</h3>
<p>In the case of a listbox, you will probably most often want to define a
callback for the&nbsp; <code>selection set</code>&nbsp; widget
subcommand.&nbsp; In most cases it does not matter whether this is a before- or
after-callback.&nbsp; Please note that the <code><a href=
"wcbRef.html#callback">wcb::callback</a></code> command expects the abbreviated
form <code>selset</code> as parameter.&nbsp; Similarly, you must pass
<code>selclear</code> to this command when defining a callback for the&nbsp;
<code>selection clear</code>&nbsp; listbox operation.
<p>In the following example we build a listbox <code>.lb</code> containing the
names of the bitmap files in the subdirectory <code>images</code> of the
directory <code>demos</code> in the Tk library directory.&nbsp; Whenever an
item is selected, the callback procedure <code>showBitmap</code> will
display the corresponding bitmap in the label <code>.picture</code>.
<blockquote>
<p><img src="listboxtest1.gif" alt="listboxtest1 - Please load this image!"
width=183 height=173>
</blockquote>
<p>Here is the relevant code fragment from the script
<code>listboxtest1.tcl</code>, contained in the <code>demos</code> directory:
<blockquote><pre>
set dirName [file join $tk_library demos images]
#
# Frame .spacer and listbox .lb
#
frame .spacer -width 10
listbox .lb -height 0 -width 0 -background gray98
set pattern [file join $dirName *.bmp]
foreach pathName [lsort [glob $pattern]] {
.lb insert end [file tail $pathName]
}
#
# Label .picture
#
label .picture -relief sunken
#
# Define a before-selset callback for .lb
#
wcb::callback .lb before selset showBitmap
#
# Callback procedure showBitmap
#
proc showBitmap {w first args} {
global dirName
set pathName [file join $dirName [$w get $first]]
.picture configure -bitmap @$pathName
}
</pre></blockquote>
<p>Recall that the&nbsp; <code>selection set</code>&nbsp; listbox operation
takes as arguments one or two indices, which will be passed automatically to
the callback as parameters, along with the name of the original Tcl command
associated with the listbox widget.&nbsp; For this reason, the arguments of the
callback procedure <code>showBitmap</code> are: the name of the original
listbox widget command (<code>w</code>), the first index (<code>first</code>),
as well as the <code>args</code> keyword representing the empty list or the
optional second index passed to the&nbsp; <code>selection set</code>&nbsp;
command.
<h3><a name="ex_listbox2"></a>An <code><font size=4>activate</font></code>
callback for a listbox</h3>
<p>The listbox used in the preceding example has the default selection mode
<code>browse</code>, hence the before-<code>selset</code> callback
<code>showBitmap</code> will be fired every time the mouse is dragged from one
element to another, with button 1 down.&nbsp; But what happens if we want to
display not only the bitmaps but also the photo images contained in the
subdirectory <code>images</code> of the directory <code>demos</code> in the Tk
library directory?&nbsp; Loading a photo image is a much more complex operation
than loading a bitmap, which can have the effect that some images cannot be
displayed quickly enough to follow the mouse when browsing with it within the
listbox.
<blockquote>
<p><img src="listboxtest2.gif" alt="listboxtest2 - Please load this image!"
width=195 height=241>
</blockquote>
<p>To solve this problem, we can either change the selection mode to have the
less common value <code>single</code>, or arrange for the images not to be
displayed when browsing with the mouse but when releasing its button 1.&nbsp;
The second method can be implemented with the aid of an <code>activate</code>
callback, as shown in the following code fragment taken from the script
<code>listboxtest2.tcl</code>, contained in the <code>demos</code> directory:
<blockquote><pre>
set dirName [file join $tk_library demos images]
image create photo photoImage
#
# Frame .spacer and listbox .lb
#
frame .spacer -width 10
listbox .lb -height 0 -width 0 -background gray98
set pattern [file join $dirName *]
foreach pathName [lsort [glob $pattern]] {
.lb insert end [file tail $pathName]
}
#
# Label .picture
#
label .picture -relief sunken
#
# Define a before-activate callback for .lb
#
wcb::callback .lb before activate showPicture
#
# Callback procedure showPicture
#
proc showPicture {w idx} {
set leafName [$w get $idx]
#
# When traversing the listbox with the arrow keys, the value
# of idx can become -1 or the number of listbox elements,
# hence the value of leafName can be an empty string:
#
if {[string compare $leafName ""] == 0} {
return ""
}
global dirName
set pathName [file join $dirName $leafName]
if {[string compare [file extension $pathName] ".bmp"] == 0} {
.picture configure -bitmap @$pathName -image ""
} else {
photoImage configure -file $pathName
.picture configure -bitmap "" -image photoImage
}
}
</pre></blockquote>
<h3><a name="ex_text"></a>Seven callbacks for a text widget</h3>
<p>The script <code>texttest1.tcl</code> in the <code>demos</code> directory
creates the text widget shown in the following figure:
<blockquote>
<p><img src="texttest1.gif" alt="texttest1 - Please load this image!"
width=366 height=260>
</blockquote>
<p>Here is the relevant code fragment:
<blockquote><pre>
#
# Text .txt
#
set width 50
text .txt -width $width -height 12 -setgrid true -wrap none -background white \
-font "Courier -12"
.txt tag configure prog -foreground red
.txt tag configure user -foreground DarkGreen
.txt insert end "Everything you type or paste into this window will\n" prog
.txt insert end "be displayed in dark green. You cannot make any\n" prog
.txt insert end "changes or selections in this red area, and will\n" prog
.txt insert end "not be able to send the message as long as any\n" prog
.txt insert end "line contains more than $width characters.\n" prog
.txt insert end "--------------------------------------------------\n" prog
set limit [.txt index insert]
#
# Label .pos displaying the current cursor position
#
label .pos -textvariable pos
#
# Button .send (actually, it does not send anything)
#
button .send -text Send -command exit
#
# Define 5 before- and 2 after-callbacks for .txt
#
wcb::callback .txt before insert protectRedArea changeColor
wcb::callback .txt before delete protectRedArea
wcb::callback .txt before selset protectRedArea
wcb::callback .txt before motion displayPos
wcb::callback .txt after insert "checkLines $width"
wcb::callback .txt after delete "checkLines $width"
#
# Callback procedure protectRedArea
#
# The parameters following w can be interpreted either as
# "index string ?tagList string tagList ...?" (for an insert
# callback), or as "from ?to?" (for a delete callback),
# or as "from ?to from to ...?" (for a selset callback).
#
proc protectRedArea {w idx args} {
global limit
if {[$w compare $idx &lt; $limit]} {
wcb::cancel
}
}
#
# Callback procedure changeColor
#
proc changeColor {w args} {
wcb::extend user
}
#
# Callback procedure displayPos
#
proc displayPos {w idx} {
set index [$w index $idx]
scan $index "%d.%d" line column
incr column
global pos
set pos [format "Line: %d Column: %d" $line $column]
}
#
# Callback procedure checkLines
#
# The parameter args can be interpreted both as "index
# string ?tagList string tagList ...?" (for an insert
# callback) and as "from ?to?" (for a delete callback).
#
proc checkLines {maxCharsPerLine w args} {
#
# Display the new cursor position
#
displayPos $w insert
#
# Disable or enable the .send button
#
scan [$w index end] "%d" lastLine
for {set line 1} {$line &lt; $lastLine} {incr line} {
scan [$w index $line.end] "%d.%d" dummy charsInLine
if {$charsInLine &gt; $maxCharsPerLine} {
.send configure -state disabled
return ""
}
}
.send configure -state normal
}
. . .
displayPos .txt insert
focus .txt
</pre></blockquote>
<p>The procedure <code>protectRedArea</code> is a before-<code>insert</code>,
before-<code>delete</code>, and before-<code>selset</code> callback.&nbsp; It
checks whether the attempted change would affect the text area displayed in
red; if this is the case, it calls the procedure <code><a href=
"wcbRef.html#cancel">wcb::cancel</a></code>, which aborts the
<code>insert</code>, <code>delete</code>, or&nbsp; <code>tag add
sel</code>&nbsp; command, respectively.
<p>The before-<code>insert</code> callback <code>changeColor</code> invokes the
<code><a href="wcbRef.html#extend">wcb::extend</a></code> command to append the
<code>user</code> tag to the argument list of the <code>insert</code> command,
thus changing the foreground color of the characters entered by the user to
<code>DarkGreen</code>.
<p>The procedure <code>displayPos</code> displays the line and column
corresponding to the index passed to it as its second argument.&nbsp; This
index will be the target position of the insertion cursor when the procedure is
triggered automatically as a before-<code>motion</code> callback.&nbsp; As seen
in the <code>checkLines</code> procedure discussed below, it is also invoked
after performing an <code>insert</code> or <code>delete</code> operation; in
that case, its second argument will be the new position of the insertion cursor
after the execution of <code>insert</code> or <code>delete</code>.&nbsp; In
this way, we are able to keep track completely of the position of the insertion
cursor.
<p>It is interesting to see what happens if we register <code>displayPos</code>
as an after- instead of before-<code>motion</code> callback.&nbsp; Well, in
that case the procedure would have to ignore its second argument and we would
have to replace the line
<blockquote><pre>
set index [$w index $idx]
</pre></blockquote>
<p>with
<blockquote><pre>
set index [$w index insert]
</pre></blockquote>
<p>The reason is that the value of the <code>idx</code> argument passed to
<code>displayPos</code> can be, for instance, <code>insert+1c</code>, where
<code>insert</code> means the position of the insertion cursor <i>before</i>
moving it forward by one character.&nbsp; The after-<code>motion</code>
callback is, however, triggered <i>after</i> the insertion cursor has been
moved, and at that time the <code>insert</code> mark already points to the new
cursor position.&nbsp; For this reason,&nbsp; <code>[$w index
$idx]</code>&nbsp; is not adequate to retrieve the position of the insertion
cursor within an after-<code>motion</code> callback.
<p>Our last procedure <code>checkLines</code> is both an
after-<code>insert</code> and after-<code>delete</code> callback.&nbsp; After
calling <code>displayPos</code> to display the new cursor position, it disables
or enables the <code>.send</code> button, depending upon whether any line of
the text widget contains more than <code>$maxCharsPerLine</code> characters.
<p>Instead of just disabling the <code>.send</code> button if any line has more
than <code>$maxCharsPerLine</code> characters, we can even prevent the user
from entering lines that are longer than allowed.&nbsp; The script
<code>texttest2.tcl</code> in the <code>demos</code> directory shows how this
can be achieved by combining widget callbacks with the undo mechanism for text
widgets, introduced in Tk 8.4.&nbsp; This script creates the text widget shown
in the following figure:
<blockquote>
<p><img src="texttest2.gif" alt="texttest2- Please load this image!"
width=366 height=260>
</blockquote>
<p>Most of the code contained in the script <code>texttest2.tcl</code> is
identical to the one in the previous example.&nbsp; The main difference is a
new line activating the undo mechanism for the text widget <code>.txt</code>
and a new version of the callback procedure <code>checkLines</code>:
<blockquote><pre>
. . .
.txt configure -undo yes
. . .
proc checkLines {maxCharsPerLine w args} {
#
# Undo the last insert or delete action if necessary
#
scan [$w index end] "%d" lastLine
for {set line 1} {$line &lt; $lastLine} {incr line} {
scan [$w index $line.end] "%d.%d" dummy charsInLine
if {$charsInLine &gt; $maxCharsPerLine} {
$w edit undo
bell
break
}
}
#
# Clear the undo and redo stacks, and display the new cursor position
#
$w edit reset
displayPos $w insert
}
. . .
</pre></blockquote>
<p>This version of the callback <code>checkLines</code> undoes the last edit
action if any text line contains more characters than the allowed
maximum.&nbsp; For this reason, we had to move the invocation of the
<code>displayPos</code> procedure to the end of the callback, because the&nbsp;
<code>edit undo</code>&nbsp; command might change the position of the insertion
cursor.
<p>Note that we could have implemented this example also without making use of
the undo mechanism for text widgets, by saving the last accepted contents of
the widget, along with the cursor position, and restoring them in case any line
gets longer than allowed.&nbsp; The resulting script would then run also with
Tk versions earlier than 8.4.
<div align=center>
<p><a href="#contents">Contents</a>&nbsp;&nbsp;&nbsp;&nbsp;
<a href="index.html">Start page</a>
</div>
</body>
</html>