12.2 Ruby/GTK2
GTK+库是GIMP(GNU Image Manipulation Program)的副产品,它的名称实际上是GIMP Toolkit。与UNIX和LSD一样,GTK+也是来自加州大学伯克利分校。
GTK+的外观与X/Motif类似,但更轻量级。GTK+来源于UNIX领域,它是越来越多的Linux用户熟悉的GNOME的基础,但跨平台程度较高。从GTK+ 2.0起,它不仅支持类似UNIX的系统,还支持MS Windows系列操作系统和包含X Window系统的Mac OS X。正在开发Mac OS X本征版本,但编写本书时还不稳定。
Ruby/GTK2是GTK+ 2.0的移植版本,不要将它与Ruby/GTK(基于GTK+ 1.2)混为一谈,后者是不兼容的且已过时。本节只介绍Ruby/GTK2。
12.2.1 概述
Ruby/GTK2库让Ruby应用程序能够使用GTK+ 2.x库。GTK+是开源的,采用GNU LGPL许可方式,因此商业应用程序也可使用它。
与大多数GUI工具包一样,GTK+也有诸如框架、窗口、对话框和布局管理器等概念。它提供了丰富的窗口部件,包括所有最基本的窗口部件(如标签、按钮和文本编辑框等),还提供了一些高级窗口部件,如树和多列列表。
虽然GTK+是使用C语言编写的,但其设计有强烈的面向对象特征。因此,Ruby/GTK2既提供了一个纯粹的面向对象API,同时又接近底层的C语言。另外,Ruby/GTK2是手工仔细实现的,而不是使用代码生成器(如SWIG)生成的。因此,该API很像Ruby:使用代码块、参数可省略等,其参考手册可从http://ruby-gnome2.sourceforge.jp/下载。
GTK+实际上建立在GLib、Pango、ATK、Cairo和GDK等库的基础之上,它支持非图形功能(GLib)、布局和UTF-8国际化文本的渲染(Pango)、辅助功能(Atk)、图形渲染(Cairo)、低级图形对象(Gdk)以及大量的窗口部件和高级图形对象(Gtk)。
编写本书时,Ruby/GTK2为0.14.1版,它与当前稳定的Ruby和GTK+版本(2.0)兼容。除Linux外,它还支持Windows系列操作系统和(包含X Window系统的)Mac OS X。正在开发Mac OS X本征版本,但当前不稳定。
GTK+是面向对象的,有一个逻辑窗口部件层次结构。Gtk::Bin和Gtk::Container功能强大,布局管理器Gtk::Box和Gtk::Table简单而灵活。Ruby/GTK2用于设置信号处理程序的机制也很方便。
GTK+窗口部件包括菜单、工具栏、工具提示、树、进度条、滑块和日历等。当前,GTK+的一个缺点是,没有提供大量标准对话框,且很难将对话框设置为模态的。另外,标准的多行文本编辑器窗口部件也有一些缺陷。
传递给Ruby/GTK2方法的所有字符串都必须是UTF-8的,不能使用有些Windows单字节或多字节代码页中的非ASCII字符。编辑UTF-8模式的Ruby脚本要小心,务必在Ruby脚本开头加上$KCODE=“U”。
12.2.2 一个简单的窗口应用程序
使用Ruby/GTK2的程序都必须请求(require)gtk2库。Ruby/GTK2通过Gtk和Gdk模块提供其功能,这意味着引用GTK+类时通常需要加上前缀Gtk::或Gdk::。
通常,调用Gtk.init来初始化Ruby/GTK2,然后创建一个顶级窗口和一个用于destroy信号的处理程序(用户关闭窗口时将发出这个信号)。调用show_all使窗口(及其子窗口部件)可见,调用Gtk.main开始事件循环。
先来看一个例子,然后对这些内容展开讨论。下面的代码段与Tk示例类似,它显示当前日期:
$KCODE = "U"
require "gtk2"
Gtk.init
window = Gtk::Window.new("Today's Date")
window.signal_connect("destroy") { Gtk.main_quit }
str = Time.now.strftime("Today is \n%B %d, %Y")
window.add(Gtk::Label.new(str))
window.set_default_size(200, 100)
window.show_all
Gtk.main
$KCODE变量在第4章讨论过。调用Gtk.init初始化Ruby/GTK2。
主窗口(类型为Gtk::Window)将作为顶级窗口,其标题栏显示指定的文本。顶级窗口有标准标题栏,其行为通常与应用程序的主窗口相同。
接下来,创建了一个处理destroy信号的处理程序,主窗口关闭时将生成destroy信号。这个处理程序(这里是一个代码块)只是退出主事件循环。Ruby/GTK2文档列出了每个窗口部件能够接收的所有信号(请务必查看超类接收的事件)。这些事件通常是由鼠标或键盘输入、定时器和窗口状态改变等触发的。
下一行代码将一个文本标签窗口部件加入到主窗口中,标签默认的大小是根据文本长度自动计算的。
默认情况下,GTK+父窗口部件的大小将根据其子窗口部件的大小自动调整。在这个例子中,在使用默认字体的情况下,字符串的长度决定了标签窗口部件的大小,而主窗口将刚好容下标签。这很小,因此使用set_default_size将主窗口的初始宽度和高度分别设置为200像素和100像素。
接下来,使用show_all使主窗口及其所有子窗口部件可见。默认情况下,主窗口被隐藏,因此,必须为大多数应用程序的主窗口调用这个方法。
调用Gtk.main开始GTK+事件循环,该方法到GTK+终止后才返回。在这个应用程序中,destroy事件处理程序导致Gtk.main退出,此时应用程序将终止。
12.2.3 使用按钮
要使用Ruby/GTK2创建按钮,可使用Gtk::Button类来定义。在简单的情况下,只要设置处理clicked事件的处理程序,该事件在用户单击按钮时触发。
程序清单12.5接受用户在文本框中输入的一行文本,然后(用户单击All Caps!按钮时)将字符串转换为大写。图12.4显示了单击按钮前的文本框。
程序清单12.5 GTK中的按钮
$KCODE = "U"
require "gtk2"
class SampleWindow < Gtk::Window
def initialize
super("Ruby/GTK2 Sample")
signal_connect("destroy") { Gtk.main_quit }
entry = Gtk::Entry.new
button = Gtk::Button.new("All Caps!")
button.signal_connect("clicked") {
entry.text = entry.text.upcase
}
box = Gtk::HBox.new
box.add(Gtk::Label.new("Text:"))
box.add(entry)
box.add(button)
add(box)
show_all
end
end
Gtk.init
SampleWindow.new
Gtk.main
在程序清单12.5中,定义了一个SampleWindow类。这种方法更清晰,因为它让类能够控制自己的外观和行为(而不要求调用者配置窗口)。这个主窗口继承了Gtk::Window。
与“当前日期”示例一样,主窗口关闭时,destroy信号的处理程序将退出GTK+事件循环。
这个类使用Gtk::Entry类创建了一个单行文本框,创建了一个文本为All Caps!的Gtk::Button。该按钮的clicked事件处理程序将文本转换为大写(clicked事件是在用户单击并松开按钮后发生的)。
Gtk::Window类是一个Gtk::Bin,因此只能包含一个子窗口部件。为在窗口中放置两个子窗口部件,将这两个窗口部件放在了一个框(box)中,然后将这个框加入到主窗口中。窗口部件被加入到Gtk::HBox中时,默认将放置到右边。还有一个对应的Gtk::VBox窗口部件,它能够将多个窗口部件垂直地排列。
与前一个例子一样,也必须调用show_all使主窗口(及其子窗口部件)可见。
用户单击按钮时,将调用clicked的信号处理程序,该处理程序读取文本框中的当前文本,将其转换为大写,然后将它发送回文本框。
真正起作用的应用程序代码在SampleWindow类定义之后,它创建主窗口并运行GTK+事件循环。
12.2.4 使用文本框
GTK+提供了Gtk::Entry类用于单行输入,如前一个例子所示。它还提供了Gtk::TextView
类,这是一个功能强大的多行编辑器,下面将介绍它。
程序清单12.6创建一个多行文本框,并在其中插入一些文本。当内容发生变化时,文本长度将显示在窗口底部的一个标签中(见图12.5)。
程序清单12.6 一个GTK文本编辑器
$KCODE = "U"
require "gtk2"
class TextWindow < Gtk::Window
def initialize
super("Ruby/GTK2 Text Sample")
signal_connect("destroy") { Gtk.main_quit }
set_default_size(200, 100)
@text = Gtk::TextView.new
@text.wrap_mode = Gtk::TextTag::WRAP_WORD
@buffer = @text.buffer
@buffer.signal_connect("changed") {
@status.text = "Length: " + @buffer.char_count.to_s
}
@buffer.create_tag('notice',
'font' => "Times Bold Italic 18",
'foreground' => "red")
@status = Gtk::Label.new
scroller = Gtk::ScrolledWindow.new
scroller.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_NEVER)
scroller.add(@text)
box = Gtk::VBox.new
box.add(scroller)
box.add(@status)
add(box)
iter = @buffer.start_iter
@buffer.insert(iter, "This is an editor")
iter.offset = 5
@buffer.insert(iter, "really ", "notice")
show_all
end
end
Gtk.init
TextWindow.new
Gtk.main
这段代码的基本结构与按钮示例类似:初始化Ruby/GTK2,定义一个窗口类(其中包含一个终止应用程序的事件处理程序),设置主窗口的初始大小,在initialize方法的末尾调用show_all使窗口可见,最后两行代码实际创建窗口并运行GTK+事件循环。
创建了一个名为@text的编辑器窗口部件,它支持自动换行,默认情况下,换行时不考虑断词。
变量@buffer是@text的文本缓冲区。给changed事件创建了一个信号处理程序,每当插入、删除或修改文本时,都将触发这个信号,从而调用相应的信号处理程序。该信号处理程序使用char_count来确定文本编辑器中当前文本的长度,并创建一个消息字符串,然后使用代码@status.text = text显示该消息。
接下来需要配置@text窗口部件,使其以不同的样式显示文本。使用create_tag创建了一个notice标记,该标记的字体为Times Bold Italic 18,前景色为红色。同样,可使用Gtk::TextTag定义包含各种属性的标记。
这里试图使用一种Times系列字体,在Windows平台中可能是Times Roman变体;在Linux/UNIX平台上,这个参数将为标准的X Window系统字体字符串。系统将返回最匹配的字体。
@status最初是空的,随后将修改其文本。
GTK+提供了两种将滚动条添加到应用程序中的方式。可直接创建Gtk::ScrollBar对象,并使用信号使其与内容窗口部件同步。但大多数情况下,使用Gtk::ScrolledWindow窗口部件更简单。
Gtk::ScrolledWindow窗口部件是一个Gtk::Bin,这意味着它只能包含一个子窗口部件。当然,这个子窗口部件可以是Gtk::Box或其他能够包含多个子窗口部件的容器。有几个GTK+窗口部件(包括Gtk::TextView)能够自动与Gtk::ScrolledWindow交互,而不要求程序员编写额外的代码。
在这个例子中,创建了一个名为scroller的Gtk::ScrolledWindow,并使用set_policy对其进行配置。编辑器不显示水平滚动条,且在其内容无法同时显示时才显示垂直滚动条。文本编辑器被直接添加到scroller中。
接下来设置Gtk::Vbox,使其垂直排列窗口部件。首先加入包含文本框的滚动窗口,使其显示在主窗口的顶部,标签@status放在底部。然后将Gtk::Vbox加入到主窗口中。
接下来的4行代码在文本编辑器中插入文本。第一行获取文本的起始位置(偏移量为0),然后在这里插入字符串。由于插入前没有文本,因此应在偏移量为零的地方插入。然后在偏移量为5的地方再插入文本,这样文本编辑器将包含字符串This really is an editor。
由于已配置了changed事件处理程序,因此调用insert时将触发它。这样,即使用户没修改文本,标签@status也将正确显示。
12.2.5 使用其他窗口部件
即使是比较简单的GUI也需要除文本框和按钮外的其他窗口部件,经常还需要单选按钮、复选框及类似的窗口部件。下面的例子演示了其中的几种。
程序清单12.7假设用户要订机票。用Gtk::TreeView、Gtk::ListStore和Gtk::TreeViewColumn等类(多列列表)列出目标城市。复选框(Gtk::CheckButton)指定是否是往返机票,并用一组单选按钮(Gtk::RadioButton类)来选择机票类型。最后是一个Purchase按钮(见图12.6)。
程序清单12.7 机票示例
$KCODE = "U"
require "gtk2"
class TicketWindow < Gtk::Window
def initialize
super("Purchase Ticket")
signal_connect("destroy") { Gtk.main_quit }
dest_model = Gtk::ListStore.new(String, String)
dest_view = Gtk::TreeView.new(dest_model)
dest_column = Gtk::TreeViewColumn.new("Destination",
Gtk::CellRendererText.new,
:text => 0)
dest_view.append_column(dest_column)
country_column = Gtk::TreeViewColumn.new("Country",
Gtk::CellRendererText.new,
:text => 1)
dest_view.append_column(country_column)
dest_view.selection.set_mode(Gtk::SELECTION_SINGLE)
[["Cairo", "Egypt"], ["New York", "USA"],
["Tokyo", "Japan"]].each do |destination, country|
iter = dest_model.append
iter[0] = destination
iter[1] = country
end
dest_view.selection.signal_connect("changed") do
@city = dest_view.selection.selected[0]
end
@round_trip = Gtk::CheckButton.new("Round Trip")
purchase = Gtk::Button.new("Purchase")
purchase.signal_connect("clicked") { cmd_purchase }
@result = Gtk::Label.new
@coach = Gtk::RadioButton.new("Coach class")
@business = Gtk::RadioButton.new(@coach, "Business class")
@first = Gtk::RadioButton.new(@coach, "First class")
flight_box = Gtk::VBox.new
flight_box.add(dest_view).add(@round_trip)
seat_box = Gtk::VBox.new
seat_box.add(@coach).add(@business).add(@first)
top_box = Gtk::HBox.new
top_box.add(flight_box).add(seat_box)
main_box = Gtk::VBox.new
main_box.add(top_box).add(purchase).add(@result)
add(main_box)
show_all
end
def cmd_purchase
text = @city
if @first.active?
text += ": first class"
elsif @business.active?
text += ": business class"
elsif @coach.active?
text += ": coach"
end
text += ", round trip " if @round_trip.active?
@result.text = text
end
end
Gtk.init
TicketWindow.new
Gtk.main
和以前一样,这个应用程序创建一个有信号处理程序的主窗口。接下来,创建了一个包含两列的多列列表窗口部件,该列表框采用模型—视图—控制器(Model-View-Controller,MVC)设计,Gtk::ListStore(模型类)有两个String列。
然后创建Gtk::TreeView,Gtk::TreeViewColumn用于配置列。第一列的标题为Destination,单元格渲染器为Gtk::CellRendererText。模型(Gtk::ListStore)的第一列(编号为零)用做文本属性值,这样,单元格渲染器用于绘制树模型中的数据。GTK+ 2.x自带了多个单元格渲染器,包括Gtk::CellRendererText、Gtk::CellRendererPixbuf和Gtk::CellRendererToggle。然后添加三行数据到列表中,并为changed事件创建一个信号处理程序。每当用户选择不同的行时,都将调用该处理程序,它更新成员变量@city,使其包含新选定行的第一列(编号为零)的文本。
另外,还创建了一个简单的复选框(Gtk::CheckButton)和按钮(Gtk::Button)。该按钮被单击时,其信号处理程序将执行cmd_purchase方法。标签@result最初为空,但之后将被设置为表示机票类型的字符串。
另外,创建了三个作为一组的单选按钮,这意味着每次只能选择其中的一个。用户单击任何一个单选按钮时,之前选中的按钮都将被自动取消选中。传递给单选按钮构造函数的第一个参数是同一组中的前一个单选按钮,因此,第一个创建的单选按钮还没有组可作为参数,后面两个按钮都以第一个按钮作为参数。
需要以对用户来说合理的方式排列窗口部件,因此组合使用了Gtk::HBox和Gtk::VBox。列表框在复选框上方,三个单选按钮在列表的右边,并垂直排列。最后,Purchase按钮显示在其他所有窗口部件的下方。
cmd_purchase方法很简单:用户单击Purchase按钮时,它生成反映所有窗口部件当前状态的字符串。单选按钮和复选框有一个active?方法,如果相应的窗口部件被选中,该方法将返回true。然后将文本放到标签@result中,使其显示在屏幕上。
大多数应用程序都将菜单作为用户界面的重要部分,下面的例子演示如何使用Ruby/GTK2创建菜单,它表明添加工具提示也很简单,这对任何程序都有吸引力。
程序清单12.8创建了一个主窗口,其中的菜单栏包含菜单File和其他两个不可用的菜单。File菜单包含一个用于退出应用程序的Exit菜单项。菜单项File和Exit都有工具提示。
程序清单12.8 GTK菜单示例
$KCODE = "U"
require "gtk2"
class MenuWindow < Gtk::Window
def initialize
super("Ruby/GTK2 Menu Sample")
signal_connect("destroy") { Gtk.main_quit }
file_exit_item = Gtk::MenuItem.new("_Exit")
file_exit_item.signal_connect("activate") { Gtk.main_quit }
file_menu = Gtk::Menu.new
file_menu.add(file_exit_item)
file_menu_item = Gtk::MenuItem.new("_File")
file_menu_item.submenu = file_menu
menubar = Gtk::MenuBar.new
menubar.append(file_menu_item)
menubar.append(Gtk::MenuItem.new("_Nothing"))
menubar.append(Gtk::MenuItem.new("_Useless"))
tooltips = Gtk::Tooltips.new
tooltips.set_tip(file_exit_item, "Exit the app", "")
box = Gtk::VBox.new
box.pack_start(menubar, false, false, 0)
box.add(Gtk::Label.new("Try the menu and tooltips!"))
add(box)
set_default_size(300, 100)
show_all
end
end
Gtk.init
MenuWindow.new
Gtk.main
同样,这段代码的基本结构与其他例子相同。在这里,创建了一个名为Exit的Gtk::MenuItem,还创建一个退出程序的信号处理程序。信号为activate,它在用户选择该菜单项时触发。
接下来创建File菜单,并在其中添加菜单项Exit,这就是创建弹出式菜单的步骤。接下来,创建一个菜单项File——菜单栏中显示的就是该菜单项。调用submenu=将菜单项File与菜单File连接起来。
接下来创建Gtk::MenuBar,并在其中添加了三个菜单:File、Nothing和Useless。只有第一个菜单能起作用,其他两个只显示在界面上。
使用一个Gtk::Tooltips对象管理所有的工具提示。要为窗口部件(如菜单项)创建工具提示,可调用set_tip,并将窗口部件、工具提示文本和包含其他私有文本的字符串传递给它。私有文本不显示在工具提示中,但可供帮助系统等使用。
使用Gtk::Vbox将菜单栏放到主窗口的顶部——其他所有窗口部件上方。这里没有使用add方法将菜单栏加入到Gtk::Vbox中,而使用pack_start,以便能够更好地控制窗口部件的外观和位置。
pack_start的第一个参数是要加入的窗口部件,第二个参数是一个布尔值,指出窗口部件是否应占据所有的可用空间。注意,这不会导致窗口部件增大,而是让窗口部件居中。在这个例子中,要将菜单栏放在屏幕顶部,因此将该参数设置为false。
第三个参数是一个布尔值,指出窗口部件是否应增大以填满所有可用空间。由于这里要使用一个小菜单,因此将该参数也设置为false。pack_start的最后一个参数表示边距,用于在窗口部件四周留出一些空间,这里不需要边距,因此将其设置为零。
文本标签将占据主窗口的大部分区域。最后,将窗口的初始大小设置为300像素宽、100像素高。
12.2.6 其他说明
Ruby/GTK2是Ruby-GNOME2项目的一部分。GNOME是一个依赖于GTK+的高级包,Ruby-GNOME2是GNOME库绑定的集合。
Ruby-GNOME2包含下列库:
· 核心库:这些库包含在ruby-gtk2包中,有时使用术语Ruby/GTK2表示这些库。所有这些库都能用于UNIX类系统、MS Windows、Mac OS X(X11下)和Cygwin(X11下)。其他的Ruby-GNOME2库请求(require)这些库。
· Ruby/GLib2:GLib是组成底层基础设施的低级核心库。它提供C数据结构处理、可移植性封装类、Unicode支持、用于运行时功能(如事件循环、线程、动态加载)的接口和对象系统。Ruby/GLib2是GLib库的封装类。由于Ruby有很好的字符串类和列表类,因此有些GLib功能没有实现。另一方面,它确实提供一些转换C和Ruby对象的重要函数。其他所有Ruby/GTK2库都请求这个库。
· Ruby/ATK:提供一套辅助功能接口。通过支持ATK接口,应用程序或工具包能够支持屏幕阅读器、放大镜和其他输入设备。
· Ruby/Pango:这是一个布局和文本渲染库,其重点是使用UTF-8进行国际化。它是GTK+(2.0)文本和字体处理的核心。
· Ruby/GdkPixbuf2:图像加载和操纵库,支持很多图像格式,如JPEG、PNG和GIF等。
· Ruby/GDK2:将GTK+同窗口系统细节隔离的中间层。
· Ruby/GTK2:包含主要的GUI窗口部件。
· 其他库:这些库和核心库一起包含在ruby-gnome2包中,它们都可用于UNIX类系统,有些库(Ruby/GtkGLExt、Ruby/Libglade2)可用于MS Windows和Mac OS X,还有些库也可用于Mac OS X(X11下)和Cygwin(X11下),但没有经过全面测试。
· Ruby/GNOME2:包含用于GNOME环境的其他窗口部件。
· Ruby/GnomeCanvas2:用于创建结构化的交互式图形的窗口部件。
· Ruby/GConf2:一个进程透明的配置数据库(与Windows注册表类似)。
· Ruby/GnomeVFS:让应用程序能够无缝地访问远程文件和本地文件。
· Ruby/Gstreamer:一个针对音频和视频的多媒体框架。
· Ruby/GtkHtml2:一个HTML窗口部件。
· Ruby/GtkGLExt:提供使用OpenGL的3D渲染。
· Ruby/GtkSourceView:一个Text窗口部件,能够突出语法和支持源代码编辑器的典型功能。
· Ruby/GtkMozEmbed:一个内嵌Mozilla Gecko渲染器的窗口部件。
· Ruby/Libart2:处理基本的绘图功能。
· Ruby/Libgda:一个GDA(GNU Data Access)体系结构接口,用于访问数据库或LDAP等数据源。
· Ruby/Libglade2:让应用程序能够在运行时加载XML文件中的用户界面。XML文件是使用GLADE创建的,GLADE是一个功能强大的用户界面生成器,可简化国际化GUI的创建。
· Ruby/PanelApplet:GNOME面板的面板applet库。
· Ruby/GnomePrint和Ruby/GnomePrintUI:提供用于打印的窗口部件。
· Ruby/RSVG:支持SVG向量图形的渲染。
· 外部库:Ruby-GNOME2库将请求这些库。
· Ruby/Cairo:一个2D图形库,支持多个输出设备。目前支持的输出目标包括X Window系统、Win32和图像缓存。还在试验中的后端包括OpenGL(通过glitz)、Quartz、XCB、PostScript和PDF文件输出。核心库请求了这个库。Ruby/Cairo也请求Ruby/GLib2。其官方主页是http://cairographics.org/。
· Ruby/OpenGL是OpenGL 3D图形库接口。在Ruby/GtkGLExt2中请求了这个库。它可用于很多平台。其官方主页是http://www2.giganet.net/~yoshi/。
· Ruby-GetText-Package为本地化提供管理翻译消息目录的功能(参见第4章)。Ruby/Libglade2就是使用这个包进行本地化的(虽然这是可选的),其他库也可使用这个库进行本地化。其官方主页是http://gettext.rubyforge.org/。
Ruby-GNOME2的官方主页是http://ruby-gnome2.sourceforge.jp/,在这里可以找到发布文件、安装指南、API参考手册、教程和示例代码。GNOME的官方主页是http://www.gnome.org/,GTK+的主页是http://www.gtk.org/。







