Ruby: GUI编程的利器

[b]ruby语言由于其灵活优雅的表达方式和优秀的OO的特性,是GUI编程语言的有力竞争者。特别是其Closure特性,能够使GUI编程时遇到的很多头痛的问题迎刃而解。
[/b]

最近手上的一个项目刚好需要做一个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里面,这么做:

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

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

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

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

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

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


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

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

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


[code]
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

[/code]

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


[b]Ruby作为GUI编程语言现在还不会成为主流,但是其动态特性将有助于解决传统GUI编程中遇到的问题,而且随着GUI binding lib的成熟,稳定,Ruby,有望在又一个领域成为编程利器。
[/b]
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ShenmeGUI是一套受Shoes启发而诞生的GUI工具,拥有相似的DSL语法,使用HTML构建界面,并实现了前后端数据的双向绑定,可以便捷地实现一些轻量的GUI应用。安装执行 gem install shenmegui 。示例代码require 'shenmegui' ShenmeGUI.app do   form(title: 'Your Application') do     button('alert').onclick do       alert 'Hello World!'     end     button('open an image').onclick do       path = get_open_file_name       @t.text = path       @i.src = path     end     stack do       label 'image path:'       @t = textarea '', width: '100%'     end     @i = image "http://7jpqbr.com1.z0.glb.clouddn.com/bw-2014-06-19.jpg"     @p = progress(75)     button(' ').onclick { @p.percent  = 5 }     button('-').onclick { @p.percent -= 5 }   end end ShenmeGUI.start!将会产生如图所示的界面:如未自动打开浏览器,可手动打开程序代码同目录的index.html。button定义按钮,并通过onclick绑定上了点击事件。第一个按钮弹出一个对话框,第二个按钮弹出一个打开文件的对话框,将文件路径写到下方定义的textarea里,并改变image的src以显示这个图片。下方的两个按钮演示了进度条的增减。系统需求Ruby版本大于等于2.0.0。因为前后端通讯使用了websocket,所以需要使用支持websocket的浏览器。目前打开文件对话框只实现了windows版本,在Linux等使用会出错,以后会尝试在其他系统实现,除此之外对系统没有要求。 标签:ShenmeGUI  GUI开发框架
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值