Ruby/Gtk2初嘗

Cichol發表於2014-12-19

環境配置

只需在命令列執行 gem install gtk2 並稍等片刻。
我使用的是windows,目前gtk2庫並不能很好相容64位ruby,體現在gem安裝出錯,linux的情況未知。

Hello, World!

讓我們從這句經典的問候開始吧。

require 'gtk2'

button = Gtk::Button.new("Hello World!")
button.signal_connect("clicked") {
  puts "Hello World!"
}

window = Gtk::Window.new("Example")
window.signal_connect("delete_event") {
  puts "delete event occurred"
  #true
  false
}

window.signal_connect("destroy") {
  puts "destroy event occurred"
  Gtk.main_quit
}

window.border_width = 50
window.add(button)
window.show_all

Gtk.main

這個HW似乎有點長,不過彈出的視窗仍然會讓人興奮不已。

解構

button = Gtk::Button.new("Hello World!")
button.signal_connect("clicked") {
  puts "Hello World!"
}

首先,我們新建了一個名為button的按鈕,並把它的label屬性設為"Hello World!"。接著我們使用了signal_connect方法,將button的點選訊號與列印字串的程式碼連結了起來。
Gtk的運作基於訊號和回撥的,程式執行時,會啟動一個主迴圈(即 Gtk.main ),這個主迴圈在接到訊號之前,不會進行任何工作。而當接到一個訊號,Gtk即會回撥對應這個事件的函式。在這個例子中,當button被點選的訊號傳給主迴圈,主迴圈便會呼叫 puts "Hello World!" ,在命令列列印一行字。

window = Gtk::Window.new("Example")
window.signal_connect("delete_event") {
  puts "delete event occurred"
  #true
  false
}

window.signal_connect("destroy") {
  puts "destroy event occurred"
  Gtk.main_quit
}

類似地,我們新建了一個title為"Example"的視窗,並新增了delete_event和destroy兩個事件。delete_event會在視窗的關閉鍵被點選時觸發,而這個事件會返回一個值,若為假則會呼叫destroy事件,這對實現“你確定要關掉它嗎?”很有用。而在destroy事件中,簡單明瞭地執行了退出Gtk主迴圈的命令。

window.border_width = 50
window.add(button)

視窗是承載按鈕等控制元件的容器,後者依賴前者以顯示。這段程式碼把button新增進了window。而border_width指的是視窗邊界距離內容物的寬度,設成50px讓這個視窗看起來有些臃腫,不過這麼做只是為了讓視窗的標題能正常顯示。
然後,執行 window.show_all 讓視窗和它包含的按鈕顯示,這和以下兩行是等價的。

button.show
window.show

最後,使用 Gtk.main 啟動Gtk主迴圈,開始等待事件訊號。

安裝Glade

Glade官網下載安裝檔案,Gtk2的設計需要使用Glade 3.8。

設計器的使用

先來瀏覽一下左側的控制元件欄,嗯,沒有什麼新奇的。
那麼首先新建一個Window,採取預設名字也就是 window1 了。右邊可以修改各種屬性,暫時只把 Common - Border width 改為10。 Gtk控制元件的排布需要藉助容器,在容器欄可以看到 Horizontal BoxTable 等等容器控制元件。嘗試一下將他們新增到 window1,例如 Table 會把視窗按照行列分成數格,就像傳統的HTML設計方法,接著就可以把按鈕等控制元件放置到每個格子裡。
然而利用表格設計顯然太不方便,實際上我們還有更好的選擇。
把剛才的 table1 容器刪掉,然後在 window1 中新增一個 Fixed 容器。試試在容器中新增一個按鈕 button1,可以發現這個按鈕會懸浮在你點選的位置。按住Shift鍵可以對按鈕的大小和位置進行調整。再新增一個文字框 label1,這個簡單的GUI作為示例大致足夠了。
將設計檔案儲存為 test.glade。它的內在大概是這個樣子:

<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <requires lib="gtk+" version="2.24"/>
  <!-- interface-naming-policy project-wide -->
  <object class="GtkWindow" id="window1">
    <property name="can_focus">False</property>
    <property name="border_width">10</property>
    <property name="window_position">center</property>
    <child>
      <object class="GtkFixed" id="fixed1">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <child>
          <object class="GtkButton" id="button1">
            <property name="label" translatable="yes">button</property>
            <property name="width_request">100</property>
            <property name="height_request">80</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
          </object>
          <packing>
            <property name="x">27</property>
            <property name="y">107</property>
          </packing>
        </child>
        <child>
          <object class="GtkLabel" id="label1">
            <property name="width_request">159</property>
            <property name="height_request">80</property>
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="label" translatable="yes">label</property>
          </object>
        </child>
      </object>
    </child>
  </object>
</interface>

使用Gtk::Builder繪製GUI

在同目錄新建一個ruby原始檔,輸入以下程式碼:

require 'gtk2'

builder = Gtk::Builder.new
builder.add_from_file('test.glade')
builder['window1'].signal_connect('destroy') { Gtk.main_quit }
builder['window1'].show_all
builder['button1'].signal_connect('clicked') { builder['label1'].label = 'Hello' }

Gtk.main

Gtk是利用 Gtk::Builder 對glade檔案進行讀取的,我們會新建一個Builder物件來繪製GUI。利用Builder物件可以直接對控制元件進行屬性和方法的操作,這段程式碼還是很好理解的。
執行程式碼,點選一下按鈕,會發現文字框的文字變成了"Hello"。由於我們並未對視窗的大小進行設定,所以實際上視窗大小是依據控制元件的位置和 Border width 決定的。

更規範的方法?

在查詢資料的過程中,我發現人們一般不會直接用Builder物件操作控制元件,而是會將控制元件的訊號與函式連結起來。在glade設計器裡,對控制元件可以設定 Signals 屬性。嘗試對 button1clicked 訊號進行設定,將其 Handler 設為 on_button1_clicked
儲存,然後執行以下ruby程式碼:

require 'gtk2'

class Builder < Gtk::Builder
  def initialize(file)
    super()
    self.add_from_file(file)
    self['window1'].signal_connect('destroy') { Gtk.main_quit }
    self['window1'].show_all
    self.connect_signals{ |handler| method(handler) }
  end
  def on_button1_clicked
    self['label1'].label = 'Hello'
  end
end

builder = Builder.new('test.glade')
Gtk.main

在這個例子中使用了更OO的方法,connect_signals 方法把控制元件的訊號和對應的函式都連結起來。比如剛才設定了 button1Handler,這裡就把 button1 的點選訊號和函式 on_button1_clicked 連結了起來。
雖然應該這才是規範的寫法,但是我對此不太理解。為每個訊號分配一個特定的函式,是不是降低了檢視和程式的分離度?直接使用控制元件 signal_connect 不是很好嗎?等待高人解答。

一個例項

附上我用gtk2做的一個IM實驗: https://github.com/CicholGricenchos/ruby-instant-messager

相關文章