本文主要涉及如下内容:

  • 有关 Ruby/Tk 的基本概念
  • Ruby/Tk 开发环境的准备
  • "你好!中国"示例程序
  • 小部件
  • 事件处理
  • 绘图支持
  • 滚动条支持
  • 布局管理
  • Ruby/Tk 扩展

有关 Ruby/Tk 的基本概念

什么是 Ruby?
Ruby 是一种开源的动态编程语言,它素以“简单、高效”而著称。了解更多关于 Ruby 的知识,请参考Ruby 中文网

什么是 Tcl?
Tcl(Tool Command Language)是一种强大的、简单的、易学易用的动态编程语言。Tcl 是开源的,并且可以跨平台运行。您可以从Tcl/Tk 漫谈中对 Tcl 有更进一步了解。

什么是 Tk?
Tk(Tool Kit)是基于 Tcl 的图形程序开发工具箱,目前 Perl、Python、Ruby 以及 Common Lisp 等多种语言都有对它的支持。用 Tk 构建的图形用户界面可以在 Linux、Unix、Apple Macintoch、Windows 等平台上运行。

什么是 Ruby/Tk?
Ruby/Tk 是一个重要的 Ruby 扩展,它为开发人员使用 Ruby 进行 Tk 程序开发提供了接口。Ruby 的主发行包中包含 Ruby/Tk,也就是说,您不需要安装额外的 Ruby 扩展就可以进行 Ruby/Tk 程序开发(当然,您的系统仍然需要安装 Tk)。

Ruby/Tk 开发环境的准备

下载并安装 Ruby
目前最新的稳定版本是 1.8.6,您可以从 Ruby 的官方网站 上下载安装包,并将它安装在您喜欢目录当中。

下载并安装 Tcl/Tk
在绝大多数的 Linux 发行版中,Tcl/Tk 都是标准选项。另外, ActiveState 提供了所有常见平台上的 Tcl/Tk 二进制安装包。当然,如果您感兴趣的话,也可以从SourceForge上自己下载源代码进行编译。

关于开发工具
目前市面上有很多种 Ruby 开发工具,比如 SciTE(Ruby 发行包中自带的开发工具)、 RDT (一个用于 Ruby 开发的 eclipse 的插件)、 FreeRIDE jEdit Ruby 插件 Arachno Ruby IDE 以及 ActiveState 的 Komodo 等等。您可以根据自己的喜好选择其中任意一种作为您的 Ruby 开发工具。

"你好!中国"示例程序


图 1. “你好!中国”示例程序
你好!中国

列表 1. “你好!中国”示例程序

                
require 'tk'

msg="你好!中国"
root=TkRoot.new{title msg}
label_msg=TkLabel.new(root){
 	text  msg
    pack :padx=>2,:pady=>2,:side=>'top'
    font "arial 20 bold"
}

Tk.mainloop
			

 

正如您所看到的,Ruby/Tk 程序相当的简洁 - 首先,您需要加载 Ruby/Tk 扩展(require 'tk'语句),接着通过语句“TkRoot.new”构造一个主窗口对象,然后向这个主窗口对象添加需要的小部件,最后调用“Tk.mainloop” 启动主事件循环以创建整个图形界面。

小部件

Ruby/Tk 提供了很多的类来实现不同的小部件。Ruby/Tk 小部件的命名规范遵循一个统一的原则:将 Tk 小部件名称之前加上"Tk"二字。例如,Tk 中的 Label 由 Ruby/Tk 中的 TkLabel 类实现,而 Button 由 TkButton 类实现。Ruby/Tk 小部件由 new 方法创建,并且要指定父对象。显式地指定父对象是一个好习惯。如果父对象没有指定,默认为根对象,也就是 TkRoot 的实例。

设置小部件属性

在构造小部件的时候一般都需要设置属性。在 Ruby/Tk 中,设置属性的方式通常有两种:一种是如“你好!中国”示例程序所示,通过一个代码块来设置属性;另一种是用哈希表的方式。例如,“你好!中国”示例中的 TkLabel 用哈希表的方式设置属性的代码如下:


列表 2. 用哈希表的方式设置属性

                
TkLabel.new(root,'text'=>msg).pack('padx'=>2,'pady'=>2,'side'=>'top')
			

 

命令绑定
Ruby/Tk 小部件通过给 command 指定一个 Proc 对象来实现交互,比如 TkButton。您可以通过下面两种方式构造一个退出系统的按钮。


列表 3. 给 command 属性指定命令(方式1)

                    
TkButton.new(root){
     text "退出"
     pack
     command{
        exit
     }
}
			



