锁定老帖子 主题:Ruby: GUI编程的利器
该帖已经被评为良好帖
|
|
---|---|
作者 | 正文 |
发表时间:2007-12-14
最近手上的一个项目刚好需要做一个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,有望在又一个领域成为编程利器。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2008-08-14
FxRuby,这个怎么样?
|
|
返回顶楼 | |
发表时间:2008-08-15
g.zhen.ning 写道 FxRuby,这个怎么样? 从binding水平来看,FxRuby做的算比较好的了,和Ruby/TK一样,都能很好地体现ruby way. 但是毕竟Fox的应用面没有GTK广,在一些需要深度定制界面的场合,或者国际化方面,GTK可能做得更好一些。另外,GTK的外观个人觉得比Fox好看。 在快速构建程序方面,FxRuby会有优势。 |
|
返回顶楼 | |
发表时间:2008-08-31
Gtk库在ruby 1.9.0下部能用了 。。怎么办?
|
|
返回顶楼 | |
发表时间:2008-08-31
adobe air or flex?
fxruby 有本书来着 |
|
返回顶楼 | |
发表时间:2008-08-31
wxz125627771 写道 Gtk库在ruby 1.9.0下部能用了 。。怎么办?
由于Ruby/GTK不是用SWIG自动生成代码,所以迁移到1.9估计要一段时间,不过1.9的正式版不是还没有出来么~ |
|
返回顶楼 | |
发表时间: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 |
|
返回顶楼 | |
发表时间:2008-09-01
这个帖子颇有意淫的感觉,在VB里写个界面需要你动手写一行代码吗?
但是一旦GUI元素多起来,且UI元素存在复杂关系,就很难维护 --- 这是明显的诋毁,或者是自己水平太臭,难道一个复杂的UI,不懂得定制control吗? |
|
返回顶楼 | |
发表时间: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元素的移动和再组合就非常方便。 |
|
返回顶楼 | |
发表时间: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里,那他就是个蹩脚货,或者是 写规划需求的人是个蹩脚货. |
|
返回顶楼 | |