论坛首页 编程语言技术论坛

Ruby: GUI编程的利器

浏览 21287 次
该帖已经被评为良好帖
作者 正文
   发表时间:2007-12-14  
ruby语言由于其灵活优雅的表达方式和优秀的OO的特性,是GUI编程语言的有力竞争者。特别是其Closure特性,能够使GUI编程时遇到的很多头痛的问题迎刃而解。


最近手上的一个项目刚好需要做一个Windows平台的GUI程序,以前是用VB,虽然VB是Windows GUI的经典工具,能够快速进行GUI原型开发,但是一旦GUI元素多起来,且UI元素存在复杂关系,就很难维护....特别在后期,一旦需求有什么变化,再去调整UI,那个叫痛苦啊。因此就想用ruby试试,加上此次项目设计很多网络通讯方面的需求,因此更加坚定了使用ruby的决心。现在项目基本完工,再回过头看,以前用VB开发时碰到的种种问题在新项目中都被很好地解决了。特别地,体会到了Closure对于GUI编程的重要性。不管未来在的GUI编程领域ruby是否能成为主流,但是可以预见那种语言一定是具备Closure(或类似)功能的。(或者只是我的美好愿望?)


GUI库选型:
ruby发行包自带TK库,用于简单的程序还可以,但是一旦有复杂界面需求时就难以满足。目前比较成熟的GUI绑定库有RubyFox,wxRuby 和 RubyGnome. 鉴于GTK用的人比较多,加上GTK在Windows上的Runtime也是比较稳定,GTK应用的代表GIMP看起来也比较漂亮,因此就选择了RubyGnome作为GUI库。

关于RubyGnome我也不多介绍,其项目主页上的文档和教程非常不错。
Ruby-Gnome项目的首页: http://ruby-gnome2.sourceforge.jp/


1. Closure 作为响应GUI消息事件
在MFC中,响应消息通常需要定义OnXXX()虚函数,而且需要在消息传递宏里面与某个消息挂上勾,然后在实现OnXXX()函数。
在VB中,IDE为你为某个控件的消息生成消息响应函数。
那么在Ruby-Gnome里面,这么做:

button = Gtk::Button.new("Button A")
button.signal_connect("clicked") do
  # ... when button clicked ...
  msgbox "Button clicked !"
end


在这一点上,MFC最为繁琐不用说了。VB由IDE为你预先做了很多工作。ruby用代码关联“clicked”事件,用Closure作为消息响应,干净利落。
表面上看,似乎ruby的方式也未必好很多,但是且慢,看下一个....

2. Closure 里面可以访问当前上下文
GUI编程经常面临的一个头痛的问题是,UI元件通常需要是全局的,至少是窗口类内全局。例如,希望button被按下的时候改变label的内容,那么就要求在响应button事件的代码内要能够访问label。在MFC中,label被迫成为全局。在VB中,你不能控制。在界面元素很多的时候,这可能会成为一个问题--你不得不仔细地为每一个UI元件命名以防止名称冲突。

而在ruby中,由于Closure能够访问当前上下文,因此正好可以完美解决这个问题:

button = Gtk::Button.new("Button A")
label = Gtk::Label.new("Hello")
button.signal_connect("clicked") do
  label.text += "click "
end


ruby的Closure使得代码“内聚”了,即相互关联的元素的作用域可以被限定在一个很小的范围,这样对于代码的维护和应付变化都是具有非凡的意义。


3. 动态打开一个类的能力使得扩展基类的功能变得简单

ruby能够动态地打开一个类并往里面增加method的能力已经不是什么新鲜事,对于这个特性也有很多争议。但对于GUI编程来说,这确实是提供了很大的方便。

在GUI编程中,msgbox是很常用的一个工具。在RubyGnome中,Gtk::Window没有msgbox这个接口,下面的例子就是封装了一个易用的Msgbox类,并打开Gtk::Window类,增加msgbox函数,这样所有基于Gtk::Window的类都可以随时调用msgbox:


require 'gtk2'

=begin
 Msgbox: an easy message box based on Gtk::MessageDialog
 usage:
  example 1:
    Msgbox.new("This is a simple message box !").show
    
  example 2:
    if Msgbox.new("Yes or No ?", :type => :QUESTION, :buttons => :YES_NO).show
      puts "Your answer is: 'yes'"
    else
      puts "Your answer is not 'yes'"
    end
    
  example 3:
    Msgbox.new("OK or cancel ?", :type => :QUESTION, :buttons => :OK_CANCEL) do
      puts "Your answer: ok"
    end
    
  example 4, from within Gtk::Window or subclass:
    msgbox "Hello"
    msgbox! "warning infomation !"
    msgbox_err "error !"
    msgbox? "answer the question ...", :buttons=>:YES_NO
    
=end