列表 4. 给 command 属性指定命令(方式 2)

                    
exit_btn=TkButton.new(root){
     text "退出"
     pack     
}
exit_btn.command=lambda{exit}
			

 

请注意,在“方式 2”中用到了一个方法 lambda,通过它能够生成显式的 Proc 对象。

变量绑定

通过 TkVariable 代理,可以将一个变量绑定到一个小部件。当小部件的值发生改变时,变量的值会随之变化;反之,当变量的值发生改变时,小部件的值也会随之变化。让我们来看一个有趣的程序。 code type="section" width="100%">


列表 5. 变量绑定示例代码

                
var=TkVariable.new
TkEntry.new(root){
  text var
  pack
}  
TkEntry.new(root){
  text var
  pack
}
			

 

运行结果如图:


图 2. 变量绑定示例程序-初始界面
变量绑定示例程序-初始界面

图 3. 变量绑定示例程序-运行结果
变量绑定示例程序-运行结果

如图所示,两个输入框被绑定到同一个 TkVarible 实例,当在第一个输入框中输入“hello”的时候,第二个输入框也会显示“hello”;反之亦然。

动态设置与获取小部件属性

如何在运行时修改小部件的属性呢?每个小部件都支持 configure 方法,该方法跟 new 方法一样通过代码块或哈希表的方式在运行时修改小部件属性。例如,我们修改“你好!中国”示例程序如下:


列表 6. 动态设置小部件属性示例代码

                
require 'tk'

msg="你好!中国"
root=TkRoot.new{title msg}
label_msg=TkLabel.new(root){
  text  msg
  pack :padx=>2,:pady=>2,:side=>'top'
  font "arial 20 bold"
}
  
TkButton.new(root){
  text "变色"
  pack :padx=>2,:pady=>2,:side=>'bottom'
  command{
    label_msg.configure('foreground'=>'red')
  }
}

Tk.mainloop
			



图 4. 动态设置小部件属性示例程序-初始界面
动态设置小部件属性示例程序-初始界面

图 5. 动态设置小部件属性示例程序-运行结果
动态设置小部件属性示例程序-运行结果

如图所示,当点击“变色”按钮,显示文本的标签的 configure 方法被调用。“foreground”属性被重新设置为“red”,因此文本“你好!中国”变成红色。

 

通过调用小部件的 cget 方法能够获得小部件属性,示例代码如下:


列表 7. 获取小部件属性示例代码

                
require 'tk'

msg="你好!中国"
root=TkRoot.new{title msg}
label_msg=TkLabel.new(root){
  text  msg
  pack :padx=>2,:pady=>2,:side=>'top'
  font "arial 20 bold"
}
  
TkButton.new(root){
  text "输出属性值"
  pack :padx=>2,:pady=>2,:side=>'bottom'
  command{
    
    puts label_msg.cget('text')
    puts label_msg.cget('justify')
    puts label_msg.cget('border')
    
  }
}
  
Tk.mainloop
			

 

输出结果:


列表 8. 获取小部件属性示例程序输出结果

                
你好!中国
center
2
			

 

事件处理

对小部件的进行操作有很多种,比如“鼠标点击”、“鼠标双击”、“鼠标滑过”等等,所有这些都被称为“事件”。Ruby/Tk 通过小部件的 bind 方法为某一事件绑定相应的行为。我们仍然在“你好!中国”示例程序的基础上进行修改。


列表 9. 事件处理示例程序

                    
require 'tk'

msg="你好!中国"
root=TkRoot.new{title msg}
label_msg=TkLabel.new(root){
  text  msg
  pack :padx=>2,:pady=>2,:side=>'top'
  font "arial 20 bold"
}
  
b=TkButton.new(root){
  text "变色"
  pack :padx=>2,:pady=>2,:side=>'bottom'
}
  
b.bind("Enter"){label_msg.configure('foreground'=>'red')}
b.bind("Leave"){label_msg.configure('foreground'=>'black')}

Tk.mainloop
			

 

