Chinaunix首页 | 论坛 | 博客
  • 博客访问: 166893
  • 博文数量: 45
  • 博客积分: 2510
  • 博客等级: 少校
  • 技术积分: 454
  • 用 户 组: 普通用户
  • 注册时间: 2009-10-19 13:35
文章分类

全部博文(45)

文章存档

2011年(1)

2009年(44)

我的朋友

分类:

2009-10-21 09:04:37

我也标题党一回:300行代码你能做什么?本文介绍一个具有builder风格的RubyGnome2布局器(GtkSimpleLayout)不到300行代码,还包括几个实用的除布局之外的增强功能。

 

前言

随着RubyGnome2库越来越完善,以及ruby1.9的性能提升,用Ruby编写GUI程序渐渐从我的业余爱好转为我工作的一个重要部分。

 

用Ruby写程序确实很有乐趣,它可以让你的想法快速地以一种优雅的方式实现。本文介绍的一个gem就是一个例子,用很少的代码,实现很有趣的功能,让编写Ruby GUI程序变得轻松愉快。

 

RubyGnome2介绍

 

虽然我以前也曾经多次地介绍过RubyGnome2,但我还是想再一次地推荐RubyGnome2,它实在是使用Ruby编写GUI程序的首选。

 

RubyGnome2是GTK+库的一个ruby扩展。它对GTK+的对象模型仔细地用Ruby的方式进行封装,保留了GTK+ API命名方式和含义,因此GTK+的文档对于RubyGnome2也是适用的---尽管我认为RubyGnome2的文档已经做得非常不错了,需要回去借鉴GTK文档的地方实在不多。

 

虽然GTK本身是由C编写的,但它有一套完整的精心设计对象体系,使得它的GUI元件可以非常灵活的自由组合,以实现复杂的,功能强大的界面。

 

由于GTK非常重视它的对象体系的灵活性,因此刚开始使用GTK编程并不容易。很多时候表面上看起来很简单的一个功能,在GTK里面却要绕几个弯才能实现。例如要设置一个label的字体,要通过Pango来实现,Pango接管了GTK的所有字体渲染事务....这种“多绕几个弯”的情况很多,它确实使得编写GTK程序不那么直接了当。但换来的是整个GTK系统变得非常灵活,以较少的代价实现强大的功能,在跨平台,换肤,国际化上面都有很好的表现。

 

RubyGnome2继承了GTK的所有特点,包括优点和缺点。

 

GUI布局

GTK程序的布局很灵活,有许多种容器可选择。在布局的时候,大多数都是推荐相对位置而不是绝对位置,这样GUI程序可以更好的适应不同分辨率的屏幕,也有利于特定风格对UI的fine tune。

最常见的容器就是“盒子”,包括“水平盒子”和“垂直盒子”。将可视的UI元件放入“盒子”,不同的“盒子”互相组合叠放就可以构建出目标布局。

 

理论上用盒子就可以构建任何相对位置布局,但是为了方面,GTK还提供了像table这样的更高级的容器。

 

盒子模型对于许多刚开始GTK编程的人觉得很难适应,即使用了可视化布局工具例如Glade,初学者也往往被盒子模型困扰。可视化布局器最擅长的是固定位置布局。对于相对位置布局,很多时候用代码构建界面比用Glade反而来的快捷方便。

 

但是用代码构建界面有一个显著的缺点,那就是“不直观”,即很难从代码中看出UI布局,这样会给后期维护以及变更带来麻烦。

 

有一些GUI库如Shose,用builder风格的代码来描述UI,使得UI布局可以通过代码形象地体现出来,如以下这个例子来自shooose.net:

 

Ruby代码 复制代码
  1. Shoes.app {   
  2.   stack(:margin => 4) {   
  3.     button "Mice"  
  4.     button "Eagles"  
  5.     button "Quail"  
  6.   }   
  7. }  

 

这样,我们就可以从代码中一下子看出UI布局了。

 