class Msgbox
  
  def initialize(text = nil, param = {}, &block)
    @param = {}
    @param[:block] ||= block                    
    if @param[:block]
      show(text, param)
    else
      set_params(text, param)
    end
  end

  def set_params(text = nil, param = {})
    @param[:parent] ||= param[:parent]
    @param[:text] ||= text
    @param[:buttons] = case param[:buttons]
                      when :CANCEL, :cancel, "CANCEL", "cancel"
                        Gtk::MessageDialog::BUTTONS_CANCEL
                      when :CLOSE, :close, "CLOSE", "close"
                        Gtk::MessageDialog::BUTTONS_CLOSE
                      when :OK,:ok, "OK", "ok"
                        Gtk::MessageDialog::BUTTONS_OK
                      when :OK_CANCEL,:ok_cancel, "OK_CANCEL", "ok_cancel"
                        Gtk::MessageDialog::BUTTONS_OK_CANCEL
                      when :YES_NO, :yes_no, "YES_NO", "yes_no"
                        Gtk::MessageDialog::BUTTONS_YES_NO
                      when :NONE, :none, "NONE", "none"
                        Gtk::MessageDialog::BUTTONS_NONE
                      else
                        @param[:buttons] || Gtk::MessageDialog::BUTTONS_OK
                      end
    @param[:flags] ||= Gtk::Dialog::MODAL
    @param[:title] ||= param[:title]
    @param[:type] =  case param[:type]
                    when :ERROR
                      @param[:title] ||= "Error"
                      Gtk::MessageDialog::ERROR
                    when :INFO
                      @param[:title] ||= "Information"
                      Gtk::MessageDialog::INFO
                    when :QUESTION
                      @param[:title] ||= "Question"
                      Gtk::MessageDialog::QUESTION
                    when :WARNING
                      @param[:title] ||= "Warning"
                      Gtk::MessageDialog::WARNING
                    else
                      @param[:title] ||= "Information"
                      @param[:type] || Gtk::MessageDialog::INFO
                    end
  end  
  
  def show(text = nil, param = {}, &block)
    set_params(text, param)
    dialog = Gtk::MessageDialog.new(@param[:parent], @param[:flags], @param[:type], @param[:buttons], @param[:text])
    dialog.title = @param[:title]
    dialog.signal_connect('response') do |w, response|
      @response = case response
                  when Gtk::Dialog::RESPONSE_ACCEPT, Gtk::Dialog::RESPONSE_OK, Gtk::Dialog::RESPONSE_APPLY, Gtk::Dialog::RESPONSE_YES
                    true
                  else
                    false
                  end
      dialog.destroy
    end
    if @param[:parent]
      x, y = @param[:parent].position
      w, h = @param[:parent].size
      dw, dh = dialog.size
      dialog.move x + (w - dw) / 2, y + (h - dh) / 2
    end
    dialog.run
    @param[:block] ||= block
    block.call if @param[:block] && @response
    @response
  end
  
end

class Gtk::Window
  def msgbox(text = nil, param = {}, &block)
    param[:parent] ||= self
    param[:block] ||= block
    Msgbox.new(text, param).show
  end
  
  def msgbox!(text = nil, param = {}, &block)
    msgbox(text, param.merge!({:type=>:WARNING, :block=>block}))
  end
  
  def msgbox_err(text = nil, param = {}, &block)
    msgbox(text, param.merge!({:type=>:ERROR, :block=>block}))
  end
  
  def msgbox?(text = nil, param = {}, &block)
    msgbox(text, param.merge!({:type=>:QUESTION, :block=>block}))
  end
  
end


if $0 == __FILE__

class TestWin < Gtk::Window
  def initialize
    super("Message Box Test")
    
    box = Gtk::HButtonBox.new
    buts = []
    ["Info", "Warn", "Error", "Question"].each do |t|
      buts << (but = Gtk::Button.new(t))
      box.pack_start but
    end
    
    buts[0].signal_connect("clicked") do 
      msgbox "Hello"
    end
    
    buts[1].signal_connect("clicked") do 
      msgbox! "Hello !"
    end
    
    buts[2].signal_connect("clicked") do 
      msgbox_err "Hello, Hello, Hello !!", :title=>"Error happens !"
    end
    
    buts[3].signal_connect("clicked") do 
      if msgbox? "Hello ?", :buttons=>:YES_NO
        msgbox "you select 'YES'"
      else
        msgbox "you don't select 'YES'"
      end
    end
    
    signal_connect("delete-event") do
      Gtk.main_quit
      false
    end
    add(box)    
  end 
    
end

win = TestWin.new
win.show_all
GC.start
Gtk.main

end



上面的例子来源于实际项目,为了使用方便做了很多封装,后面还有一段测试代码,所以有点长。如果你也用RubyGnome开发GUI,那么这个简易的Msgbox将会带来很多方便


Ruby作为GUI编程语言现在还不会成为主流,但是其动态特性将有助于解决传统GUI编程中遇到的问题,而且随着GUI binding lib的成熟,稳定,Ruby,有望在又一个领域成为编程利器。



   发表时间:2008-08-14  
