本章主要用一系列脚本介绍 tcl 和 tk, 其目的在于展示 tck 和 tk 的整体结构,以及它们能够做到什么。
1. 入门
tcl 命令
命令类似于 shell 中的命令,接收参数然后返回结果。每一条 tcl 命令都会返回一个结果。如果该命令没有有意义的结果,他会返回一个空字符串。
例如: expr 命令
expr 14.1 * 6 * sin(.2)
expr rand()
# 和 C 语言一样,布尔型结果用数值表达,1 表示真,0 表示假
expr (3 > 4) || (6 <= 7)
tk 界面
button .b -text "Hello" -command "puts Good-bye!; exit"
pack .b
tk 中构建图形用户界面的基本单元是组件(widget), 一个组件就是一个有特定外观和行为的窗口。
组件是分层组织的例如 .a 代表主组件的一个子组件, .a.b.c 代表 .a.b 的一个子组件。
变量与替换
set a 1.5
set b [expr $a * 4]
puts $b
变量替换: tcl 将 $
符和它后面的变量名替换为改变量的值,因此执行 expr $a * 4
时, expr
接收的参数实际是 1.5 * 4
变量值总是可以用字符串表示,但可能是以原生二进制形式保存的
命令替换: 使用 [ ] 可以将一条命令的执行结果作为另一条命令的输入参数
控制结构
tcl 过程 (procedure)
# 计算阶乘
proc factorial {val} {
set result 1
while {$val > 0} {
set result [expr $result * $val]
# incr 命令将对整型变量 val 加上 -1
incr val -1
}
return $result
}
puts [factorial 3]
tcl 中大括号和双引号所包含的内容都可以嵌入空格,但是2点不同
- 大括号可嵌套
- 大括号里的内容会原封不动传给命令,不会发生替换
proc 命令获取三个参数,过程名称,用空格分隔的参数名列表以及过程块
proc 会把过程名称作为一个新命令加入到 tcl 解释器
括号替换原则
在编写Tcl脚本时一个很困难的问题就是“替换”,在你需要它的时候保证它发生,在你不需要它的时候保证它不发生。
过程块必须用大括号括起来,是因为当过程块作为一个参数传递给proc时,我们不希望发生变量替换或命令替换,我们希望这些替换在过程块作为Tcl脚本处理的时候发生。
while命令块用大括号括起来的原因是一样的:我们不是希望替换在解析while命令的时候发生一次,而是希望每次while循环处理循环块的时候都进行替换。
while的参数{$val>0}也需要这个大括号。否则变量val的值只在解析while命令时替换一次,那样这个while就会陷入死循环。你可以试验一下把这个示例中的大括号用双引号代替,看看会出现什么情况。
例如
set a 12
set b "$a"
set c {$a}
puts $b
puts $c
局部变量
过程中的变量为局部变量,包括参数 (参数传入方式为复制其值)
2. 关于 tcl 语言
tcl 没有确定的语法来解释整个语言,其包括的只是十多条用于解析参数和执行替换的简单原则。
Tcl脚本的具体行为是由所执行的命令确定的。命令决定了应该把一个参数视为它所代表的值,还是变量名,或是要执行的代码块等。
例如
while {$val > 0} {
set result [expr $result * $val]
incr val -1
}
在处理这个命令时,Tcl解释器只知道这个命令有三个词,其中第一个是命令名。Tcl解释器并不知道wile的第一个输入参数是表达式,第二个是Tcl脚本。
完成对这个命令的解析之后,Tcl解释器会把这个命令中的参数单词都传给while,然后wile命令会把第一个参数作为表达式,把第二个参数作为Tcl脚本处理。
如果表达式的值非零,那么while就会把第二个参数传回Tcl解释器进行处理。到了这一步,解释器就会把这第二个参数作为脚本对待(即执行命令,进行变量替换,调用expr、set以及incr命令
现在考虑下面示例
set {$val > 0} {
set result [expr $result * $val]
incr val -1
}
对于Tcl解释器来说,set命令与while命令唯一的不同就是它们的命令名不同
解释器处理这个命令的方式与while命令一样,唯一的不同之处是执行命令时调用的过程不同
set命令将其第一个参数作为变量名,第二个参数作为变量的值,因此,这里它会设置一个变量,变量名为$val>0
又因为 set 把第二个参数作为变量值,并没有作为脚本传回解释器执行,因此最终该变量值会原封不动的等于括号里的内容
倘若括号里的内容是引号而不是大括号,那么tcl在处理这条命令时,会先发生替换才传给 set
这样做的一个有意义的结果是,脚本可以为命令定义全新的控制结构,这一功能是大多数语言不具备的。
3. 事件绑定
考虑下面这个示例
proc factorial {val} {
set result 1
while {$val > 0} {
set result [expr $result * $val]
incr val -1
}
return $result
}
entry .value -width 6 -relief sunken -textvariable value
label .description -text "factorial is"
label .result -textvariable result
button .calculate -text "Calculate" \
-command {set result [factorial $value]}
bind .value <Return> {
.calculate flash
.calculate invoke
}
grid .value .description .result -padx 1m -pady 1m
grid .calculate - - -padx 1m -pady 1m
bind 命令有三个参数: 组件名, 事件说明, 以及指定组件中指定事件发生时调用的 tcl 脚本
事件说明符表如下:
事件说明符 | 含义 |
---|---|
<Button-1> | 按下 1 号鼠标键 |
<1> | <Button-1> 的简写 |
<ButtonRelease-1> | 释放 1 号鼠标键 |
<Double-Button-1> | 双击 1 号鼠标键 |
<Key-a> | 按下 a 键 |
<a> 或 a | <Key-a> 的简写 |
<Motion> | 鼠标移动了, 无论按键是否有按下 |
<B1-Motion> | 1 号键按下时鼠标移动了 |
进行绑定的脚本会访问有关事件的各方面信息,例如当事件发生时鼠标所在的位置
bind . <Motion> {puts "pointer at %x,%y"}
创建一个组件时,一个与该组件同名的 tcl 命令也就创建了,你可以调用这个命令来与该组件通信。
组件命令的第一个参数是某一个选项,后面的其他参数是该选项所需的变量。这个脚本中第一条组件命令是让按钮闪烁。(您不一定能看到按钮闪烁,这和计算机系统的显示方案有关。)
第二条组件命令让按钮组件调用它自己的-command选项,就如同您用鼠标单击按钮一样,从而实现回车后执行阶乘计算。
不同类型的组件的组件命令支持不同的选项集,但不同类型之间也有很多选项是相似的。
例如,所有的组件都支持一个组件命令configure,用来修改组件的设置选项。
# 将输入组件背景改为黄色
.value configure -background yellow
# 让用户界面这个按钮无法响应
.calculate configure -state disbale