builder风格的代码和HTML很类似,对于熟悉HTML的web界面设计者来说,可视化的编辑器并没有多大的必要。

 

 

RubyGnome2没有为我们提供builder方式的布局,因此UI代码写起来就像:

 

Ruby代码 复制代码
  1. class MyWin < Gtk::Window   
  2.   def initialize   
  3.     super  
  4.     vbox = Gtk::VBox.new  
  5.     btn_mice = Gtk::Button.new 'Mice'  
  6.     vbox.pack_start btn_mice   
  7.     btn_eagles = Gtk::Button.new 'Eagles'  
  8.     vbox.pack_start btn_eagles   
  9.     btn_quail = Gtk::Button.new 'Quail'  
  10.     vbox.pack_start btn_quail   
  11.     add vbox   
  12.   end  
  13. end  

  

从上面的代码中很难一下子看出UI布局。

 

如果也为RubyGnome2构建一个builder风格的布局器,那么代码就会变成:

 

Ruby代码 复制代码
  1. class MyWin < Gtk::Window   
  2.   
  3.   def initialize   
  4.     super  
  5.     add my_layout   
  6.   end  
  7.   
  8.   def my_layout   
  9.     vbox do  
  10.       button 'Mice'  
  11.       button 'Eagles'  
  12.       button 'Quail'  
  13.     end  
  14.   end  
  15.   
  16. end  

 

嗯,这个代码就和Shose差不多了,可以从代码中一眼看出UI布局。

 

本文所介绍的GtkSimpleLayout其功能之一就是为RubyGnome2提供builder风格的布局器。

 

GtkSimpleLayout布局器

这个简单的布局器原先只有200行不到的代码,我经常是直接拷贝到项目中使用。后来逐渐添了些功能,觉得它变得更有用了,于是便发布到github生成gem,方便感兴趣者使用。

 

Source:

or:

gem source -a && gem install rickyzheng-GtkSimpleLayout

 

以下是主要功能介绍以及简单例子。

 

提供Builder风格布局

正如上面的例子中所介绍的,GtkSimpleLayout为RubyGnome2带来了builder风格的布局功能,只需要为布局的类扩展GtkSimpleLayout::Base即可,一个完整的例子:

 

