分享

Ruby: GUI编程的利器

 guitarhua 2013-08-16

  • 300行代码你能做什么
  • 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./


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

Java代码  收藏代码
  1. button = Gtk::Button.new("Button A")  
  2. button.signal_connect("clicked"do  
  3.   # ... when button clicked ...  
  4.   msgbox "Button clicked !"  
  5. end  


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

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

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

Java代码  收藏代码
  1. button = Gtk::Button.new("Button A")  
  2. label = Gtk::Label.new("Hello")  
  3. button.signal_connect("clicked"do  
  4.   label.text += "click "  
  5. end  


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


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

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

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


Java代码  收藏代码
  1. require 'gtk2'  
  2.   
  3. =begin  
  4.  Msgbox: an easy message box based on Gtk::MessageDialog  
  5.  usage:  
  6.   example 1:  
  7.     Msgbox.new("This is a simple message box !").show  
  8.       
  9.   example 2:  
  10.     if Msgbox.new("Yes or No ?", :type => :QUESTION, :buttons => :YES_NO).show  
  11.       puts "Your answer is: 'yes'"  
  12.     else  
  13.       puts "Your answer is not 'yes'"  
  14.     end  
  15.       
  16.   example 3:  
  17.     Msgbox.new("OK or cancel ?", :type => :QUESTION, :buttons => :OK_CANCEL) do  
  18.       puts "Your answer: ok"  
  19.     end  
  20.       
  21.   example 4, from within Gtk::Window or subclass:  
  22.     msgbox "Hello"  
  23.     msgbox! "warning infomation !"  
  24.     msgbox_err "error !"  
  25.     msgbox? "answer the question ...", :buttons=>:YES_NO  
  26.       
  27. =end  
  28.   
  29. class Msgbox  
  30.     
  31.   def initialize(text = nil, param = {}, &block)  
  32.     @param = {}  
  33.     @param[:block] ||= block                      
  34.     if @param[:block]  
  35.       show(text, param)  
  36.     else  
  37.       set_params(text, param)  
  38.     end  
  39.   end  
  40.   
  41.   def set_params(text = nil, param = {})  
  42.     @param[:parent] ||= param[:parent]  
  43.     @param[:text] ||= text  
  44.     @param[:buttons] = case param[:buttons]  
  45.                       when :CANCEL, :cancel, "CANCEL""cancel"  
  46.                         Gtk::MessageDialog::BUTTONS_CANCEL  
  47.                       when :CLOSE, :close, "CLOSE""close"  
  48.                         Gtk::MessageDialog::BUTTONS_CLOSE  
  49.                       when :OK,:ok, "OK""ok"  
  50.                         Gtk::MessageDialog::BUTTONS_OK  
  51.                       when :OK_CANCEL,:ok_cancel, "OK_CANCEL""ok_cancel"  
  52.                         Gtk::MessageDialog::BUTTONS_OK_CANCEL  
  53.                       when :YES_NO, :yes_no, "YES_NO""yes_no"  
  54.                         Gtk::MessageDialog::BUTTONS_YES_NO  
  55.                       when :NONE, :none, "NONE""none"  
  56.                         Gtk::MessageDialog::BUTTONS_NONE  
  57.                       else  
  58.                         @param[:buttons] || Gtk::MessageDialog::BUTTONS_OK  
  59.                       end  
  60.     @param[:flags] ||= Gtk::Dialog::MODAL  
  61.     @param[:title] ||= param[:title]  
  62.     @param[:type] =  case param[:type]  
  63.                     when :ERROR  
  64.                       @param[:title] ||= "Error"  
  65.                       Gtk::MessageDialog::ERROR  
  66.                     when :INFO  
  67.                       @param[:title] ||= "Information"  
  68.                       Gtk::MessageDialog::INFO  
  69.                     when :QUESTION  
  70.                       @param[:title] ||= "Question"  
  71.                       Gtk::MessageDialog::QUESTION  
  72.                     when :WARNING  
  73.                       @param[:title] ||= "Warning"  
  74.                       Gtk::MessageDialog::WARNING  
  75.                     else  
  76.                       @param[:title] ||= "Information"  
  77.                       @param[:type] || Gtk::MessageDialog::INFO  
  78.                     end  
  79.   end    
  80.     
  81.   def show(text = nil, param = {}, &block)  
  82.     set_params(text, param)  
  83.     dialog = Gtk::MessageDialog.new(@param[:parent], @param[:flags], @param[:type], @param[:buttons], @param[:text])  
  84.     dialog.title = @param[:title]  
  85.     dialog.signal_connect('response'do |w, response|  
  86.       @response = case response  
  87.                   when Gtk::Dialog::RESPONSE_ACCEPT, Gtk::Dialog::RESPONSE_OK, Gtk::Dialog::RESPONSE_APPLY, Gtk::Dialog::RESPONSE_YES  
  88.                     true  
  89.                   else  
  90.                     false  
  91.                   end  
  92.       dialog.destroy  
  93.     end  
  94.     if @param[:parent]  
  95.       x, y = @param[:parent].position  
  96.       w, h = @param[:parent].size  
  97.       dw, dh = dialog.size  
  98.       dialog.move x + (w - dw) / 2, y + (h - dh) / 2  
  99.     end  
  100.     dialog.run  
  101.     @param[:block] ||= block  
  102.     block.call if @param[:block] && @response  
  103.     @response  
  104.   end  
  105.     
  106. end  
  107.   
  108. class Gtk::Window  
  109.   def msgbox(text = nil, param = {}, &block)  
  110.     param[:parent] ||= self  
  111.     param[:block] ||= block  
  112.     Msgbox.new(text, param).show  
  113.   end  
  114.     
  115.   def msgbox!(text = nil, param = {}, &block)  
  116.     msgbox(text, param.merge!({:type=>:WARNING, :block=>block}))  
  117.   end  
  118.     
  119.   def msgbox_err(text = nil, param = {}, &block)  
  120.     msgbox(text, param.merge!({:type=>:ERROR, :block=>block}))  
  121.   end  
  122.     
  123.   def msgbox?(text = nil, param = {}, &block)  
  124.     msgbox(text, param.merge!({:type=>:QUESTION, :block=>block}))  
  125.   end  
  126.     
  127. end  
  128.   
  129.   
  130. if $0 == __FILE__  
  131.   
  132. class TestWin < Gtk::Window  
  133.   def initialize  
  134.     super("Message Box Test")  
  135.       
  136.     box = Gtk::HButtonBox.new  
  137.     buts = []  
  138.     ["Info""Warn""Error""Question"].each do |t|  
  139.       buts << (but = Gtk::Button.new(t))  
  140.       box.pack_start but  
  141.     end  
  142.       
  143.     buts[0].signal_connect("clicked"do   
  144.       msgbox "Hello"  
  145.     end  
  146.       
  147.     buts[1].signal_connect("clicked"do   
  148.       msgbox! "Hello !"  
  149.     end  
  150.       
  151.     buts[2].signal_connect("clicked"do   
  152.       msgbox_err "Hello, Hello, Hello !!", :title=>"Error happens !"  
  153.     end  
  154.       
  155.     buts[3].signal_connect("clicked"do   
  156.       if msgbox? "Hello ?", :buttons=>:YES_NO  
  157.         msgbox "you select 'YES'"  
  158.       else  
  159.         msgbox "you don't select 'YES'"  
  160.       end  
  161.     end  
  162.       
  163.     signal_connect("delete-event"do  
  164.       Gtk.main_quit  
  165.       false  
  166.     end  
  167.     add(box)      
  168.   end   
  169.       
  170. end  
  171.   
  172. win = TestWin.new  
  173. win.show_all  
  174. GC.start  
  175. Gtk.main  
  176.   
  177. end  


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


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



    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多