我们给按钮定义了两个事件绑定:当“进入”(Enter 事件)按钮时,修改文本颜色为红色;当“离开”(Leave 事件)按钮时,修改文本颜色为黑色。因此当鼠标滑过按钮的时候,您可以看到文本的颜色变化。
上面的示例非常简单,事实上事件名称可以是多个通过连接符分隔的字符串组成,其格式为“修饰符-修饰符-事件类型-细节”。“修饰符”包括 “Button1”、“Control”、“Alt”、“Shift”等等,从任何 Tk 的文档里您都可以获得完整的“修师符”列表。 “事件类型”的名称来自于 X11 命名规范。“事件类型”的值包括“ButtonPress”、“KeyPress”、“Expose”等等。关于“细节”的值,对按钮而言,它是整数1到 5当中的一个值;对按键而言,它是键的符号。例如,形如“Control-Button1-ButtonRelease”(等同于“Control- ButtonRelease-1”)的事件会在 Control 键被按下,同时鼠标键 1 被释放的时刻触发。您可以通过以下代码来体验一下该事件的效果。


列表 10. 事件“Control-ButtonRelease-1”示例代码

                    
require 'tk'

msg="你好!中国"
root=TkRoot.new{title msg}
label_msg=TkLabel.new(root){
  text  msg
  pack :padx=>2,:pady=>2,:side=>'top'
  font "arial 20 bold"
}
label_msg.bind("Control-ButtonRelease-1"){label_msg.configure('foreground'=>'red')}
Tk.mainloop
			

 

绘图支持

Ruby/Tk 提供了 TkCanvas 类以及其他一些类来支持绘图功能。一般而言,这样的一些类都以 Tkc 开头,c 表示 Canvas,例如 TkcLine、TkcOval 等等。下面是一个简单的程序示例。


列表 11. 绘图支持示例代码

                
require 'tk'

title_text="绘图示例程序"
root=TkRoot.new{title title_text}
c=TkCanvas.new(root)
c.pack
#head
TkcOval.new(c,30,30,80,80).fill 'blue'
#arms
TkcLine.new(c,10,110,100,110)
#body
TkcLine.new(c,55,80,55,170)
#left leg
TkcLine.new(c,55,170,10,250)
#right leg
TkcLine.new(c,55,170,100,250)
  
Tk.mainloop
			

 

该代码的输出结果如下:


图 6. 绘图示例代码运行结果
绘图示例代码运行结果

滚动条支持

TkCanvas、TkListbox 以及 TkText 允许被设置滚动条。以下代码示范了如何为 TkText 设置滚动条,通过运行该示例代码,您将看到小部件与滚动条两者是如何交互的。


列表 12. 滚动条实现示例代码

                    
require 'tk'

title_text="滚动条示例程序"
root=TkRoot.new{title title_text}
text_area=TkText.new(root){
  width 30
  height 10
  pack :padx=>2,:pady=>2,:side=>'left',:fill=>'both'
}
scroll_bar=TkScrollbar.new(root)do
  orient 'vertical'
  pack :fill=>'both',:side=>'right'
end
scroll_bar.command(proc { |*args|
  text_area.yview(*args)
})
text_area.yscrollcommand(proc { |first, last|
  scroll_bar.set(first, last)
})
	
Tk.mainloop
			

 

首先,我们创建一个 TkScrollbar 的实例,并指明它是在垂直方向上滚动的(orient 'vertical')。由于滚动条和小部件之间的影响是相互的,所以接下来为滚动条指定 command 方法,该方法回调了 TkText 的 yview 方法,从而实现了滚动条对 TkText 的影响。然后,为 TkText 定义 yscrollcommand 方法,该方法回调滚动条的 set 方法,从而影响滚动条的显示。

布局管理

Ruby/Tk 有提供了三种方式管理小部件布局:place 方式、grid 方式以及 pack 方式。下面让我们一一来看这三种布局方式的不同。

place 方式

每个小部件都有 place 方法,该方法用 place 方式管理小部件的位置和大小。先让我们看一个示例程序。


图 7. place 方式示例代码运行结果
place 方式示例代码运行结果

其代码如下:


列表 13. place 方式示例代码

                
require 'tk'

title_text="place 方式示例程序"
root=TkRoot.new{title title_text}

TkButton.new(root, 'text'=>'按钮 1').place('height'=>50,'width'=>100,'x'=>20,'y'=>40)
TkButton.new(root, 'text'=>'按钮 2').place('height'=>80,'width'=>80,'x'=>50,'y'=>80)

Tk.mainloop
			

 

两个按钮怎么会重叠呢?正如大家看到的,place 方式只是简单地根据开发者为小部件指定的位置和大小放置它们。很多“图形界面构建器”程序都采用这种方式:当用户从“工具箱”中拖放一些小部件到工作区域 之后,便采用 place 方式 生成源代码。这种方式看上去很不错,但是有没有问题呢?这种方式的确存在问题。当把程序运行在不同的操作系统或者配置不同的计算机上时,由于不同系统的系 统字体可能是不同的,显示效果可能就会出乎意料。例如,原来一个 40 像素宽的按钮可能显示成60像素大小,这样就有 可能与相邻的小部件叠加起来。因此,很多人不会在实践中大量运用这种方式。

grid 方式

grid 方式用一种“表格”的方式来管理其中的小部件,通过指定“行”和“列”的值确定小部件的位置。让我们来看一段示例代码以及运行结果。


列表 14. grid 方式示例代码

                
require 'tk'

title_text="grid 方式示例程序"
root=TkRoot.new{title title_text}

TkButton.new(root, 'text'=>'按钮1').grid('row'=>1,'column'=>1)
TkButton.new(root, 'text'=>'按钮2').grid('row'=>2,'column'=>2)
TkButton.new(root, 'text'=>'按钮3').grid('row'=>3,'column'=>3)
TkButton.new(root, 'text'=>'按钮4').grid('row'=>1,'column'=>3)
TkButton.new(root, 'text'=>'按钮5').grid('row'=>3,'column'=>1)

Tk.mainloop
			

 

运行结果如下:


图 8. grid 方式示例代码运行结果
grid 方式示例代码运行结果

正如您所看到的,grid 方式采用一种十分“规矩”的方式放置小部件,它用相同的值限制其中小部件的宽度和高度。通过运用这种方式,当小部件不能撑满单元格时,还可以通过设置 “sticky”属性的值来指定其贴靠的方向;另外,通过设置属性“columnspan”和“rowspan”的值,使小部件可以占据多个单元格。当 然,grid 方法提供的属性远不只本文所涉及的这些,具体的请参阅Tk文档。

pack方式

在实践中,使用的最多的是 pack 方式。它不仅使用简单,而且有很强的灵活性。pack 方式采用一种称为“空”的模式管理小部件的位置和大小。最开始整个窗口是“空”的,当放入小部件的时候,您需要为 pack 方法指定“side”参数(该参数代表您要把小部件放在矩形“空”区域的哪一边,有效值为“left”、“right”、“top”以及 “bottom”),不管小部件是不是能将“那一边”撑满,pack 方式会将整个“空”区域的整个“那一边”给这个小部件,并且从整个“空”区域中减去这个小部件占据的大小。之后加入的小部件遵循相同的方式在剩余的“空” 区域中占据某一边。如果您仍不能理解这个规则,请看下面的示例。


列表 15. pack 方式示例代码

                
require 'tk'

title_text="pack 方式示例程序"
root=TkRoot.new{title title_text}

TkButton.new(root, 'text'=>'按钮1').pack('side'=>'top','fill'=>'both')
TkButton.new(root, 'text'=>'按钮2').pack('side'=>'left','fill'=>'both')
TkButton.new(root, 'text'=>'按钮3').pack('side'=>'bottom','fill'=>'both')
TkButton.new(root, 'text'=>'按钮4').pack('side'=>'right','fill'=>'both')
TkButton.new(root, 'text'=>'按钮5').pack('side'=>'top','fill'=>'both')

Tk.mainloop
			

 

运行结果:


图 9. pack 方式示例代码运行结果
pack 方式示例代码运行结果

当您根据代码,按照逆时针方向,从“按钮 1”到“按钮 5”观察它们所占据的位置和大小时,就能够理解这种所谓“空”的模式。

Ruby/Tk 扩展

前面提到的 TkButton、TkCanvas 等都是 Tk 的核心小部件。除此之外,还有很多第三方的小部件,我们称它们为“Tk 扩展”。这些第三方的 Tk 小部件往往比核心小部件更加强大。既然有“Tk 扩展”,就必然有“Ruby/Tk 扩展”与之对应。您可以在 Ruby 的安装目录中找到它们:目录“RUBY_INSTALL_DIR\lib\ruby\RUBY_VERSION_NUMBER\tk”中是核心 Ruby/Tk 代码,而目录“RUBY_INSTALL_DIR\lib\ruby\RUBY_VERSION_NUMBER\tkextlib”中是 Ruby/Tk 扩展代码。

结束语

Ruby/Tk 与 Perl/Tk 极其得相似,因此,在学习 Ruby/Tk 开发时,您可能需要参考一些关于 Perl/Tk 的书,比如《Mastering Perl-Tk》、《Perl/Tk Pocket Reference》等等。尤其是当您不知道某个小部件有哪些可用的属性时,完全可以查阅 Perl/Tk 的参考手册。希望本文对于您学习 Ruby/Tk 能有所帮助。有任何问题,请发邮件给我(shenrui@cn.ibm.com),我会尽力回答您的问题。