300行代碼你能做什麼

我也標題黨一回: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:

 

Shoes.app {
  stack(:margin => 4) {
    button "Mice"
    button "Eagles"
    button "Quail"
  }
}

 

這樣,我們就可以從代碼中一下子看出UI佈局了。

 

builder風格的代碼和HTML很類似,對於熟悉HTML的web界面設計者來說,可視化的編輯器並沒有多大的必要。

 

 

RubyGnome2沒有爲我們提供builder方式的佈局,因此UI代碼寫起來就像:

 

class MyWin < Gtk::Window
  def initialize
    super
    vbox = Gtk::VBox.new
    btn_mice = Gtk::Button.new 'Mice'
    vbox.pack_start btn_mice
    btn_eagles = Gtk::Button.new 'Eagles'
    vbox.pack_start btn_eagles
    btn_quail = Gtk::Button.new 'Quail'
    vbox.pack_start btn_quail
    add vbox
  end
end
  

從上面的代碼中很難一下子看出UI佈局。

 

如果也爲RubyGnome2構建一個builder風格的佈局器,那麼代碼就會變成:

 

class MyWin < Gtk::Window

  def initialize
    super
    add my_layout
  end

  def my_layout
    vbox do
      button 'Mice'
      button 'Eagles'
      button 'Quail'
    end
  end

end
 

 

嗯,這個代碼就和Shose差不多了,可以從代碼中一眼看出UI佈局。

 

本文所介紹的GtkSimpleLayout其功能之一就是爲RubyGnome2提供builder風格的佈局器。

 

GtkSimpleLayout佈局器

這個簡單的佈局器原先只有200行不到的代碼,我經常是直接拷貝到項目中使用。後來逐漸添了些功能,覺得它變得更有用了,於是便發佈到github生成gem,方便感興趣者使用。

 

Source: git://github.com/rickyzheng/GtkSimpleLayout.git

or:

gem source -a http://gems.github.com && gem install rickyzheng-GtkSimpleLayout

 

以下是主要功能介紹以及簡單例子。

 

提供Builder風格佈局

正如上面的例子中所介紹的,GtkSimpleLayout爲RubyGnome2帶來了builder風格的佈局功能,只需要爲佈局的類擴展GtkSimpleLayout::Base即可,一個完整的例子:

 

require 'gtk2'
require 'simple_layout'

class MyWin < Gtk::Window
  include SimpleLayout::Base
  def initialize
    super
    add my_layout
    signal_connect('destroy') do
      Gtk.main_quit
    end
  end

  def my_layout
    hbox do
        label 'Hello, '
        button 'World !'
      end
  end
end

MyWin.new.show_all
Gtk.main

 

 

從上面的例子中可以看出,GtkSimpleLayout並沒有改變RubyGnome2程序的主框架,它只是一個擴充。

 

 

屬性設置

在放置UI元件的時候,往往需要設置初始屬性,或者要指定佈局參數。GtkSimpleLayout用Hash來傳遞這些屬性與參數,例如:

 

vbox do
  button 'sensitive = false', :sensitive => false  # 初始爲disable狀態
  button 'expand space', :layout => [true, true]  # 指定這個button填充剩餘空間
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元件設置相同的屬性的情況,例如:

hbox do
    button 'C', :layout => [false, false, 5]
    button 'D', :layout => [false, false, 5]
    button 'E', :layout => [false, false, 5]
end


 這個時候,可以用"with_attr"來簡化:

hbox  do
  with_attr :layout => [false, false, 5] do
    button 'C'
    button 'D'
    button 'E'
  end
end 

 

特殊容器

有些容器的放置子元件的時候有 特殊要求,例如Gtk::HPaned,左邊子窗口要用Gtk::HPaned#add1()來添加,右邊的用Gtk::HPaned#add2()。對於這種容器,GtkSimpleLayout要特別對待,就以hpaned爲例:

hpaned do
  area_first do
    frame 'first area'
  end
  area_second do
    frame 'second area'
  end
end


  

需要特殊對待的容器有:

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

 

標識UI元件

GtkSimpleLayout用":id => ??"這個參數爲UI元件進行標識,例如:

hbox do
  button 'first', :id => :btn_first
  button 'second', :id => :btn_second
end
 

之後,可以用component()函數取得這個UI元件:

my_first_button = component(:btn_first)
my_second_button = component(:btn_second)

...
my_first_button.signal_connect('clicked') do
  puts "first button clicked"
end

my_second_button.signal_connect('clicked') do
  puts "second button clicked"
end

 

如果嫌麻煩,GtkSimpleLayout還提供了expose_components()用於自動將所有已標識的元件添加爲實例讀屬性(getter):

expose_components() # 將自動添加btn_first和btn_second這兩個讀屬性(getter)。
...
btn_first.signal_connect('clicked') do
  puts "first button clicked"
end

btn_second.signal_connect('clicked') do
  puts "second button clicked"
end

 

 

自動事件響應映射

如果你嫌顯式調用signal_connect來註冊事件麻煩,那麼GtkSimpleLayout爲你提供了自動事件響應映射的功能:

require 'gtk2'
require 'simple_layout'

class MyWin < Gtk::Window
  include SimpleLayout::Base
  def initialize
    super
    add my_layout
    register_auto_events()  # 註冊自動事件響應映射
  end

  def my_layout
    hbox do
      button "First', :btn_first
      button "Second", :btn_second
    end
  end

  # 事件響應函數
  def btn_first_on_clicked(*_)
    puts "First button clicked"
  end

  # 事件響應函數
  def btn_second_on_clicked(*_)
    puts "Second button clicked"
  end

  # 退出事件響應函數
  def self_on_destroy(*_)
    Gtk.main_quit
  end
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佈局麼?

require 'gtk2'
require 'simple_layout'

class MyWin < Gtk::Window
  include SimpleLayout::Base
  def initialize
    super
    add my_layout
    signal_connect('destroy') do
      Gtk.main_quit
    end
  end

  def my_layout
    vbox do
      with_attr :border_width => 3 do
        hbox do
          entry :id => :ent_input, :layout => [true, true, 5]
        end
        hbox do
          frame do
            label 'M', :set_size_request => [20, 20]
          end
          hbutton_box do
            button 'Backspace'
            button 'CE'
            button 'C'
          end
        end
        hbox do
          vbutton_box do
            button 'MC'
            button 'MR'
            button 'MS'
            button 'M+'
          end
          with_attr :layout => [true, true] do
            number_and_operators_layout
          end
        end
      end
    end
  end

  def number_and_operators_layout
    vbox do
      [ ['7', '8', '9', '/', 'sqt'],
        ['4', '5', '6', '*', '%'],
        ['1', '2', '3', '-', '1/x'],
        ['0', '+/=', '.', '+', '=']].each do |cols|
        hbox :layout => [true, true] do
          cols.each do |txt|
            button txt, :set_size_request => [20, 20], :layout => [true, true]
          end
        end
      end
    end
  end

end

MyWin.new.show_all
Gtk.main

 

 

 

Enjoy it :-)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章