FxRuby,这个怎么样?
0 请登录后投票
   发表时间:2008-08-15  
g.zhen.ning 写道
FxRuby,这个怎么样?

从binding水平来看,FxRuby做的算比较好的了,和Ruby/TK一样,都能很好地体现ruby way. 但是毕竟Fox的应用面没有GTK广,在一些需要深度定制界面的场合,或者国际化方面,GTK可能做得更好一些。另外,GTK的外观个人觉得比Fox好看。

在快速构建程序方面,FxRuby会有优势。

0 请登录后投票
   发表时间:2008-08-31  
Gtk库在ruby 1.9.0下部能用了 。。怎么办?
0 请登录后投票
   发表时间:2008-08-31  
adobe air or flex?
fxruby 有本书来着
0 请登录后投票
   发表时间:2008-08-31  
wxz125627771 写道
Gtk库在ruby 1.9.0下部能用了 。。怎么办?


由于Ruby/GTK不是用SWIG自动生成代码,所以迁移到1.9估计要一段时间,不过1.9的正式版不是还没有出来么~


0 请登录后投票
   发表时间:2008-08-31  
借助Ruby闭包,GUI编程确实可以变得很简洁。
顺这个帖子推荐一下Shoes,一个基于Ruby的小巧GUI工具包:
http://code.whytheluckystiff.net/shoes/
可运行在windows/macos/ubuntu(gtk)下面,http://the-shoebox.org/是Shoes应用的集中地。

我们可以看到一个经典的扫雷游戏只用270行ruby代码就可以搞定,这270行包括用代码"画"地雷/旗子/数字等等,可以和Java来对比看实现需要多少行:
http://the-shoebox.org/apps/36
0 请登录后投票
   发表时间:2008-09-01  
这个帖子颇有意淫的感觉,在VB里写个界面需要你动手写一行代码吗?


但是一旦GUI元素多起来,且UI元素存在复杂关系,就很难维护 --- 这是明显的诋毁,或者是自己水平太臭,难道一个复杂的UI,不懂得定制control吗?
0 请登录后投票
   发表时间:2008-09-01  
ray_linn 写道
这个帖子颇有意淫的感觉,在VB里写个界面需要你动手写一行代码吗?


有利必有弊。有时候可视化的拖拖放放反而不如写代码容易。

ray_linn 写道
但是一旦GUI元素多起来,且UI元素存在复杂关系,就很难维护 --- 这是明显的诋毁,或者是自己水平太臭,难道一个复杂的UI,不懂得定制control吗?


年轻人,拜托下次回贴之前动动脑子,诋毁?我和VB有仇?定制control很难么?你认为有很多人不会么?

定制control一般用于控件,如果标准的控件能做到,一般不会去再造轮子。如果你是指定制control用于作为一个容器来规划GUI,那么它的成本太高,当你想把原先属于A group的控件放到B group将会很痛苦。如果定制control象MS Word里面对图形元素'group'和'ungroup'那么方便,那倒也不失为一个不错的方法。

相比而言在Ruby/GTK中,由于GUI元素在代码控制之下,加上duck typing,GUI元素的移动和再组合就非常方便。

0 请登录后投票
   发表时间:2008-09-02  
rubynroll 写道
ray_linn 写道
这个帖子颇有意淫的感觉,在VB里写个界面需要你动手写一行代码吗?


有利必有弊。有时候可视化的拖拖放放反而不如写代码容易。

ray_linn 写道
但是一旦GUI元素多起来,且UI元素存在复杂关系,就很难维护 --- 这是明显的诋毁,或者是自己水平太臭,难道一个复杂的UI,不懂得定制control吗?


年轻人,拜托下次回贴之前动动脑子,诋毁?我和VB有仇?定制control很难么?你认为有很多人不会么?

定制control一般用于控件,如果标准的控件能做到,一般不会去再造轮子。如果你是指定制control用于作为一个容器来规划GUI,那么它的成本太高,当你想把原先属于A group的控件放到B group将会很痛苦。如果定制control象MS Word里面对图形元素'group'和'ungroup'那么方便,那倒也不失为一个不错的方法。

相比而言在Ruby/GTK中,由于GUI元素在代码控制之下,加上duck typing,GUI元素的移动和再组合就非常方便。



有时候可视化的拖拖放放反而不如写代码容易...
你题目里所谓的复杂的界面,你慢慢用代码写出来,估计我都弄完了,你还在那里爬呢.

你的回复,只能说明你规划control的能力很弱.Control的代价远比单纯摆放UI元素要低,而且局部修改不影响大局.
如果有个程序员成天需要把A Control放到B Control里,那他就是个蹩脚货,或者是 写规划需求的人是个蹩脚货.

0 请登录后投票
论坛首页 编程语言技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics