RubyGnome2 ライブラリを用いた GTK 環境での Ruby GUI プログラミングの基本的な考え方
前書き
RubyGnome2ライブラリがどんどん良くなり、ruby1.9の性能向上もあって、RubyでGUIプログラムを書くことは、趣味から仕事の主要な部分へと徐々に移行しています。
Rubyでプログラムを書くのは本当に楽しくて、自分のアイデアを素早く、エレガントに実装することができます。この記事で紹介するある逸品は、RubyのGUIプログラムを簡単に楽しく書けるようにする、非常に興味深い機能をわずかなコードで実現した例です。
RubyGnome2入門
RubyGnome2はこれまで何度も紹介してきましたが、改めてお勧めします。RubyでGUIプログラムを書くには、本当に最初の選択肢です。
RubyGnome2 は GTK+ ライブラリを Ruby で拡張したものです。GTK+のオブジェクトモデルをRubyで注意深くラップし、GTK+のAPIの命名法と意味を保持します。したがって、GTK+のドキュメントはRubyGnome2にも適用可能です。
GTK自体はCで書かれていますが、慎重に設計されたオブジェクトのシステム全体を持っているため、そのGUIコンポーネントは非常に柔軟で、自由に組み合わせて複雑で強力なインターフェースを実装することができます。
GTKはオブジェクトシステムの柔軟性に重点を置いているため、GTKを使ったプログラミングは、最初は簡単ではありません。表面的には単純に見える機能でも、GTKで実装するためには何度か回り道をしなければならないことが多々あります。例えば、ラベルのフォントを設定したい場合、GTKのフォントレンダリングのトランザクションをすべて引き継ぐPangoを介してそれを行う必要があります・・・。このような余分な回り道をするケースはたくさんあり、GTKのプログラムを書くのが簡単ではなくなります。それはGTKのプログラムを書くことを簡単にはしませんが、その代わりにGTKシステム全体が少ない費用で非常に柔軟で強力になり、クロスプラットフォーム、スキニング、国際化において非常に優れています。
RubyGnome2は、GTKの長所と短所を含むすべての機能を継承しています。
GUIレイアウト
GTKプログラムのレイアウトは柔軟で、多くの種類のコンテナから選択することができます。レイアウトに関しては、GUIプログラムが異なる解像度のスクリーンに適応し、特定のスタイルのUIを微調整しやすくするために、絶対位置よりも相対位置を推奨するものがほとんどです。
最も一般的なコンテナはボックスで、水平ボックスと垂直ボックスがあります。Visual UI コンポーネントは "boxes" に配置され、異なる "boxes" を互いに積み重ねて目的のレイアウトを構築することができます。
理論的には、どんな相対位置のレイアウトもボックスで構築できますが、利便性のために、GTKはテーブルのようなより高度なコンテナも提供しています。
ボックスモデルは、GTKプログラミングを初めて行う多くの人にとって馴染みにくく、Gladeのようなビジュアルレイアウトツールでも、初心者はボックスモデルに悩まされることが多いようです。ビジュアルレイアウトツールは、固定位置のレイアウトに最適です。相対位置レイアウトの場合は、代わりにGladeを使うよりも、コードでインターフェイスを構築した方が早くて簡単な場合が多いのです。
shoose.netの以下の例のように、UIレイアウトをコードで視覚化できるように、ビルダー形式のコードでUIを記述するShoseなどのGUIライブラリがあります。
Shoes.app {
stack(:margin => 4) {
button "Mice"
button "Eagles"
button "Quail"
}
}
こうすることで、コードからUIレイアウトを一度に確認することができるのです。
ビルダースタイルのコードはHTMLに非常に似ており、HTMLに慣れたWebインターフェース設計者にとっては、ビジュアルエディタはあまり必要ありません。
RubyGnome2にはレイアウトのためのビルダー的なアプローチは用意されていないので、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 用のビルダー風レイヤーも構築すると、以下のようなコードになります。
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 用にビルダースタイルのレイアウトを提供することです。
GtkSimpleLayout レイアウト
このシンプルなレイアウトは元々200行に満たないコードで、プロジェクトにそのままコピーして使うことが多かったです。その後、徐々に機能を追加していき、より便利になってきたので、githubにリリースしてgemを生成し、興味のある方に使っていただいています。
ソース: git://github.com/rickyzheng/GtkSimpleLayout.git
または
gem source -a http://gems.github.com && gem install rickyzheng-GtkSimpleLayout
以下、主な機能と簡単な例を説明します。
ビルダースタイルのレイアウトを提供する
上の例で説明したように、GtkSimpleLayout は RubyGnome2 にビルダースタイルのレイアウト機能をもたらします。レイアウトのクラスは 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 コンポーネントを配置する際、初期プロパティを設定したり、レイアウトパラメータを指定する必要がある場合があります。
vbox do
button 'sensitive = false', :sensitive => false # Initially disable
button 'expand space', :layout => [true, true] # Specify this button to fill the remaining space
end
上記の例では、最初のボタンの初期状態はdisableです。 ":sensitive => false" このパラメータは、最終的にはプロパティ設定に変換されます。Gtk::Button#sensitive=false に設定できるプロパティについては、RubyGnome2 API ドキュメントまたは GTK のドキュメントを参照してください。GtkSimpleLayoutは、ここでは単純なパラメータ変換にとどめています。
2つ目のボタンの ":layout => [true, true]" は、ちょっと特殊です。この ":layout" パラメータは GtkSimpleLayout の予約パラメータで、この UI がコンテナに入れられると、その内容に変換されるものです。この例では、コンテナは vbox(Gtk::VBox) で、デフォルトの結合メソッドは Gtk::VBox#pack_start です。この例の [true, true] は最終的に pack_start に渡されるため、このボタンが 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 : グリッドを使用して入力します。
nodebook : サブページを追加するためのページを使用します。
UIコンポーネントのマーク付け
GtkSimpleLayout は ":id => ? " このパラメータで UI コンポーネントを識別します。
hbox do
button 'first', :id => :btn_first
button 'second', :id => :btn_second
end
その後、このUIコンポーネントはcomponent()関数で取得することができます。
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() も提供し、識別されたすべてのコンポーネントをインスタンス読み取り可能なプロパティ (ゲッター) として自動的に追加してくれます。
expose_components() # は、btn_first と btn_second の読み取り属性(ゲッター)を自動的に追加します。
...
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() # Register automatic event response mapping
end
def my_layout
hbox do
button "First', :btn_first
button "Second", :btn_second
end
end
# Event response functions
def btn_first_on_clicked(*_)
puts "First button clicked"
end
# Event response function
def btn_second_on_clicked(*_)
puts "Second button clicked"
end
# Exit event response function
def self_on_destroy(*_)
Gtk.main_quit
end
end
最後の「self」は、ホストコンテナを指しています。
UIグループ化
GtkSimpleLayoutでは、レイアウト時にUIグループを指定することができます。
GtkSimpleLayoutのUIグループ化ルールは以下の通りです。
デフォルトでは、名前付きコンテナ(つまり :id パラメータが渡されたもの)は、所属する子コンポーネントのグループを自動的に作成し、グループ名はコンテナの Ming になります。
コンテナに :gid=>? パラメータが渡されると、そのコンテナが属するサブコンポーネントに対して、その名前でグループが作成されます。
複数のコンテナが同じ :gid 名を持つことができ、その場合、所属するサブコンポーネントは同じグループに分類されます。
UI は "group" で明示的にグループ化することができ、これは仮想コンテナと見なすことができます。
component_children(group_name)を使って、UIグループを取得します。
UIグループ化の例は長くなるので、ここには掲載しませんが、ソースコードのexamples/group.rbファイルをご覧ください。
UIとロジックのコードの分離
GtkSimpleLayout は潜在的にインターフェイスのコードと論理処理(またはイベント応答)のコードを分離することを強制するので、プログラム全体の階層がより明確になります。レイアウトの結果は依然としてコンテナであり、そのコンテナを他のコンテナに入れて、より複雑なインタフェースに組み合わせることができるため、より多くのインタフェース部品を持つプログラムでは、レイアウトを分割することが容易になります。
レイアウト時に変数を使用して、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
お楽しみに :-)
関連
-
Rubyイテレータの知識まとめ
-
Rubyの乱数生成方法のまとめ
-
win7でrubyのソースコードからコンパイルしてインストールする方法
-
Rubyオブジェクト指向プログラミングにおけるクラスメソッドとクラスエクステンション
-
Rubyの並列処理とグローバルロック
-
RubyのSimple FactoryパターンとFactory Methodパターンを利用する
-
Rubyのインストールと操作
-
Ruby on Railsのパフォーマンスを最適化するためのいくつかの方法についての考察
-
RubyインタプリタをOSにインストールするためのチュートリアル
-
Ruby on Railsのビューの書き方に関するいくつかのアドバイス
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
PythonのFlaskフレームワークでSERVER_NAMEドメイン名を設定するためのチュートリアル
-
RubyのMonkey Patchの開発例
-
UbuntuでRuby on RailsフレームワークとRubyMine IDEを設定する
-
MongoDBに接続するためのRuby on Railsフレームワークアプリケーション チュートリアル
-
Rubyオブジェクト指向の知識まとめ
-
RubyのProcクラスとProc.newメソッドの使用法
-
Rubyの文字列と配列の最大化問題の考察
-
rubyのダブルイコール==問題
-
Rubyのプライベートとプロテクトを簡単にご紹介します。
-
Rubyプログラミングにおけるアサインメント関連操作