Ruby代码 复制代码
  1. require 'gtk2'  
  2. require 'simple_layout'  
  3.   
  4. class MyWin < Gtk::Window   
  5.   include SimpleLayout::Base   
  6.   def initialize   
  7.     super  
  8.     add my_layout   
  9.     signal_connect('destroy'do  
  10.       Gtk.main_quit   
  11.     end  
  12.   end  
  13.   
  14.   def my_layout   
  15.     hbox do  
  16.         label 'Hello, '  
  17.         button 'World !'  
  18.       end  
  19.   end  
  20. end  
  21.   
  22. MyWin.new.show_all   
  23. Gtk.main  


 

从上面的例子中可以看出,GtkSimpleLayout并没有改变RubyGnome2程序的主框架,它只是一个扩充。

 

 

属性设置

在放置UI元件的时候,往往需要设置初始属性,或者要指定布局参数。GtkSimpleLayout用Hash来传递这些属性与参数,例如:

 

Ruby代码 复制代码
  1. vbox do  
  2.   button 'sensitive = false':sensitive => false  # 初始为disable状态   
  3.   button 'expand space':layout => [truetrue]  # 指定这个button填充剩余空间   
  4. end  


上面这个例子中,第一个button的初始状态为disable。 ":sensitive => false"这个参数最终被转换成属性设置:Gtk::Button#sensitive=false,至于Gtk::Button有那些属性可以设置,请参阅RubyGnome2 API文档或GTK文档。GtkSimpleLayout在这里只是作一个简单参数的转换而已。

 

第二个button的":layout => [true, true]"有点特殊。":layout" 参数是GtkSimpleLayout的保留参数,它会被转换成当这个UI被放入容器时候的参数。这个例子中,容器是vbox(Gtk::VBox),默认的加入方法是Gtk::VBox#pack_start,这个例子中的[true, true] 最终会被传递到pack_start,因此这个button在被加入vbox的时候调用的方法以及参数是:"Gtk::VBox#pack_start( button, true, true)"。

 

因此,要使用GtkSimpleLayout,就首先要熟悉RubyGnome2的各个元件,容器的用法,以及参数。当你熟悉了RubyGnome2以后,用GtkSimpleLayout就会非常简单。

 

批量属性设置

在UI布局的时候,经常碰到要对一组UI元件设置相同的属性的情况,例如:

Ruby代码 复制代码
  1. hbox do  
  2.     button 'C':layout => [falsefalse, 5]   
  3.     button 'D':layout => [falsefalse, 5]   
  4.     button 'E':layout => [falsefalse, 5]   
  5. end  


 这个时候,可以用"with_attr"来简化:

Ruby代码 复制代码
  1. hbox  do  
  2.   with_attr :layout => [falsefalse, 5] do  
  3.     button 'C'  
  4.     button 'D'  
  5.     button 'E'  
  6.   end  
  7. end   

 

特殊容器

有些容器的放置子元件的时候有 特殊要求,例如Gtk::HPaned,左边子窗口要用Gtk::HPaned#add1()来添加,右边的用Gtk::HPaned#add2()。对于这种容器,GtkSimpleLayout要特别对待,就以hpaned为例:

Ruby代码 复制代码
  1. hpaned do  
  2.   area_first do  
  3.     frame 'first area'  
  4.   end  
  5.   area_second do  
  6.     frame 'second area'  
  7.   end  
  8. end  


  

需要特殊对待的容器有:

  • hpaned/vpaned : 用area_first和area_second来添加子窗口。
  • table : 用grid来填充格子。
  • nodebook : 用page来添加子页。

 

标识UI元件

GtkSimpleLayout用":id => ??"这个参数为UI元件进行标识,例如:

Ruby代码 复制代码
  1. hbox do  
  2.   button 'first':id => :btn_first  
  3.   button 'second':id => :btn_second  
  4. end  

之后,可以用component()函数取得这个UI元件:

Ruby代码 复制代码
  1. my_first_button = component(:btn_first)   
  2. my_second_button = component(:btn_second)   
  3.   
  4. ...   
  5. my_first_button.signal_connect('clicked'do  
  6.   puts "first button clicked"  
  7. end  
  8.   
  9. my_second_button.signal_connect('clicked'do  
  10.   puts "second button clicked"  
  11. end  

 

如果嫌麻烦,GtkSimpleLayout还提供了expose_components()用于自动将所有已标识的元件添加为实例读属性(getter):

Ruby代码 复制代码
  1. expose_components() # 将自动添加btn_first和btn_second这两个读属性(getter)。   
  2. ...   
  3. btn_first.signal_connect('clicked'do  
  4.   puts "first button clicked"  
  5. end  
  6.   
  7. btn_second.signal_connect('clicked'do  
  8.   puts "second button clicked"  
  9. end  

 

 

自动事件响应映射

如果你嫌显式调用signal_connect来注册事件麻烦,那么GtkSimpleLayout为你提供了自动事件响应映射的功能:

Ruby代码 复制代码
  1. require 'gtk2'  
  2. require 'simple_layout'  
  3.   
  4. class MyWin < Gtk::Window   
  5.   include SimpleLayout::Base   
  6.   def initialize   
  7.     super  
  8.     add my_layout   
  9.     register_auto_events()  # 注册自动事件响应映射   
  10.   end  
  11.   
  12.   def my_layout   
  13.     hbox do  
  14.       button "First', :btn_first  
  15.       button "Second":btn_second  
  16.     end  
  17.   end  
  18.   
  19.   # 事件响应函数   
  20.   def btn_first_on_clicked(*_)   
  21.     puts "First button clicked"  
  22.   end  
  23.   
  24.   # 事件响应函数   
  25.   def btn_second_on_clicked(*_)   
  26.     puts "Second button clicked"  
  27.   end  
  28.   
  29.   # 退出事件响应函数   
  30.   def self_on_destroy(*_)   
  31.     Gtk.main_quit   
  32.   end  
  33. end  

 

最后那个'self‘是指宿主容器。

 

UI分组

有时候你希望对UI元件进行分组,这样就可以对同一组的UI元件进行控制,如使能或禁止整个组。GtkSimpleLayout允许你在布局的时候指定UI组。

GtkSimpleLayout的UI分组规则如下:

  • 默认情况下,已命名的容器(即传入了:id参数)自动对自己所属的子元件建立一个组,组名就是容器明。
  • 如果容器传入:gid=>??参数,则以此名称为所属子元件建立组。
  • 允许多个容器的:gid名字相同,这种情况下所属子元件将归为同一个组。
  • 可以用“group”来显式对UI分组,group可以看作是一个虚拟的容器。

用component_children(group_name)来获取UI组。

 

由于UI分组的例子比较长不在此列出,请参阅源码中的examples/group.rb文件

 

 

UI与逻辑代码分离

由于GtkSimpleLayout潜在地迫使使用者分离界面代码和逻辑处理(或事件响应)代码,使得整个程序的层次结构更加清晰。对于界面元件比较多的程序,可以很方便的分区进行layout,因为layout的结果还是容器,这个容器又可以放入其他容器组合成更复杂的界面。

 

由于GtkSimpleLayout并不改变RubyGnome2的程序结构,你可以选择在你的程序中部分或全部使用GtkSimpleLayout。虽然本文所提供的例子都是静态布局,但由于GtkSimpleLayout是存代码构建UI,因此你完全可以在布局的时候传入变量,进行动态布局和动态生成UI,而仍然保持UI代码的“可视化”。

 

 

有兴趣者可以看看GtkSimpleLayout实现的代码,不过300行而已,这就是Ruby的魅力。

 

最后,贴上一个计算器的界面部分的代码例子,你能从代码中看出UI布局么?

Ruby代码 复制代码
  1. require 'gtk2'  
  2. require 'simple_layout'  
  3.   
  4. class MyWin < Gtk::Window   
  5.   include SimpleLayout::Base   
  6.   def initialize   
  7.     super  
  8.     add my_layout   
  9.     signal_connect('destroy'do  
  10.       Gtk.main_quit   
  11.     end  
  12.   end  
  13.   
  14.   def my_layout   
  15.     vbox do  
  16.       with_attr :border_width => 3 do  
  17.         hbox do  
  18.           entry :id => :ent_input:layout => [truetrue, 5]   
  19.         end  
  20.         hbox do  
  21.           frame do  
  22.             label 'M':set_size_request => [20, 20]   
  23.           end  
  24.           hbutton_box do  
  25.             button 'Backspace'  
  26.             button 'CE'  
  27.             button 'C'  
  28.           end  
  29.         end  
  30.         hbox do  
  31.           vbutton_box do  
  32.             button 'MC'  
  33.             button 'MR'  
  34.             button 'MS'  
  35.             button 'M+'  
  36.           end  
  37.           with_attr :layout => [truetruedo  
  38.             number_and_operators_layout   
  39.           end  
  40.         end  
  41.       end  
  42.     end  
  43.   end  
  44.   
  45.   def number_and_operators_layout   
  46.     vbox do  
  47.       [ ['7''8''9''/''sqt'],   
  48.         ['4''5''6''*''%'],   
  49.         ['1''2''3''-''1/x'],   
  50.         ['0''+/=''.''+''=']].each do |cols|   
  51.         hbox :layout => [truetruedo  
  52.           cols.each do |txt|   
  53.             button txt, :set_size_request => [20, 20], :layout => [truetrue]   
  54.           end  
  55.         end  
  56.       end  
  57.     end  
  58.   end  
  59.   
  60. end  
  61.   
  62. MyWin.new.show_all   
  63. Gtk.main  


 

Enjoy it :-)

原文连接:

阅读(696) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~