Ruby Tk とは?GUIプログラミング入門【徹底解説】

Ruby Tk とは?GUIプログラミング入門【徹底解説】

はじめに:GUIプログラミングの世界へようこそ

コンピュータと私たちの間で情報をやり取りする方法は様々ですが、最も直感的で広く使われているのがGraphical User Interface(GUI)、すなわちグラフィカルユーザーインターフェースです。ボタンをクリックしたり、ウィンドウをドラッグしたり、テキストボックスに入力したり、これらの操作を可能にするのがGUIアプリケーションです。コマンドラインインターフェース(CUI)に慣れている方でも、GUIアプリケーションを自分で作れるようになれば、より多くのユーザーに使いやすいツールを提供したり、自分の作業を視覚的に効率化したりすることができます。

Rubyは、その読みやすく書きやすいシンタックスで人気のプログラミング言語ですが、もちろんGUIアプリケーションを開発するためのライブラリも豊富に存在します。代表的なものとしては、Gtk(GObject Introspection)、Qt(Qt bindings)、Shoesなどがありますが、Rubyの標準ライブラリとして提供されているGUIツールキットがあります。それがTkです。

本記事では、Rubyの標準ライブラリとして手軽に利用できるTk(以降、Ruby Tkと呼びます)に焦点を当て、GUIプログラミングの基本から応用までを徹底的に解説します。約5000語にわたるこの詳細な解説を通して、Ruby Tkを使ったGUIアプリケーション開発の扉を開き、自分のアイデアを形にする第一歩を踏み出しましょう。

なぜRuby Tkを選ぶのか?

RubyでGUIアプリケーションを開発する際に、Ruby Tkを選ぶことにはいくつかのメリットがあります。

  1. 標準ライブラリであること: これが最大の利点です。別途ライブラリをインストールする手間がなく、RubyがインストールされていればすぐにGUI開発を始められます。手軽にちょっとしたGUIツールを作りたい場合に非常に便利です。
  2. シンプルさ: Tkは比較的シンプルな設計思想を持っています。学習コストが比較的低く、GUIプログラミングの基本的な概念(ウィジェット、ジオメトリマネージャ、イベント処理など)を理解するのに適しています。
  3. 歴史: Tk自体は、Tclというスクリプト言語のために開発されたGUIツールキットです。長い歴史を持ち、安定しています。Ruby TkはそのTcl/TkをRubyから利用できるようにしたバインディングです。

一方で、モダンなGUIツールキット(例えばQtやGtk)と比較すると、見た目のカスタマイズ性や提供されるウィジェットの種類、パフォーマンスの面で劣る部分があることも事実です。しかし、ちょっとしたユーティリティツールや学習用としては、Ruby Tkは十分に強力で手軽な選択肢となります。

記事で学ぶこと

この記事では、Ruby Tkを使ったGUIプログラミングをゼロから始め、以下の内容を習得することを目指します。

  • Ruby Tkの基本的な使い方と最初のプログラム
  • 様々な種類のウィジェット(ボタン、テキストボックス、ラベルなど)の使い方
  • ウィジェットをウィンドウ内に配置する方法(ジオメトリマネージャ)
  • ユーザーのアクション(クリック、キー入力など)に反応するイベント処理
  • 変数連携、見た目のカスタマイズ、Ttkなど、より高度なトピック
  • 簡単なサンプルアプリケーションの作成

さあ、Rubyを使ったGUIプログラミングの世界へ飛び込んでみましょう!

Ruby Tkの基本:最初のGUIプログラム

Ruby Tkを使うための準備は非常に簡単です。ほとんどの場合、別途何かをインストールする必要はありません。Rubyの多くのバージョンには、既にTkのサポートが含まれています。

Tkとは何か?

Tkは、元々Tcl(Tool Command Language)というスクリプト言語のために開発されたGUIツールキットです。クロスプラットフォームに対応しており、Unix系OS、Windows、macOSなどで動作します。Ruby Tkは、このTcl/TkライブラリをRubyから呼び出して利用するためのラッパーライブラリです。つまり、Rubyコードの中でTkの機能を使うための橋渡し役をしています。

Ruby Tkのインストール(通常は不要)

前述の通り、多くのRuby環境ではTkは標準で含まれています。試しに以下のコードを実行してみてください。

ruby
require 'tk'
puts "Ruby Tk is available!"

もしエラーが出ずに “Ruby Tk is available!” と表示されれば、Ruby Tkはあなたの環境で利用可能です。
もし LoadError: cannot load such file -- tk のようなエラーが出た場合、Tkライブラリが含まれていないRubyを使用している可能性があります。その場合は、システムにTcl/Tkライブラリがインストールされていることを確認した上で、Rubyを再インストールするか、Rubyのバージョンを上げることを検討してください。多くのLinuxディストリビューションやmacOSでは、Tcl/Tkはデフォルトでインストールされています。Windowsの場合は、RubyInstallerなどでTkサポートが有効になっているバージョンを選択する必要があります。

最初のRuby Tkプログラム (“Hello, World!”)

GUIプログラミングの慣習にならい、まずは画面に「Hello, World!」と表示するだけの簡単なプログラムを作成します。

“`ruby

gui_hello.rb

require ‘tk’ # Tkライブラリを読み込む

最上位ウィンドウ(ルートウィンドウ)を作成

TkRoot.new {} は新しいウィンドウを作成し、設定用のブロックを渡す

root = TkRoot.new {
title “初めてのGUIアプリ” # ウィンドウのタイトルを設定
minsize(200, 100) # ウィンドウの最小サイズを設定 (幅, 高さ)
}

ラベルウィジェットを作成

TkLabel.new(親ウィジェット) {} は新しいラベルを作成し、親ウィジェットを指定

label = TkLabel.new(root) {
text “Hello, World!” # 表示するテキストを設定
font ‘Arial 20 bold’ # フォントを設定 (種類 サイズ スタイル)
foreground ‘blue’ # 文字色を設定
background ‘lightgray’ # 背景色を設定
pady 20 # 上下パディング (外側)
}

ラベルをウィンドウ内に配置

packメソッドはウィジェットを自動的に配置するジオメトリマネージャの一つ

label.pack(
padx: 30, # 左右パディング (外側)
pady: 20 # 上下パディング (外側)
)

GUIイベントループを開始

これがないとウィンドウが表示されずにプログラムが終了してしまう

Tk.mainloop
“`

このコードを gui_hello.rb のような名前で保存し、ターミナルやコマンドプロンプトで ruby gui_hello.rb と実行してみてください。すると、指定したタイトルとサイズを持つウィンドウが表示され、中央に「Hello, World!」というテキストが表示されるはずです。

コードの解説

この短いコードの中に、Ruby Tkアプリケーションの基本的な構造が含まれています。

  1. require 'tk': Tkライブラリを使用可能にします。
  2. root = TkRoot.new { ... }: これがGUIアプリケーションの出発点となる最上位ウィンドウを作成します。通常、アプリケーションに一つだけ存在するメインウィンドウです。TkRootクラスのインスタンスとして作成され、ブロック {} の中にウィンドウの設定(タイトルやサイズなど)を記述できます。
  3. label = TkLabel.new(root) { ... }: TkLabelはテキストや画像を表示するためのウィジェットです。TkLabel.newの最初の引数 root は、このラベルウィジェットがどのウィンドウやコンテナの中に配置されるかを示す「親ウィジェット」です。ここでは、先ほど作成した root ウィンドウの子としてラベルを作成しています。ブロック {} の中では、ラベルの表示内容 (text) や見た目 (font, foreground, background, pady など) を設定できます。
  4. label.pack(...): ウィジェットを作成しただけでは画面には表示されません。ウィンドウ内のどこに配置するかを指示する必要があります。pack は、ウィジェットをウィンドウまたは親ウィジェット内に自動的に配置する「ジオメトリマネージャ」と呼ばれるメソッドの一つです。ここではラベルをウィンドウ内に配置しています。padxpady オプションでウィジェットの周りに余白を設定できます。
  5. Tk.mainloop: これが非常に重要です。GUIアプリケーションは、ユーザーからの入力(マウスクリック、キー入力など)やシステムのイベント(ウィンドウのリサイズなど)が発生するのを待ち続け、それに応じて処理を実行する「イベント駆動型」のプログラムです。Tk.mainloop は、このようなイベントを待ち受けるための無限ループを開始します。このメソッドが呼び出されるまで、ウィンドウは表示されず、プログラムはすぐに終了してしまいます。ウィンドウを閉じたり、プログラムを終了する操作が行われるまで、このループは継続します。

これで、Ruby Tkを使った最も基本的なGUIプログラムを作成・実行できるようになりました。次に、GUIを構成する様々な「ウィジェット」について詳しく見ていきましょう。

ウィジェットの種類と使い方

GUIアプリケーションは、様々な部品(ウィジェット)の組み合わせでできています。ボタン、テキストボックス、ラベルなどがその代表例です。Ruby Tkで利用できる主要なウィジェットとその使い方を解説します。

基本的なウィジェット

TkLabel (テキストや画像の表示)

先ほどの例でも使いましたが、TkLabelは主に静的なテキストや画像を表示するために使います。

“`ruby
require ‘tk’

root = TkRoot.new { title “Label Example” }

シンプルなテキストラベル

TkLabel.new(root) {
text “これはラベルです。”
pack
}

フォントや色を変えたラベル

TkLabel.new(root) {
text “装飾されたラベル”
font ‘Times 16 italic’
foreground ‘darkgreen’
background ‘yellow’
pack
}

画像を表示するラベル (事前にgifファイルを用意)

tkphoto = TkPhotoImage.new(file: ‘my_image.gif’)

TkLabel.new(root) {

image tkphoto

pack

}

注意: TkPhotoImageはgif形式をネイティブにサポート。pngなど他の形式は追加ライブラリが必要な場合がある。

Tk.mainloop
“`

主なオプション:
* text: 表示する文字列
* image: 表示する画像オブジェクト (TkPhotoImage など)
* font: フォント、サイズ、スタイル ('Arial 12 bold')
* foreground (fg): 文字色
* background (bg): 背景色
* padx, pady: テキストや画像と境界線の間の余白(内側のパディング)
* anchor: ラベル内のテキスト/画像の配置 ('n', 's', 'e', 'w', 'nw', 'ne', 'sw', 'se', 'center')
* justify: 複数行テキストの揃え ('left', 'center', 'right')

TkButton (ボタン、コマンド実行)

ユーザーがクリックしてアクションを実行するためのウィジェットです。

“`ruby
require ‘tk’

root = TkRoot.new { title “Button Example” }

クリックでメッセージを表示するボタン

button1 = TkButton.new(root) {
text “クリックしてね”
# commandオプションにProcやlambdaを指定して、ボタンクリック時の処理を定義
command proc { puts “ボタンがクリックされました!” }
pack(pady: 10)
}

別のアクションを実行するボタン

button2 = TkButton.new(root) {
text “ウィンドウを閉じる”
command proc { root.destroy } # root.destroyでウィンドウを閉じる
pack(pady: 10)
}

ボタンのテキストを実行中に変更

button3 = TkButton.new(root) {
text “最初はこれ”
command proc {
button3.configure(text: “クリックされた!”) # configureメソッドでオプション変更
}
pack(pady: 10)
}

無効化されたボタン

button4 = TkButton.new(root) {
text “使えません”
state ‘disabled’ # ボタンを無効化
pack(pady: 10)
}

Tk.mainloop
“`

主なオプション:
* text: ボタンに表示する文字列
* command: ボタンがクリックされたときに実行されるProcまたはlambdaオブジェクト
* image: ボタンに表示する画像
* state: ボタンの状態 ('normal', 'disabled')
* activeforeground, activebackground: ボタンが押されたときの文字色/背景色

TkEntry (一行テキスト入力)

ユーザーが一行のテキストを入力するためのフィールドです。

“`ruby
require ‘tk’

root = TkRoot.new { title “Entry Example” }

label = TkLabel.new(root) {
text “名前を入力してください:”
pack(pady: 5)
}

テキスト入力フィールド

entry = TkEntry.new(root) {
pack(pady: 5)
}

入力内容を取得して表示するボタン

button = TkButton.new(root) {
text “入力内容を表示”
command proc {
input_text = entry.get # entry.getで入力内容を取得
puts “入力された名前: #{input_text}”
}
pack(pady: 5)
}

テキストフィールドの値を設定するボタン

button_set = TkButton.new(root) {
text “値を設定”
command proc {
entry.set(“初期値”) # entry.setで値を設定
}
pack(pady: 5)
}

パスワード入力フィールド (表示文字をマスク)

password_label = TkLabel.new(root) { text “パスワード:” }.pack(pady: 5)
password_entry = TkEntry.new(root) {
show ‘‘ # 入力文字をで表示
pack(pady: 5)
}

Tk.mainloop
“`

主なオプション:
* textvariable: 後述する TkVariable と連携させ、入力内容をRuby変数と同期させます。
* show: 入力された文字を別の文字に置き換えて表示します(例: show '*' でパスワード入力フィールド)。
* state: エントリの状態 ('normal', 'disabled', 'readonly').
* width: フィールドの幅(文字数単位)。

メソッド:
* get: 現在の入力文字列を取得します。
* set(text): フィールドの文字列を設定します。
* delete(first, last=nil): 指定した範囲(インデックス)の文字を削除します。0 は先頭、'end' は末尾。delete(0, 'end') ですべて削除。
* insert(index, text): 指定した位置に文字列を挿入します。0 は先頭、'end' は末尾、'insert' はカーソル位置。

TkText (複数行テキストエディタ)

複数行のテキストを入力・編集・表示するためのウィジェットです。簡単なテキストエディタやログ表示などに使えます。

“`ruby
require ‘tk’

root = TkRoot.new { title “Text Example” }

複数行テキストウィジェット

text_widget = TkText.new(root) {
width 40 # 幅 (文字数)
height 10 # 高さ (行数)
pack(pady: 10)
}

テキストを挿入

text_widget.insert(‘end’, “これは\n”)
text_widget.insert(‘end’, “複数行の\n”)
text_widget.insert(‘end’, “テキストです。\n”)

テキストを取得して表示するボタン

button_get = TkButton.new(root) {
text “テキストを取得”
command proc {
# 最初の文字 (1.0) から最後の文字 (‘end’) までを取得。
# 取得範囲の最後に改行が含まれるため、一般的には end-1c (‘end’の1文字前) を使う
content = text_widget.get(‘1.0’, ‘end-1c’)
puts “— テキスト内容 —”
puts content
puts “——————–”
}
pack(pady: 5)
}

テキストを全削除するボタン

button_clear = TkButton.new(root) {
text “全クリア”
command proc {
text_widget.delete(‘1.0’, ‘end’) # 最初の文字から最後の文字まで削除
}
pack(pady: 5)
}

タグによるテキスト装飾の例

text_widget.insert(‘end’, “強調したいテキスト\n”)

タグの定義

text_widget.tag_configure(‘bold_italic’, font: ‘Arial 12 bold italic’, foreground: ‘red’)

タグの適用

タグの範囲は ‘行.文字位置’ で指定。1.0 は1行目0文字目 (先頭)

4.0 は4行目0文字目 (4行目の先頭)

text_widget.tag_add(‘bold_italic’, ‘4.0’, ‘4.end’) # 4行目全体にタグ適用

Tk.mainloop
“`

主なメソッド:
* insert(index, text): 指定したインデックスにテキストを挿入します。インデックスは '行.文字位置' の形式(例: '1.0' は1行目の先頭、'end' は末尾、'insert' はカーソル位置)。
* get(index1, index2=nil): 指定した範囲のテキストを取得します。
* delete(index1, index2=nil): 指定した範囲のテキストを削除します。
* tag_configure(tag_name, options): 指定したタグの名前で装飾スタイルを定義します。
* tag_add(tag_name, index1, index2=nil): 指定した範囲にタグを適用します。
* tag_remove(tag_name, index1, index2=nil): 指定した範囲からタグを解除します。

TkCheckButton (チェックボックス)

オン/オフの状態を選択するためのチェックボックスです。

“`ruby
require ‘tk’

root = TkRoot.new { title “CheckButton Example” }

チェックボタンの状態を保持する変数 (後述のTkVariableを使用するのが一般的だが、ここでは簡単のため直接)

チェックボタンの状態は 0 (オフ) または 1 (オン) で返されることが多い

TkVariable を使うと、boolean 値や他の値を扱うことができる

check_var = TkVariable.new(0) # 初期値 0 (オフ)

check_button = TkCheckButton.new(root) {
text “このオプションを有効にする”
# チェックボタンの状態と連携する変数
variable check_var
# onvalue: オンになったときに variable に設定される値 (デフォルトは 1)
# offvalue: オフになったときに variable に設定される値 (デフォルトは 0)
# command: 状態が変更されたときに実行されるProc (ここでは変数連携のみなので不要)
pack(pady: 10)
}

button_state = TkButton.new(root) {
text “チェック状態を表示”
command proc {
# variableオプションで指定した変数の値を取得
state = check_var.value
puts “チェックボタンの状態: #{state} (#{state == 1 ? ‘オン’ : ‘オフ’})”
}
pack(pady: 5)
}

Tk.mainloop
“`

主なオプション:
* text: チェックボタンのラベルテキスト
* variable: チェックボタンの状態と連携する TkVariable オブジェクト
* onvalue: オンになったときに variable に設定される値
* offvalue: オフになったときに variable に設定される値
* state: ウィジェットの状態 ('normal', 'disabled')

TkRadioButton (ラジオボタン)

複数の選択肢の中から一つだけを選択するためのラジオボタンです。同じ variable を指定したラジオボタンがグループとして扱われます。

“`ruby
require ‘tk’

root = TkRoot.new { title “RadioButton Example” }

ラジオボタングループの状態を保持する変数

radio_var = TkVariable.new(“Option1”) # 初期値

label = TkLabel.new(root) {
text “お好みの色を選択してください:”
pack(pady: 5)
}

最初のラジオボタン

radio1 = TkRadioButton.new(root) {
text “赤”
variable radio_var # 同じ変数を使うことでグループ化
value “Red” # このボタンが選択されたときに variable に設定される値
pack(anchor: ‘w’) # 左寄せで配置 (ジオメトリマネージャ pack のオプション)
}

2番目のラジオボタン

radio2 = TkRadioButton.new(root) {
text “緑”
variable radio_var
value “Green”
pack(anchor: ‘w’)
}

3番目のラジオボタン

radio3 = TkRadioButton.new(root) {
text “青”
variable radio_var
value “Blue”
pack(anchor: ‘w’)
}

button_state = TkButton.new(root) {
text “選択中の色を表示”
command proc {
selected_value = radio_var.value
puts “選択中の色: #{selected_value}”
}
pack(pady: 10)
}

Tk.mainloop
“`

主なオプション:
* text: ラジオボタンのラベルテキスト
* variable: ラジオボタングループの状態と連携する TkVariable オブジェクト
* value: このボタンが選択されたときに variable に設定される値
* state: ウィジェットの状態 ('normal', 'disabled')

TkListbox (リスト選択)

複数の項目を表示し、その中から一つまたは複数を選択するためのリストボックスです。

“`ruby
require ‘tk’

root = TkRoot.new { title “Listbox Example” }

label = TkLabel.new(root) {
text “好きなフルーツを選択してください:”
pack(pady: 5)
}

リストボックスウィジェット

listbox = TkListbox.new(root) {
height 5 # 表示する行数
selectmode ‘single’ # 選択モード: ‘single’, ‘browse’, ‘multiple’, ‘extended’
# ‘single’ または ‘browse’ は単一選択
# ‘multiple’ または ‘extended’ は複数選択
pack(pady: 5)
}

リスト項目を追加

fruits = [“りんご”, “バナナ”, “オレンジ”, “ぶどう”, “いちご”, “メロン”]
fruits.each do |fruit|
listbox.insert(‘end’, fruit) # ‘end’ はリストの最後に追加
end

button_select = TkButton.new(root) {
text “選択中の項目を表示”
command proc {
# 選択されている項目のインデックスリストを取得
selected_indices = listbox.curselection
if selected_indices.empty?
puts “何も選択されていません。”
else
puts “選択された項目:”
selected_indices.each do |index|
# インデックスを指定して項目テキストを取得
item_text = listbox.get(index)
puts “- #{item_text}”
end
end
}
pack(pady: 10)
}

Tk.mainloop
“`

主なオプション:
* height: 表示する行数。これより多い項目はスクロール可能になります(スクロールバーとの連携が必要)。
* width: 表示する文字数。
* selectmode: 選択モード ('single', 'browse', 'multiple', 'extended').
* 'single': 単一選択のみ(ドラッグやCtrl/Shiftクリック不可)。
* 'browse': 単一選択のみ(ドラッグで選択範囲変更可能)。デフォルト。
* 'multiple': 複数選択可能(クリックでオン/オフ切り替え)。
* 'extended': 複数選択可能(Shift+クリック、Ctrl+クリックなどが有効)。
* state: ウィジェットの状態 ('normal', 'disabled')。

メソッド:
* insert(index, *elements): 指定したインデックスに項目を追加します。'end' で最後に追加。
* delete(first, last=nil): 指定した範囲の項目を削除します。
* get(first, last=nil): 指定した範囲の項目テキストを取得します。
* curselection: 選択されている項目のインデックスのリストを返します。複数選択モードの場合。単一選択モードでは選択されていなければ空配列、選択されていればインデックス1つの配列。
* size: 項目の総数を返します。
* selection_includes(index): 指定したインデックスの項目が選択されているか判定します。

TkScrollbar (スクロールバー)

他のウィジェット(TkText, TkListbox, TkCanvas)のスクロール機能を提供します。スクロールバーと対象ウィジェットを連携させる必要があります。

“`ruby
require ‘tk’

root = TkRoot.new { title “Scrollbar Example” }

テキストウィジェットを作成(スクロールさせたい対象)

text_widget = TkText.new(root) {
width 40
height 10
wrap ‘word’ # 行末で単語の途中で改行しない
}

垂直スクロールバーを作成

v_scrollbar = TkScrollbar.new(root) {
# orientオプションで ‘vertical’ または ‘horizontal’ を指定
orient ‘vertical’
}

テキストウィジェットとスクロールバーを連携

テキストウィジェットの yscrollcommand をスクロールバーの set コマンドに設定

text_widget.yscrollcommand v_scrollbar.method(:set)

スクロールバーの command をテキストウィジェットの yview コマンドに設定

v_scrollbar.command text_widget.method(:yview)

サンプルの長いテキストを挿入

long_text = “これはスクロール可能なテキストウィジェットの例です。\n” * 50
text_widget.insert(‘end’, long_text)

ウィジェットの配置 (packを使う場合、スクロールバーと対象ウィジェットを適切に配置する必要がある)

フレームでまとめて配置すると管理しやすい

frame = TkFrame.new(root).pack(fill: ‘both’, expand: true)

テキストウィジェットとスクロールバーをフレーム内に配置

packをfill=’both’, expand=true で行うと、ウィンドウサイズ変更時にウィジェットも拡大縮小される

text_widget.pack(in: frame, side: ‘left’, fill: ‘both’, expand: true)
v_scrollbar.pack(in: frame, side: ‘right’, fill: ‘y’)

Tk.mainloop
“`

連携の仕組み:
1. 対象ウィジェットの設定: スクロール対象のウィジェット(例: TkText)のスクロールコマンドオプション (yscrollcommand または xscrollcommand) に、スクロールバーの set メソッドを設定します。ウィジェットがスクロール範囲を計算し、その情報をスクロールバーの set メソッドに渡します。
2. スクロールバーの設定: スクロールバーの command オプションに、対象ウィジェットのビューコマンド (yview または xview) を設定します。ユーザーがスクロールバーを操作すると、スクロールバーは現在の位置情報を対象ウィジェットのビューコマンドに渡します。

配置の注意点:
pack を使う場合、スクロールバーを片側に fill: 'y' で配置し、対象ウィジェットをもう片側に fill: 'both', expand: true で配置するのが一般的なパターンです。これらを TkFrame で囲むと、グループとして管理しやすくなります。

TkScale (スライダー)

数値を選択するためのスライダーです。

“`ruby
require ‘tk’

root = TkRoot.new { title “Scale Example” }

スケールウィジェット

scale = TkScale.new(root) {
from 0 # 最小値
to 100 # 最大値
resolution 1 # 解像度(スライダーの移動ステップ)
orient ‘horizontal’ # スライダーの方向 (‘horizontal’ または ‘vertical’)
label “値” # ラベルテキスト
# variableオプションでTkVariableと連携することも可能
# commandオプションで、値が変更されたときに実行されるProcを指定することも可能
pack(pady: 10)
}

button_get = TkButton.new(root) {
text “現在の値を取得”
command proc {
current_value = scale.value # スライダーの現在の値を取得
puts “現在の値: #{current_value}”
}
pack(pady: 5)
}

Tk.mainloop
“`

主なオプション:
* from: 最小値
* to: 最大値
* resolution: 値のステップサイズ
* orient: 方向 ('horizontal', 'vertical')
* label: スケールのラベルテキスト
* length: スケールの長さ(ピクセル単位)
* variable: TkVariable と連携
* command: 値が変更されたときに呼び出されるProc

メソッド:
* value: 現在の値を返します。
* value=(new_value): 値を設定します。

コンテナウィジェット

他のウィジェットをグループ化したり、特定のレイアウトを提供したりするためのウィジェットです。

TkFrame (グループ化)

複数のウィジェットをまとめて配置・管理するための最も基本的なコンテナです。枠線や背景色を設定して視覚的なグループ化もできます。

“`ruby
require ‘tk’

root = TkRoot.new { title “Frame Example” }

最初のフレーム

frame1 = TkFrame.new(root) {
# 枠線の種類と太さ (groove, sunken, raised, flat, ridge, solid)
borderwidth 2
relief ‘groove’
pack(padx: 10, pady: 10)
}

frame1内にラベルとボタンを配置

TkLabel.new(frame1) { text “フレーム1内のラベル” }.pack(side: ‘top’)
TkButton.new(frame1) { text “ボタンA” }.pack(side: ‘left’, padx: 5, pady: 5)
TkButton.new(frame1) { text “ボタンB” }.pack(side: ‘left’, padx: 5, pady: 5)

2番目のフレーム

frame2 = TkFrame.new(root) {
background ‘lightblue’ # 背景色
pack(padx: 10, pady: 10)
}

frame2内に別のラベルとエントリを配置

TkLabel.new(frame2) { text “フレーム2内のラベル” }.pack
TkEntry.new(frame2) { width 20 }.pack

Tk.mainloop
“`

フレームを使うことで、複雑なUIでもウィジェットをグループ化して管理しやすくなります。特にジオメトリマネージャ(pack, grid)を使う際に、フレームを組み合わせることで柔軟なレイアウトを実現できます。

主なオプション:
* borderwidth: 枠線の太さ(ピクセル単位)
* relief: 枠線の種類 ('flat', 'raised', 'sunken', 'groove', 'ridge', 'solid')
* background (bg): 背景色
* width, height: フレームのサイズ

TkToplevel (新しい独立したウィンドウ)

メインウィンドウ(TkRoot)とは別に、新しい独立したウィンドウを作成します。ダイアログウィンドウや設定ウィンドウなどに使われます。

“`ruby
require ‘tk’

root = TkRoot.new { title “Toplevel Example” }

button_open = TkButton.new(root) {
text “新しいウィンドウを開く”
command proc {
# 新しいトップレベルウィンドウを作成
toplevel = TkToplevel.new {
title “サブウィンドウ”
}

# サブウィンドウ内にウィジェットを配置
TkLabel.new(toplevel) {
  text "これはサブウィンドウです。"
  pack(padx: 20, pady: 20)
}

TkButton.new(toplevel) {
  text "サブウィンドウを閉じる"
  command proc { toplevel.destroy } # サブウィンドウを閉じる
  pack(pady: 10)
}

}
pack(pady: 20)
}

Tk.mainloop
“`

TkToplevel.newTkRoot.new と似ていますが、アプリケーションのルートウィンドウではなく、既存のウィンドウの子ウィンドウとして扱われます(ただし、見た目は独立したウィンドウです)。親ウィンドウを閉じると、その子であるToplevelウィンドウも閉じられます。

TkPanedWindow (分割ウィンドウ)

複数のウィジェット(または他のコンテナ)を、ユーザーがサイズを変更できる区切り線(セパレーター)で分割して表示するコンテナです。

“`ruby
require ‘tk’
require ‘tk/ttk/panedwindow’ # TtkバージョンのPanedWindowを使う

root = TkRoot.new { title “PanedWindow Example” }

PanedWindowを作成

TtkPanedwindowはモダンな見た目を提供

panedwindow = Ttk::Panedwindow.new(root) {
orient ‘horizontal’ # 分割方向 (‘horizontal’ または ‘vertical’)
pack(fill: ‘both’, expand: true, padx: 10, pady: 10)
}

最初のペイン (フレームや他のウィジェット)

pane1 = TkFrame.new(panedwindow) {
background ‘lightcoral’
}
TkLabel.new(pane1) { text “ペイン1” }.pack(padx: 30, pady: 30)

2番目のペイン

pane2 = TkFrame.new(panedwindow) {
background ‘lightgreen’
}
TkLabel.new(pane2) { text “ペイン2” }.pack(padx: 30, pady: 30)

ペインをPanedWindowに追加

panedwindow.add(pane1)
panedwindow.add(pane2)

Tk.mainloop
“`

メソッド:
* add(widget, options=nil): PanedWindowにウィジェット(ペイン)を追加します。オプションでペインの重みなどを指定できます。
* remove(widget_or_index): 指定したウィジェットまたはインデックスのペインを削除します。

TkNotebook (タブ)

複数の「ページ」を持つタブ付きインターフェースを提供するコンテナです。各ページには任意のウィジェットを配置できます。

“`ruby
require ‘tk’
require ‘tk/ttk/notebook’ # TtkバージョンのNotebookを使う

root = TkRoot.new { title “Notebook Example” }

Notebookウィジェットを作成

notebook = Ttk::Notebook.new(root) {
pack(fill: ‘both’, expand: true, padx: 10, pady: 10)
}

最初のタブ(フレームを作成し、タブとして追加)

tab1_frame = TkFrame.new(notebook) {
background ‘lightblue’
}
TkLabel.new(tab1_frame) { text “これはタブ1の内容です” }.pack(padx: 50, pady: 50)
notebook.add(tab1_frame, text: “タブ 1”) # addメソッドでフレームとタブ名を追加

2番目のタブ

tab2_frame = TkFrame.new(notebook) {
background ‘lightyellow’
}
TkButton.new(tab2_frame) { text “タブ2のボタン” }.pack(padx: 50, pady: 50)
notebook.add(tab2_frame, text: “タブ 2”)

3番目のタブ (最初は無効)

tab3_frame = TkFrame.new(notebook) {
background ‘lightcoral’
}
TkEntry.new(tab3_frame) { width 30 }.pack(padx: 50, pady: 50)
notebook.add(tab3_frame, text: “タブ 3”, state: ‘disabled’) # state: ‘disabled’ で無効化

タブを切り替えるイベントをハンドリング

notebook.bind(‘<>’, proc {
# 現在選択されているタブのインデックスを取得
selected_index = notebook.index(‘current’)
puts “タブが変更されました。選択中のタブのインデックス: #{selected_index}”
# 選択されたタブのウィジェットを取得
# selected_widget = notebook.winfo_children[selected_index]
# puts “選択中のウィジェット: #{selected_widget}”
})

Tk.mainloop
“`

メソッド:
* add(widget, options=nil): Notebookにウィジェット(タブの内容)を追加します。オプションでタブ名 (text), 状態 (state), 画像 (image) などを指定します。
* forget(index_or_widget): 指定したタブを削除します。
* select(index_or_widget): 指定したタブを選択状態にします。
* index(index_or_specifier): タブのインデックスを取得します。'current' で現在選択されているタブのインデックスを取得。

メニューウィジェット

アプリケーションにメニューバー、プルダウンメニュー、コンテキストメニューを追加します。

“`ruby
require ‘tk’

root = TkRoot.new { title “Menu Example” }

メニューバーを作成

menubar = TkMenu.new(root)

Fileメニューを作成し、メニューバーに追加

filemenu = TkMenu.new(menubar, tearoff: false) # tearoff: false で破線セパレータを非表示
menubar.add(‘cascade’, menu: filemenu, label: ‘ファイル’) # メニューバーにFileメニューをカスケードとして追加

Fileメニューに項目を追加

filemenu.add(‘command’, label: ‘新規’, command: proc { puts “新規ファイル作成…” })
filemenu.add(‘command’, label: ‘開く…’, command: proc { puts “ファイルを開く…” })
filemenu.add(‘separator’) # 区切り線を追加
filemenu.add(‘command’, label: ‘閉じる’, command: proc { puts “ファイルを閉じる…” })
filemenu.add(‘command’, label: ‘終了’, command: proc { root.destroy }, accelerator: ‘Cmd+Q’) # アクセラレーター表示

Editメニューを作成し、メニューバーに追加

editmenu = TkMenu.new(menubar, tearoff: false)
menubar.add(‘cascade’, menu: editmenu, label: ‘編集’)

Editメニューにチェックボタンとラジオボタン項目を追加

editmenu.add(‘checkbutton’, label: ‘オプションA’, variable: TkVariable.new(0))
editmenu.add(‘separator’)
editmenu.add(‘radiobutton’, label: ‘モードX’, variable: TkVariable.new(‘X’), value: ‘X’)
editmenu.add(‘radiobutton’, label: ‘モードY’, variable: TkVariable.new(‘Y’), value: ‘Y’)

メニューバーをルートウィンドウに設定

root.menu(menubar)

コンテキストメニュー (右クリックメニュー) を作成

context_menu = TkMenu.new(root, tearoff: false)
context_menu.add(‘command’, label: ‘コピー’, command: proc { puts “コピー…” })
context_menu.add(‘command’, label: ‘貼り付け’, command: proc { puts “貼り付け…” })

ウィンドウ全体への右クリックイベントにコンテキストメニューを表示するバインディング

root.bind_all(““, proc { |event| # MacはButton-2(中クリック)だが、Windows/LinuxはButton-3が右クリック
# プラットフォームによって調整が必要
# event.x_root, event.y_root はスクリーン座標
context_menu.popup(event.x_root, event.y_root)
})

Windows/Linuxの場合の右クリック

if TK_PLATFORM[‘platform’] != ‘darwin’ # tk/lib/tkutil.rb で定義される定数
root.bind_all(““, proc { |event|
context_menu.popup(event.x_root, event.y_root)
})
end

Tk.mainloop
“`

メニューの種類 (add メソッドの最初の引数):
* 'command': クリックすると command オプションで指定したProcが実行される通常のメニュー項目。
* 'checkbutton': チェックボックス付きのメニュー項目。variable オプションで状態と連携。
* 'radiobutton': ラジオボタン付きのメニュー項目。同じ variable を持つ項目がグループ化され、一つだけ選択可能。value オプションで値を指定。
* 'separator': メニュー項目の区切り線。
* 'cascade': サブメニューを持つ項目。menu オプションに TkMenu オブジェクトを指定。

popup(x, y) メソッドは、指定したスクリーン座標にコンテキストメニューを表示します。

ダイアログウィジェット

ファイル選択、メッセージ表示など、標準的なダイアログを提供します。require 'tk/dialog' または require 'tk/messagebox' などが必要です。

“`ruby
require ‘tk’
require ‘tk/messagebox’ # メッセージボックス用
require ‘tk/tkcommondialog’ # ファイルダイアログ用

root = TkRoot.new { title “Dialog Example” }

情報メッセージボックス

button_info = TkButton.new(root) {
text “情報メッセージ”
command proc {
Tk.messageBox(
type: ‘ok’, # ダイアログの種類 (ok, okcancel, yesno, yesnocancel, retrycancel, abortretryignore)
icon: ‘info’, # アイコン (info, warning, error, question)
title: ‘情報’, # タイトル
message: ‘これは情報メッセージです。’ # メッセージ本文
)
}
pack(pady: 5)
}

質問メッセージボックス (Yes/No)

button_question = TkButton.new(root) {
text “質問メッセージ”
command proc {
result = Tk.messageBox(
type: ‘yesno’,
icon: ‘question’,
title: ‘確認’,
message: ‘続行しますか?’
)
puts “ユーザーの選択: #{result}” # result は ‘yes’, ‘no’ などの文字列
}
pack(pady: 5)
}

ファイルを開くダイアログ

button_open_file = TkButton.new(root) {
text “ファイルを開く”
command proc {
# askopenfilename: ファイルパス文字列を返す
# askopenfile: Fileオブジェクトを返す
filepath = Tk.getOpenFile(
title: ‘開くファイルを選択’,
# filetypes: [
# [‘Text Files’, ‘.txt’],
# [‘All Files’, ‘
.*’]
# ],
# initialdir: Dir.pwd # 初期ディレクトリ
)
if filepath && !filepath.empty?
puts “選択されたファイル: #{filepath}”
# 実際のファイル読み込み処理など…
else
puts “ファイルは選択されませんでした。”
end
}
pack(pady: 5)
}

ファイルを保存ダイアログ

button_save_file = TkButton.new(root) {
text “ファイルを保存”
command proc {
# asksaveasfilename: ファイルパス文字列を返す
# asksaveasfile: 書き込み可能なFileオブジェクトを返す
filepath = Tk.getSaveFile(
title: ‘保存先を選択’,
# initialfile: ‘untitled.txt’, # デフォルトのファイル名
# defaultextension: ‘.txt’, # デフォルトの拡張子
# filetypes: [
# [‘Text Files’, ‘.txt’],
# [‘All Files’, ‘
.*’]
# ]
)
if filepath && !filepath.empty?
puts “保存先に指定されたパス: #{filepath}”
# 実際のファイル保存処理など…
else
puts “ファイルは保存されませんでした。”
end
}
pack(pady: 5)
}

Tk.mainloop
“`

Tk.messageBox の戻り値は、ユーザーがクリックしたボタンに応じた文字列 ('ok', 'yes', 'no', 'cancel', 'retry', 'ignore', 'abort') です。

Tk.getOpenFile および Tk.getSaveFile は、ファイル選択ダイアログを表示します。ファイルが選択された場合はファイルパス文字列を、キャンセルされた場合は空文字列を返します。Tk.getOpenFile の代わりに Tk.askopenfile を使うと、選択されたファイルをオープンしたFileオブジェクトが返されます(クローズを忘れないように注意)。同様に Tk.getSaveFile の代わりに Tk.asksaveasfile を使うと、書き込み用にオープンされたFileオブジェクトが返されます。

ジオメトリマネージャ (ウィジェット配置)

ウィジェットを作成したら、それをウィンドウ内のどこに、どのようなサイズで配置するかをRuby Tkに指示する必要があります。この配置を管理するのが「ジオメトリマネージャ」です。Ruby Tkには主に3つのジオメトリマネージャがあります。

  1. pack: シンプルな配置に適しています。ウィジェットを親ウィジェットの端に詰め込むように配置したり、並べて配置したりします。
  2. grid: 行と列を用いた表形式の配置に適しています。フォームや表を作成するのに便利です。
  3. place: 絶対座標または相対座標を指定して配置します。自由度が高い反面、ウィンドウサイズが変わったときにレイアウトが崩れやすく、管理が煩雑になりがちです。

一つの親ウィジェットの中で複数のジオメトリマネージャを混在させることはできません。例えば、ルートウィンドウ直下の子ウィジェットはすべてpackで配置するか、すべてgridで配置する必要があります。ただし、TkFrameなどのコンテナウィジェットを使えば、フレーム内ではpack、別のフレーム内ではgridのように使い分けることが可能です。

pack メソッド:詰め込み式配置

packは最もシンプルで、ウィジェットを上下左右に詰めるように配置します。簡単なレイアウトや、ウィジェットを垂直または水平に並べたい場合に便利です。

“`ruby
require ‘tk’

root = TkRoot.new { title “Pack Example” }

デフォルトのpack (上から順に詰める)

TkLabel.new(root) { text “ラベル1” }.pack
TkButton.new(root) { text “ボタン1” }.pack

sideオプション (配置する辺)

TkLabel.new(root) { text “左に配置” }.pack(side: ‘left’)
TkButton.new(root) { text “右に配置” }.pack(side: ‘right’)
TkFrame.new(root) { # Frameで囲んで中央に残ったスペースに配置
background ‘lightgray’
TkLabel.new(self) { text “中央” }.pack(padx: 10, pady: 10)
}.pack # sideオプションなしで残ったスペースに配置

fillオプション (余白を埋める)

TkLabel.new(root) { text “X方向に拡張” }.pack(side: ‘bottom’, fill: ‘x’, pady: 5)
TkButton.new(root) { text “両方向に拡張” }.pack(side: ‘bottom’, fill: ‘both’, expand: true) # expandと組み合わせるとサイズ変更に追従

padx, pady (外側のパディング)

TkButton.new(root) { text “パディングあり” }.pack(padx: 20, pady: 10)

anchorオプション (配置位置)

packでexpandやfillを使わない場合、ウィジェットは中央に配置される

anchorでウィジェットが占めるスペース内の配置位置を指定

TkLabel.new(root) { text “北西に配置” }.pack(anchor: ‘nw’)

Tk.mainloop
“`

主な pack オプション:
* side: ウィジェットを配置する親ウィジェットの辺 ('top', 'bottom', 'left', 'right'). デフォルトは 'top'
* fill: ウィジェットに割り当てられたスペース内で、ウィジェット自身をどのように拡張して埋めるか ('none', 'x', 'y', 'both'). 'none' は拡張しない。'x' は水平方向に、'y' は垂直方向に、'both' は両方向に拡張。
* expand: 親ウィジェットのサイズが変更されたときに、ウィジェットに割り当てられたスペースを拡大・縮小するか (true または false). true にすると、親ウィジェットの余ったスペースを均等に分割してウィジェットに割り当てます。fill オプションと組み合わせて使用することが多いです。
* padx, pady: ウィジェットの外側の水平/垂直パディング(ピクセル単位)。ウィジェットとその周囲の要素との間にスペースを開けたいときに使います。
* ipadx, ipady: ウィジェットの内側の水平/垂直パディング(ピクセル単位)。ウィジェットのコンテンツとその境界線との間にスペースを開けたいときに使います。
* anchor: ウィジェットが占めるスペース内で、ウィジェット自身を配置する位置 ('n', 's', 'e', 'w', 'nw', 'ne', 'sw', 'se', 'center'). デフォルトは 'center'

pack はシンプルですが、複雑なレイアウトには向きません。異なる side のウィジェットを組み合わせる場合、配置の順番が重要になります。

grid メソッド:表形式配置

grid はウィジェットを仮想的なグリッド(行と列)の中に配置します。フォームやデータ表示など、構造化されたレイアウトに適しています。

“`ruby
require ‘tk’

root = TkRoot.new { title “Grid Example” }

名前入力フォームの例

TkLabel.new(root) { text “名前:” }.grid(row: 0, column: 0, sticky: ‘e’, padx: 5, pady: 5) # row=0, column=0 に配置、右寄せ
entry_name = TkEntry.new(root) { width 30 }.grid(row: 0, column: 1, padx: 5, pady: 5) # row=0, column=1 に配置

TkLabel.new(root) { text “メールアドレス:” }.grid(row: 1, column: 0, sticky: ‘e’, padx: 5, pady: 5)
entry_email = TkEntry.new(root) { width 30 }.grid(row: 1, column: 1, padx: 5, pady: 5)

rowspan, columnspan (複数セルを結合)

TkButton.new(root) {
text “送信”
command proc {
puts “名前: #{entry_name.get}”
puts “メールアドレス: #{entry_email.get}”
}
}.grid(row: 2, column: 0, columnspan: 2, pady: 10) # row=2, column=0 から2列分を結合して配置

grid_columnconfigure と grid_rowconfigure で列や行の伸縮を設定

例えば、2列目 (インデックス1) をウィンドウサイズ変更時に拡張させたい場合

root.grid_columnconfigure(1, weight: 1)

例えば、1行目 (インデックス0) と 2行目 (インデックス1) をウィンドウサイズ変更時に均等に拡張させたい場合

root.grid_rowconfigure(0, weight: 1)

root.grid_rowconfigure(1, weight: 1)

Tk.mainloop
“`

主な grid オプション:
* row, column: ウィジェットを配置する行番号、列番号(0から始まるインデックス)。
* rowspan, columnspan: ウィジェットが占める行数、列数(複数セルを結合)。
* sticky: セル内にウィジェットをどのように配置し、セルのサイズ変更時にどのように拡張させるか ('n', 's', 'e', 'w' の組み合わせ、または 'nsew'). 'nsew' はセルいっぱいに広がる。
* padx, pady: セル内のウィジェットの外側の水平/垂直パディング。
* ipadx, ipady: セル内のウィジェットの内側の水平/垂直パディング。

grid の強力な機能として、grid_columnconfigure および grid_rowconfigure メソッドがあります。これらを使うと、ウィンドウサイズが変更されたときに、特定の列や行がどれだけ拡大・縮小するか(weight オプションで重みを指定)や、最小サイズ (minsize) を設定できます。これにより、レスポンシブなレイアウトを比較的容易に実現できます。

place メソッド:絶対/相対座標配置

place はウィジェットを親ウィジェット内の指定した座標に配置します。絶対座標または親ウィジェットサイズに対する相対座標を使用できます。最も自由度が高い反面、ウィンドウサイズが変わるとレイアウトが崩れやすく、要素が増えると管理が非常に煩雑になります。

“`ruby
require ‘tk’

root = TkRoot.new { title “Place Example” }

絶対座標で配置

TkLabel.new(root) { text “絶対座標 (10, 10)” }.place(x: 10, y: 10)
TkButton.new(root) { text “絶対座標 (50, 50)” }.place(x: 50, y: 50)

相対座標で配置 (親ウィジェットのサイズに対する比率 0.0〜1.0)

TkLabel.new(root) { text “相対座標 (右下)” }.place(relx: 1.0, rely: 1.0, anchor: ‘se’) # 右下端に配置
TkButton.new(root) { text “相対座標 (中央)” }.place(relx: 0.5, rely: 0.5, anchor: ‘center’) # 中央に配置

相対サイズで配置

エントリを親ウィジェットの幅の80%, 高さの10%にする

entry = TkEntry.new(root) { background ‘yellow’ }
entry.place(relx: 0.1, rely: 0.1, relwidth: 0.8, relheight: 0.1) # x=10%, y=10% の位置から、幅80%, 高さ10%

Tk.mainloop
“`

主な place オプション:
* x, y: 親ウィジェットの左上隅からの絶対座標(ピクセル単位)。
* relx, rely: 親ウィジェットの幅/高さに対する相対座標(0.0〜1.0)。
* width, height: ウィジェットの幅/高さ(ピクセル単位)。
* relwidth, relheight: 親ウィジェットの幅/高さに対する相対サイズ(0.0〜1.0)。
* anchor: ウィジェットのどの点を x, y (または relx, rely) の位置に合わせるか ('n', 's', 'e', 'w', 'nw', 'ne', 'sw', 'se', 'center'). デフォルトは 'nw'

place は特殊なレイアウト(ウィジェットを重ねる、特定のピクセル位置に厳密に配置するなど)には有効ですが、通常のアプリケーションでは packgrid を使う方がレイアウトの管理が容易で、ウィンドウサイズ変更への対応も簡単です。

どのジオメトリマネージャを使うべきか?

  • シンプルまたは線形な配置: pack が最も手軽です。ツールバーやステータスバー、要素を縦または横に並べるだけのレイアウトに適しています。
  • フォームや表形式の配置: grid が最適です。行と列で構造的に配置でき、rowspan, columnspan, sticky を使えば複雑な表組みも可能です。
  • 特殊な配置: ウィジェットを重ねたい場合や、ピクセル単位で厳密な位置指定が必要な場合は place を検討します。ただし、非推奨とされることが多いです。

複数のジオメトリマネージャを組み合わせる場合は、TkFrameなどのコンテナウィジェットで領域を区切り、それぞれのフレーム内で異なるジオメトリマネージャを使用します。例えば、ウィンドウ全体を上下にpackで分割し、上のフレームにはgridでフォームを配置し、下のフレームにはpackでボタンを並べる、といった構造は一般的です。

イベント処理

GUIアプリケーションはイベント駆動型です。ユーザーのマウスクリック、キー入力、ウィンドウのサイズ変更、ウィジェットの値変更など、様々なイベントが発生します。これらのイベントが発生したときに特定の処理を実行するように設定するのがイベント処理です。

Ruby Tkには主に2つのイベント処理方法があります。

  1. command オプション: ボタンやメニュー項目などの一部のウィジェットが持つオプションで、クリックなどの特定のイベント発生時に実行されるProcやlambdaを指定します。最もシンプルなイベント処理方法です。
  2. bind メソッド: より汎用的で強力な方法です。特定のウィジェット、ウィジェットのクラス、またはアプリケーション全体 (root または all) に対して、特定種類のイベント(イベントシーケンス)が発生したときに実行されるProcやlambdaを紐付けます。キー入力やマウスの移動など、より細かいイベントに対応できます。

command オプションによる簡単なイベント処理

これは既に TkButtonTkMenu の項目で見てきました。ボタンがクリックされたときに特定のProcが実行されるように設定する例です。

“`ruby
require ‘tk’

root = TkRoot.new { title “Command Example” }

TkButton.new(root) {
text “実行”
command proc {
puts “ボタンが押されました!”
# ここに実行したい処理を書く
}
}.pack(pady: 20)

Tk.mainloop
“`

command オプションは手軽ですが、ボタンがクリックされたときという特定のイベントにしか対応できません。また、イベントが発生した際のマウスカーソル位置などの詳細な情報を取得することはできません。

bind メソッドによる詳細なイベント処理

bind メソッドは、より多くの種類のイベントに対して、より柔軟な処理を定義できます。

“`ruby
require ‘tk’

root = TkRoot.new { title “Bind Example” }

label = TkLabel.new(root) {
text “ここをクリックまたはキー入力してください”
pack(padx: 30, pady: 30)
}

左クリックイベント () にバインド

label.bind(““, proc { puts “ラベルが左クリックされました” })

ラベルへのマウスエンターイベント ()

label.bind(““, proc {
label.configure(background: ‘yellow’)
})

ラベルからのマウスリーブイベント ()

label.bind(““, proc {
label.configure(background: ‘SystemButtonFace’) # デフォルトの背景色に戻す
})

ウィンドウ全体へのキー入力イベント ()

イベントハンドラProcはイベントオブジェクトを引数に受け取れる

root.bind(““, proc { |event|
# eventオブジェクトからキー情報を取得
puts “キー入力イベント発生: keysym=#{event.keysym}, char=#{event.char}”
})

Return (Enter) キーが押されたイベント ()

root.bind(““, proc { puts “Enterキーが押されました” })

ウィンドウサイズ変更イベント ()

root.bind(““, proc { |event|
# eventオブジェクトから新しいウィンドウサイズを取得
puts “ウィンドウサイズが変更されました: #{event.width}x#{event.height}”
})

ダブルクリックイベント ()

label.bind(““, proc { puts “ラベルがダブルクリックされました” })

バインディングの解除

label.unbind(““) # ラベルの左クリックバインディングを解除

Tk.mainloop
“`

bind メソッドの基本的な形式は widget.bind(event_sequence, proc) です。

  • widget: バインディングを設定する対象のウィジェット(または root または Tk.bind_all の場合はアプリケーション全体)。Tk.bind_class(class_name, event_sequence, proc) を使うと、特定のクラスのすべてのウィジェットにバインディングを設定できます(例: Tk.bind_class('Button', '<Enter>', ...))。
  • event_sequence: 処理を実行したいイベントの種類を指定する文字列。様々な形式があります。
    • <EventType-Modifier-Detail> の形式が多いです。
    • EventType: Button, Key, Motion (マウス移動), Enter (マウスが領域に入る), Leave (マウスが領域から出る), FocusIn, FocusOut, Configure (サイズ変更や移動), Destroy (ウィジェット破棄) など。
    • Modifier: Control, Shift, Alt, Double (ダブルクリック), Triple (トリプルクリック) など。ハイフンで繋げて複数指定できます (<Control-Shift-KeyPress-A>).
    • Detail: マウスボタン (1, 2, 3 – 通常、左、中、右), キーボードのキー (a, b, Return, space, Up, Down, Left, Right, F1, F2, Escape など). KeyPress はキーが押されたとき、KeyRelease はキーが離されたとき。
    • 例: <Button-1> (左クリック), <Control-Button-1> (Ctrl+左クリック), <Key> (任意のキー入力), <KeyPress-a> (‘a’キー押下), <Return> (Enterキー押下), <Configure> (サイズ変更/移動).
  • proc: イベント発生時に実行されるProcまたはlambda。イベントに関する情報が必要な場合は、Procの引数に |event| を指定します。

イベントオブジェクト (event):
bind メソッドのProcに渡されるイベントオブジェクトからは、発生したイベントに関する様々な情報を取得できます。

  • event.x, event.y: イベントが発生したウィジェット内での相対座標(ピクセル)。
  • event.x_root, event.y_root: イベントが発生したスクリーン全体での絶対座標(ピクセル)。
  • event.keysym: 押されたキーの名前を表す文字列(例: "Return", "a", "Control_L")。キーボードイベントの場合。
  • event.char: 押されたキーに対応する文字を表す文字列(例: "a")。キーボードイベントの場合。
  • event.state: イベント発生時の修飾キー(Shift, Controlなど)の状態を示す数値。
  • event.width, event.height: ウィジェットの新しい幅/高さ(Configureイベントの場合)。
  • event.widget: イベントが発生したウィジェットオブジェクト自身。

bind メソッドを使うことで、GUIアプリケーションのあらゆる操作に対してきめ細やかな反応を実装できます。

高度なトピック

Ruby Tkには、基本的なウィジェットと配置、イベント処理の他にも、アプリケーション開発に役立つ様々な機能があります。

変数との連携 (TkVariable)

GUIウィジェットの多くは、その表示内容や状態をRubyの変数と連携させることができます。特に TkEntry, TkLabel, TkCheckButton, TkRadioButton, TkScale などで利用されるのが TkVariable オブジェクトです。

TkVariable を使うと、Ruby側の変数とTkウィジェットの値を双方向で同期させることができます。

“`ruby
require ‘tk’

root = TkRoot.new { title “TkVariable Example” }

TkStringVar: 文字列を扱うTkVariable

string_var = TkStringVar.new(“初期文字列”)

ラベルに変数と連携

label = TkLabel.new(root) {
textvariable string_var # ラベルの表示テキストを string_var と連携
pack(pady: 5)
}

エントリに変数を連携

entry = TkEntry.new(root) {
textvariable string_var # エントリの入力内容を string_var と連携
width 30
pack(pady: 5)
}

ボタンクリックでRuby側から変数を変更

button_ruby = TkButton.new(root) {
text “Ruby側から変数変更”
command proc {
# string_var.value = “Rubyから変更されました! (#{Time.now.strftime(‘%H:%M:%S’)})”
string_var.value = “Rubyから変更されました!”
puts “Ruby側で変数を変更しました。”
}
}.pack(pady: 5)

変数の変更を監視 (trace)

‘w’ は書き込み (variable.value = …) 時

‘r’ は読み込み (variable.value) 時

‘u’ は未設定化 (variable.unset) 時

string_var.trace(‘w’, proc { |name, index, mode|
# トレースProcは変数名、インデックス(配列変数用)、モードを引数に受け取る
# indexは通常空文字列”
puts “変数 ‘#{name}’ が変更されました (モード: #{mode}). 新しい値: #{string_var.value}”
})

TkIntVar: 整数を扱うTkVariable

int_var = TkIntVar.new(0)

スケールに変数を連携

scale = TkScale.new(root) {
from 0
to 100
orient ‘horizontal’
variable int_var
pack(pady: 5)
}

ラベルに整数の値を表示

TkLabel.new(root) {
textvariable int_var # 整数の変数も文字列として表示される
pack(pady: 5)
}

Tk.mainloop
“`

TkVariable にはいくつかの種類があります。
* TkVariable: ジェネリックな変数クラス。
* TkStringVar: 文字列専用。
* TkIntVar: 整数専用。
* TkDoubleVar: 浮動小数点数専用。
* TkBooleanVar: 真偽値専用 (true/false または 0/1)。

ウィジェットの textvariable, variable オプションにこれらの TkVariable インスタンスを指定します。
TkVariable インスタンスの値の取得・設定は .value メソッド(または代入 .value =)で行います。

variable.trace(mode, proc) メソッドを使うと、変数の値が読み込まれた (‘r’)、書き込まれた (‘w’)、または未設定 (‘u’) になったときに、指定したProcを実行できます。これは、ウィジェットの変更に連動して他の処理を行いたい場合に便利です。

Tkの設定オプション (configure)

ウィジェットを作成する際に、ブロック {} の中で様々なオプション(テキスト、色、フォントなど)を設定しました。これらのオプションは、ウィジェット作成後またはプログラムの実行中にも変更することができます。これには configure メソッドを使います。

“`ruby
require ‘tk’

root = TkRoot.new { title “Configure Example” }

label = TkLabel.new(root) {
text “初期テキスト”
font ‘Arial 12’
pack(pady: 10)
}

button_change = TkButton.new(root) {
text “ラベルを変更”
command proc {
# configureメソッドで複数のオプションをまとめて変更
label.configure(
text: “変更後のテキスト (#{Time.now.strftime(‘%H:%M:%S’)})”,
foreground: ‘red’,
font: ‘Times 14 bold’
)
}
}.pack(pady: 10)

特定のオプションの値を取得

button_get = TkButton.new(root) {
text “ラベルのフォントを取得”
command proc {
current_font = label.cget(‘font’) # cgetメソッドで単一のオプションの値を取得
puts “現在のフォント設定: #{current_font}”
}
}.pack(pady: 10)

Tk.mainloop
“`

  • widget.configure(option: value, ...): 一つまたは複数のオプションを新しい値に設定します。
  • widget.cget(option): 指定したオプションの現在の値を取得します。

configure メソッドを使うことで、アプリケーションの状態に応じてウィジェットの見た目や振る舞いを動的に変更できます。

スタイルとテーマ (Ttk)

従来のTkウィジェットは、OSによって見た目が大きく異なったり、少し古臭く見えたりすることがありました。Ttk (Themed Tk) は、よりモダンな見た目を提供し、テーマシステムによって一貫した見た目を実現するための拡張機能です。

Ruby TkでTtkウィジェットを使用するには、require 'tk/ttk' または個別のTtkウィジェット (require 'tk/ttk/button', require 'tk/ttk/label' など) を読み込みます。Ttkウィジェットのクラス名は、元のTkウィジェット名の先頭に Ttk が付きます(例: TkButton -> Ttk::Button)。

“`ruby
require ‘tk’
require ‘tk/ttk’ # Ttkウィジェットとテーマシステムを読み込む

root = TkRoot.new { title “Ttk Example” }

利用可能なテーマを確認

puts “利用可能なテーマ: #{Ttk::Style.theme_names.join(‘, ‘)}”

テーマを設定 (例: ‘clam’, ‘alt’, ‘default’, ‘classic’)

OSによって利用可能なテーマは異なります

Ttk::Style.theme_use(‘clam’) rescue puts “テーマ ‘clam’ は利用できません”

Ttk::Style.theme_use(‘alt’) rescue puts “テーマ ‘alt’ は利用できません”

Ttk::Style.theme_use(‘default’) rescue puts “テーマ ‘default’ は利用できません”

Ttkウィジェットを使用

ttk_label = Ttk::Label.new(root) {
text “これはTtkラベルです”
pack(pady: 10)
}

ttk_button = Ttk::Button.new(root) {
text “Ttkボタン”
command proc { puts “Ttkボタンがクリックされました” }
pack(pady: 10)
}

ttk_entry = Ttk::Entry.new(root) {
pack(pady: 10)
}

プログレスバー (Ttkにのみ存在するウィジェット)

require ‘tk/ttk/progressbar’

ttk_progressbar = Ttk::Progressbar.new(root) {

mode ‘indeterminate’ # または ‘determinate’

pack(pady: 10)

}

ttk_progressbar.start(50) # アニメーション開始 (indeterminateの場合)

Tk.mainloop
“`

Ttkウィジェットは従来のTkウィジェットと互換性がありますが、一部オプションが異なる場合があります。Ttkウィジェットを使うことで、アプリケーションの見た目を現代的にし、OS間で一貫性を持たせることができます。

画像表示 (TkPhotoImage)

ラベル、ボタン、キャンバスなどに画像を表示するには、TkPhotoImage オブジェクトを使います。TkPhotoImage は、GIF形式の画像をネイティブにサポートしています。PNGなどの他の形式を扱うには、Pillow (PIL) などの追加ライブラリが必要になる場合がありますが、Ruby Tk単体ではGIFが最も手軽です。

“`ruby
require ‘tk’

root = TkRoot.new { title “Image Example” }

begin
# GIF画像ファイルを読み込む
# 事前に ‘ruby.gif’ というGIFファイルを同じディレクトリに用意してください
img = TkPhotoImage.new(file: ‘ruby.gif’)

# ラベルに画像を表示
label_img = TkLabel.new(root) {
image img # imageオプションにTkPhotoImageオブジェクトを指定
pack(pady: 10)
}

# ボタンに画像とテキストを一緒に表示
button_img_text = TkButton.new(root) {
image img
text “Ruby”
compound ‘left’ # 画像とテキストの配置 (‘left’, ‘right’, ‘top’, ‘bottom’, ‘center’)
command proc { puts “画像付きボタンがクリックされました” }
pack(pady: 10)
}

rescue Tcl::TclError => e
# ファイルが見つからないなどのエラーハンドリング
TkLabel.new(root) {
text “エラー: 画像ファイルが見つからないか、読み込めません (#{e.message})”
foreground ‘red’
pack(pady: 20)
}.pack
end

Tk.mainloop
“`

TkPhotoImage.new(file: 'filepath') で画像ファイルを読み込みます。作成したTkPhotoImageオブジェクトを、ウィジェットの image オプションに設定します。

描画 (TkCanvas)

TkCanvas ウィジェットを使うと、線、四角形、円、テキスト、画像などを描画したり、これらの描画要素(アイテム)を操作したりすることができます。グラフ描画、簡単なゲーム、カスタマイズされたウィジェット作成などに利用できます。

“`ruby
require ‘tk’

root = TkRoot.new { title “Canvas Example” }

Canvasウィジェットを作成

canvas = TkCanvas.new(root) {
width 300
height 200
background ‘white’
pack(padx: 10, pady: 10)
}

直線を描画

canvas.create_line(50, 50, 250, 150, fill: ‘blue’, width: 2)

四角形を描画

canvas.create_rectangle(100, 80, 200, 120, outline: ‘red’, fill: ‘yellow’, width: 1)

円または楕円を描画 (バウンディングボックスで指定)

canvas.create_oval(120, 90, 180, 110, outline: ‘green’, fill: ‘lightgreen’)

テキストを描画

canvas.create_text(150, 20, text: “Canvas描画テスト”, font: ‘Arial 14 bold’, fill: ‘purple’)

多角形を描画

canvas.create_polygon(10, 180, 50, 160, 90, 180, fill: ‘orange’, outline: ‘darkorange’)

Canvasアイテムへのイベントバインディング

create_* メソッドはアイテムID (整数) を返す

rectangle_item_id = canvas.create_rectangle(10, 10, 60, 40, fill: ‘cyan’)

アイテムIDまたはタグを指定してイベントをバインド

canvas.itembind(rectangle_item_id, ““, proc { puts “四角形がクリックされました (アイテムID: #{rectangle_item_id})” })

アイテムの移動 (例: ボタンクリックで四角形を移動)

button_move = TkButton.new(root) {
text “四角形を移動”
command proc {
# moveメソッドでアイテムを移動 (x, y 方向に相対移動)
canvas.move(rectangle_item_id, 10, 10)
}
}

アイテムの色の変更 (例: ボタンクリックで四角形の色を変更)

button_color = TkButton.new(root) {
text “色を変更”
command proc {
# itemconfigureメソッドでアイテムのオプションを変更
canvas.itemconfigure(rectangle_item_id, fill: ‘magenta’)
}
}

button_move.pack(side: ‘left’, padx: 5, pady: 5)
button_color.pack(side: ‘left’, padx: 5, pady: 5)

Tk.mainloop
“`

Canvasで作成された描画要素は「アイテム」と呼ばれ、それぞれにIDが割り当てられます。
主な描画メソッド:
* create_line(x1, y1, x2, y2, ..., options): 線を描画。
* create_rectangle(x1, y1, x2, y2, options): 四角形を描画。
* create_oval(x1, y1, x2, y2, options): 楕円または円を描画(バウンディングボックスの対角線座標を指定)。
* create_text(x, y, options): テキストを描画。
* create_image(x, y, options): 画像を描画。
* create_polygon(x1, y1, x2, y2, ..., options): 多角形を描画。

Canvasアイテムを操作するメソッド:
* coords(item_id, *coords): アイテムの座標を取得または設定。
* move(item_id, dx, dy): アイテムを相対移動。
* delete(item_id_or_tag): アイテムを削除。
* itemconfigure(item_id_or_tag, options): アイテムのオプションを変更。
* itembind(item_id_or_tag, event_sequence, proc): アイテムにイベントをバインド。

マルチスレッドとGUI

Tk(そしてRuby Tk)は基本的にシングルスレッドで動作します。Tk.mainloop が実行されている間、GUIイベントを処理するためのメインスレッドが専有されます。もし時間のかかる処理(ファイルの読み書き、ネットワーク通信、複雑な計算など)をメインスレッドで行うと、GUIが応答しなくなり、フリーズしたように見えます。

時間のかかる処理を行う場合は、別スレッドで行うのが一般的です。しかし、バックグラウンドスレッドから直接GUIウィジェットを操作することは、スレッドセーフではないため避けるべきです。バックグラウンドスレッドの処理結果をGUIに反映させたい場合は、メインスレッドに対してGUI更新のリクエストをキューに入れるなどの方法を取る必要があります。

Ruby Tkでは、Tk.after(milliseconds, proc) メソッドを使って、指定したミリ秒後にメインスレッド上でProcを実行させることができます。これを利用して、バックグラウンドスレッドからメインスレッドに処理結果を渡し、GUIを更新するというパターンが考えられます。

“`ruby
require ‘tk’
require ‘thread’ # スレッドを使うために必要

root = TkRoot.new { title “Multithread Example” }

label = TkLabel.new(root) {
text “処理待機中…”
pack(pady: 20)
}

button = TkButton.new(root) {
text “時間のかかる処理を開始”
command proc {
button.state(‘disabled’) # ボタンを無効化

# バックグラウンドスレッドで時間のかかる処理を実行
Thread.new do
  puts "バックグラウンド処理開始..."
  sleep 3 # 3秒間待機する時間のかかる処理の例
  result = "処理が完了しました! (#{Time.now.strftime('%H:%M:%S')})"
  puts "バックグラウンド処理完了。"

  # メインスレッドにGUI更新を依頼
  # Tk.after を使って、メインスレッドのイベントループでProcを実行させる
  Tk.after(0, proc {
    label.configure(text: result) # GUIウィジェットの更新
    button.state('normal')      # ボタンを有効化
    puts "GUIが更新されました。"
  })
end

}
}.pack(pady: 10)

Tk.mainloop
“`

この例では、ボタンクリック時に新しいスレッドを作成し、その中で sleep という時間のかかる処理を行っています。処理が終わったら、Tk.after(0, ...) を使って、メインスレッドのイベントループが次にアイドル状態になったときに実行されるProcを登録しています。このProcの中でGUI(ラベルとボタンの状態)を更新しています。このようにすることで、時間のかかる処理中でもGUIがフリーズすることを防ぎ、処理完了後にGUIを安全に更新できます。

国際化対応 (i18n)

Ruby TkはUnicode (UTF-8) をサポートしており、日本語などの多言語文字を扱うことができます。ラベルやボタンのテキスト、テキストウィジェットへの入力などで、UTF-8文字列をそのまま使用できます。

フォントについては、OSにインストールされている適切なフォントを指定する必要があります。例えば、日本語を表示するには日本語フォントを指定します。

“`ruby
require ‘tk’

root = TkRoot.new { title “国際化対応 Example” }

日本語テキストを表示するラベル

TkLabel.new(root) {
text “こんにちは、世界!”
# 日本語フォントを指定 (環境に合わせて適切なフォント名に変更してください)
font ‘Meiryo 14’ # Windowsの場合の例
# font ‘Hiragino Maru Gothic ProN 14’ # macOSの場合の例
# font ‘Takao Gothic 14’ # Linuxの場合の例
pack(pady: 10)
}

他の言語のテキストも表示可能 (適切なフォントがあれば)

TkLabel.new(root) { text “Hello, World!” }.pack

TkLabel.new(root) { text “Hola, Mundo!” }.pack

Tk.mainloop
“`

アプリケーション内で表示する文字列を外部ファイル(リソースファイル)に分離し、実行時にユーザーのロケールに応じて適切な言語の文字列を読み込むことで、本格的な国際化(i18n)および地域化(l10n)に対応できます。Rubyには gettext などのライブラリがありますが、Tk自体が提供する直接的な国際化機能は限られており、Ruby側の仕組みと組み合わせて実現します。

Ruby/Tkの限界と他の選択肢との比較

Ruby Tkは標準ライブラリとして手軽に使えるのが大きな魅力ですが、いくつかの限界もあります。

  • 見た目とカスタマイズ性: 従来のTkウィジェットの見た目はOS標準から少しずれていたり、古く見えたりすることがあります。Ttkを使えば改善されますが、非常に凝ったデザインや高度なカスタムウィジェットの作成には限界があります。
  • 機能: 提供されるウィジェットは基本的で網羅的ですが、Webビューや高度なグラフ描画、3Dグラフィックスなどの機能は含まれていません。これらが必要な場合は、別のライブラリや外部コンポーネントとの連携が必要になります。
  • パフォーマンス: 大量のデータを扱うリスト表示や、複雑な描画をリアルタイムで行う場合など、特定のシナリオではパフォーマンスが問題になる可能性があります。特に起動速度は他のGUIツールキットに比べて遅いという指摘もあります。
  • コミュニティ/情報: 他のGUIツールキット(Qt, Gtkなど)に比べて、Ruby Tkのユーザーコミュニティは小さめかもしれません。日本語の情報も限られていることがあります。

RubyでGUI開発を検討する際の他の主な選択肢:

  • Gtk (GObject Introspection): GNOMEデスクトップ環境などで使われるライブラリです。RubyからGtk+を呼び出すためのruby-gir-ffiなどのライブラリがあります。モダンな見た目と豊富なウィジェット、クロスプラットフォーム対応が特徴です。C言語で書かれているため高速ですが、Rubyからの利用にはバインディングが必要です。
  • Qt (Qt bindings): KDEデスクトップ環境などで使われる非常に高機能なライブラリです。qtbindings (現在は非推奨で開発停止気味) や、より新しい rqt (PyQt/PySideのラッパー) などがあります。洗練された見た目、豊富なウィジェット、強力なツール(Qt Designerなど)が特徴です。商用利用にはライセンスに注意が必要な場合があります。
  • Shoes: Rubyistによって開発された、シンプルさと手軽さを追求したGUIライブラリです。独自の描画エンジンを持ち、手軽にグラフィカルなアプリケーションを作成できます。TkやGtk/Qtに比べて機能は限定的ですが、プロトタイピングや簡単なアプリケーションには非常に手軽です。

Ruby Tkは「標準ライブラリで手軽にGUIを試したい」「簡単な設定ツールやユーティリティを作りたい」といった用途に非常に適しています。より高度な機能や洗練された見た目が必要な場合は、GtkやQtなどの他のライブラリを検討するのが良いでしょう。

サンプルアプリケーションの作成

ここまで学んだウィジェット、ジオメトリマネージャ、イベント処理、変数連携などの知識を使って、簡単なGUIアプリケーションを作成してみましょう。

簡単な電卓アプリ

数値入力フィールド、数字ボタン、演算子ボタン、結果表示ラベルを備えた簡単な電卓を作成します。

“`ruby
require ‘tk’
require ‘tk/ttk’ # モダンなTtkウィジェットを使う

root = TkRoot.new { title “簡単な電卓” }

ウィジェットを配置するためのフレーム

main_frame = Ttk::Frame.new(root) { pack(padx: 10, pady: 10) }

結果を表示するエントリフィールド

readonly にして、ユーザーが直接編集できないようにする

result_var = TkStringVar.new(“0”)
result_entry = Ttk::Entry.new(main_frame) {
textvariable result_var
state ‘readonly’
justify ‘right’ # 右寄せ
width 25 # 幅を広げる
pack(pady: 10, fill: ‘x’) # fill ‘x’ で横幅いっぱいに広がる
}

ボタンを配置するフレーム (グリッドを使う)

button_frame = Ttk::Frame.new(main_frame) { pack }

ボタンクリック時の処理を定義するProc

押されたボタンのテキスト(’0′..’9′, ‘+’, ‘-‘, ‘*’, ‘/’, ‘=’)を受け取る

calculator = Object.new # 計算ロジックを保持するオブジェクト (簡単のため単なるObject)
calculator.instance_variable_set(:@current_input, “0”) # 現在入力中の値
calculator.instance_variable_set(:@operator, nil) # 現在選択されている演算子
calculator.instance_variable_set(:@first_operand, nil) # 最初のオペランド

def calculator.button_clicked(button_text, result_var)
current_input = @current_input
operator = @operator
first_operand = @first_operand

case button_text
when /[0-9]/
# 数字ボタン
if current_input == “0” || (operator && first_operand.nil?)
current_input = button_text # 最初の数字または演算子入力後の最初の数字
else
current_input += button_text # 数字を追加
end
@first_operand = nil if operator # 演算子入力後の入力を開始したら最初のオペランドフラグをクリア
result_var.value = current_input # 結果表示を更新

when ‘+’, ‘-‘, ‘*’, ‘/’
# 演算子ボタン
if first_operand.nil?
# 最初のオペランドを設定
@first_operand = current_input.to_f
else
# 続けて演算子が押された場合、前回の演算で結果を計算
calculate_result(result_var)
@first_operand = result_var.value.to_f # 計算結果を次の最初のオペランドにする
end
@operator = button_text # 演算子を記憶
@current_input = “0” # 次の数値入力を準備

when ‘=’
# 等号ボタン
if operator && first_operand
calculate_result(result_var)
# 計算後は状態をリセット
@current_input = result_var.value # 計算結果を入力中の値とする
@operator = nil
@first_operand = nil
end

when ‘C’
# クリアボタン
@current_input = “0”
@operator = nil
@first_operand = nil
result_var.value = “0” # 結果表示をクリア

when ‘.’
# 小数点ボタン
unless current_input.include?(‘.’)
current_input += “.”
result_var.value = current_input
end
@current_input = current_input

end
end

def calculator.calculate_result(result_var)
begin
operand2 = @current_input.to_f
result = case @operator
when ‘+’ then @first_operand + operand2
when ‘-‘ then @first_operand – operand2
when ‘*’ then @first_operand * operand2
when ‘/’
if operand2 == 0
raise ZeroDivisionError, “ゼロによる除算”
end
@first_operand / operand2
else return
end

# 結果を文字列として表示
result_var.value = result.to_s
@current_input = result_var.value # 計算結果を次の入力開始値とする

rescue => e
result_var.value = “エラー: #{e.message}”
@current_input = “0”
@operator = nil
@first_operand = nil
end
end

ボタンの配置データ (テキスト, 行, 列, 横結合)

button_layout = [
[‘7’, 0, 0], [‘8’, 0, 1], [‘9’, 0, 2], [‘/’, 0, 3],
[‘4’, 1, 0], [‘5’, 1, 1], [‘6’, 1, 2], [‘*’, 1, 3],
[‘1’, 2, 0], [‘2’, 2, 1], [‘3’, 2, 2], [‘-‘, 2, 3],
[‘0’, 3, 0], [‘.’, 3, 1], [‘=’, 3, 2, 2], # ‘=’ ボタンは2列結合
[‘C’, 4, 0, 4] # ‘C’ ボタンは4列結合
]

ボタンをgridで配置

button_layout.each do |(text, row, col, columnspan)|
button = Ttk::Button.new(button_frame) {
text text
# commandオプションに Proc.new を使い、calculatorオブジェクトのメソッドを呼び出す
# button_text と result_var を引数として渡す
command proc { calculator.button_clicked(text, result_var) }
# command proc { calculator.method(:button_clicked).call(text, result_var) } # 別記法
}
# gridで配置。columnspanがあれば指定
button.grid(row: row, column: col, columnspan: columnspan, sticky: ‘nsew’, padx: 2, pady: 2)
end

ウィンドウサイズ変更時にボタンも拡大するように設定

各列と行にweightを設定

4.times do |i|
button_frame.grid_columnconfigure(i, weight: 1)
end
5.times do |i|
button_frame.grid_rowconfigure(i, weight: 1)
end

Tk.mainloop
“`

この電卓アプリは、TkStringVar で結果表示と入力を管理し、Ttk::EntryTtk::Button を使用しています。ボタンの配置には grid ジオメトリマネージャを使用し、sticky: 'nsew'grid_columnconfigure, grid_rowconfigure を使って、ウィンドウサイズ変更時にボタンが自動的に拡大縮小するように設定しています。ボタンクリック時のロジックは、calculator というオブジェクトに encapsulated しています。

簡単なTODOリストアプリ

テキスト入力フィールド、追加ボタン、リストボックス、削除ボタンを備えた簡単なTODOリストを作成します。

“`ruby
require ‘tk’
require ‘tk/ttk’
require ‘tk/listbox’
require ‘tk/scrollbar’ # Listboxと連携するため

root = TkRoot.new { title “簡単なTODOリスト” }

メインフレーム

main_frame = Ttk::Frame.new(root) { pack(padx: 10, pady: 10, fill: ‘both’, expand: true) }

新しいTODO項目入力エリア

input_frame = Ttk::Frame.new(main_frame) { pack(fill: ‘x’) }

entry_todo = Ttk::Entry.new(input_frame) { width 40 }
entry_todo.pack(side: ‘left’, fill: ‘x’, expand: true, padx: 5, pady: 5)

TODO項目追加ボタン

button_add = Ttk::Button.new(input_frame) {
text “追加”
command proc {
todo_text = entry_todo.get.strip # 入力テキストを取得し、前後空白を除去
if !todo_text.empty?
listbox_todo.insert(‘end’, todo_text) # リストボックスの最後に追加
entry_todo.delete(0, ‘end’) # 入力フィールドをクリア
end
}
}
button_add.pack(side: ‘left’, padx: 5, pady: 5)

Enterキーでも追加できるようにEntryにバインド

entry_todo.bind(‘‘, button_add.cget(‘command’)) # ボタンのcommand Procを再利用

TODOリスト表示エリア (ListboxとScrollbarをフレームで囲む)

list_frame = Ttk::Frame.new(main_frame) { pack(fill: ‘both’, expand: true) }

TODOリストボックス

listbox_todo = TkListbox.new(list_frame) {
height 10 # 表示行数
selectmode ‘single’ # 単一選択
pack(side: ‘left’, fill: ‘both’, expand: true) # スクロールバーと連携するため side: ‘left’
}

スクロールバー

scrollbar_todo = Ttk::Scrollbar.new(list_frame) {
orient ‘vertical’
pack(side: ‘right’, fill: ‘y’)
}

リストボックスとスクロールバーを連携

listbox_todo.yscrollcommand scrollbar_todo.method(:set)
scrollbar_todo.command listbox_todo.method(:yview)

選択した項目を削除するボタン

button_delete = Ttk::Button.new(main_frame) {
text “選択した項目を削除”
command proc {
# 選択されている項目のインデックスリストを取得 (単一選択モードでもリストで返る)
selected_indices = listbox_todo.curselection
unless selected_indices.empty?
# 後ろから削除しないとインデックスがずれるため、逆順に処理
selected_indices.reverse.each do |index|
listbox_todo.delete(index)
end
end
}
}
button_delete.pack(pady: 10)

初期項目を追加 (任意)

[“牛乳を買う”, “部屋を掃除する”, “Ruby Tkを学ぶ”].each do |item|
listbox_todo.insert(‘end’, item)
end

Tk.mainloop
“`

このTODOリストアプリでは、Ttk::Entry で項目を入力し、Ttk::Button で追加、TkListboxTtk::Scrollbar でリストを表示・スクロール可能にし、別の Ttk::Button で選択項目を削除します。pack ジオメトリマネージャをフレームで階層的に使用し、fillexpand オプションでウィンドウサイズ変更に追従するようにしています。Entry ウィジェットに <Return> キーのバインディングを設定し、Enterキーで項目を追加できるようにしているのもポイントです。Listboxの項目削除は、選択された複数の項目を削除する場合にインデックスのずれを防ぐため、インデックスを逆順にして処理しています。

デバッグとトラブルシューティング

GUIアプリケーションの開発では、レイアウトが意図した通りにならない、イベントが発火しない、予期しないエラーが発生するといった問題によく遭遇します。Ruby Tkでのデバッグやトラブルシューティングのヒントをいくつか紹介します。

  1. エラーメッセージを読む: Ruby Tkでエラーが発生した場合、Tk (Tcl) レベルでのエラーとRubyレベルでのエラーの両方が出力されることがあります。スタックトレースを注意深く読み、エラーが発生したTkコマンドやRubyのコード行を特定することが第一歩です。特にTclのエラーメッセージは情報が少ないこともありますが、どのウィジェットやオプションに関連しているか手掛かりになることがあります。
  2. puts デバッグ: 最も基本的ですが効果的なデバッグ方法です。Procやコマンドが実行されているか、変数の値がどうなっているかなどを、puts でコンソールに出力して確認します。イベントハンドラの先頭に puts "イベント #{event_sequence} が発生しました" のような行を入れると、イベントが正しくバインドされているか確認できます。
  3. ジオメトリマネージャの問題: レイアウトが崩れる問題は、ジオメトリマネージャのオプション(side, fill, expand, sticky, weight など)の組み合わせが原因であることが多いです。
    • pack: side の順番が重要です。フレームで区切って、それぞれのフレーム内で独立したpackレイアウトを組むと管理しやすくなります。
    • grid: row, column, sticky の指定が正しいか確認します。ウィンドウサイズ変更時に伸縮しない場合は、grid_columnconfigure, grid_rowconfigureweight オプションの設定を確認します。フレームのfillexpandも影響します。
    • place: ウィンドウサイズ変更時にウィジェットの位置やサイズが変わるか確認します。相対座標/サイズ (relx, rely, relwidth, relheight) と絶対座標/サイズ (x, y, width, height) のどちらを使うべきか、anchor は正しいかを確認します。
  4. ウィジェットのオプション: ウィジェットが表示されない、期待通りに動作しない場合、指定したオプション名や値が間違っている可能性があります。公式ドキュメントやリファレンスでオプションを確認します。widget.cget('option_name') で実行時のオプション値を確認することも有効です。
  5. イベントバインディングの確認: bind が正しく機能しない場合、イベントシーケンスの文字列が正しいか、対象ウィジェットが正しいか、Procが正しく定義されているかを確認します。Tk.bind_allTk.bind_class が意図しないウィジェットに影響を与えていないかも確認します。
  6. tkconsole: Tkには対話型のコンソール機能があります。これを使うと、実行中のGUIアプリケーションのウィジェットツリーを参照したり、Tcl/Tkコマンドを直接実行してウィジェットの状態を確認したり、オプションを変更したりできます。Ruby Tkからこれを利用するには、require 'tk/console' を追加し、必要に応じてコンソールを開くウィジェット(例: メニュー項目)を用意します。

    “`ruby

    TkConsoleを起動する例 (デバッグ用)

    require ‘tk’
    require ‘tk/console’

    root = TkRoot.new { title “Debug Example” }

    TkButton.new(root) {
    text “TkConsoleを開く”
    command proc { Tk.console } # TkConsoleウィンドウを開く
    }.pack(pady: 20)

    Tk.mainloop
    ``
    TkConsoleを開くと、Tcl/Tkのコマンドを入力できます。例えば、
    .でルートウィンドウ、.buttonでボタンウィジェットを参照し、.label configure -text “新しいテキスト”` のようにコマンドを実行できます。

これらのデバッグ手法を組み合わせることで、GUIアプリケーションの挙動を理解し、問題を特定して修正することができます。

まとめ:Ruby Tkを使ったGUI開発の第一歩

この記事では、Ruby Tkを使ったGUIプログラミングについて、その基本的な仕組みから様々なウィジェットの使い方、配置方法、イベント処理、そしてより高度なトピックまでを網羅的に解説しました。約5000語の詳細な解説と多数のコード例を通して、Ruby Tkアプリケーション開発の基礎をしっかりと学ぶことができたはずです。

Ruby Tkの魅力は、何と言ってもその手軽さにあります。Rubyの標準ライブラリとして提供されているため、特別なインストールなしにGUI開発を始められます。シンプルなAPIはGUIプログラミングの基本的な概念を学ぶのに適しており、ちょっとした個人的なツールやユーティリティをGUI化したい場合に非常に強力な選択肢となります。

もちろん、モダンな大規模アプリケーションや、非常に複雑でカスタマイズ性の高いGUIが必要な場合は、QtやGtkといった他のライブラリの方が適している場合もあります。しかし、GUI開発の入門として、あるいは手軽さを重視する場合であれば、Ruby Tkは十分に検討に値する、むしろ最適な選択肢の一つと言えるでしょう。

Ruby Tkを学ぶ次のステップ

この記事でカバーした内容は、Ruby Tkの機能の大部分を網羅していますが、さらに深く学ぶためのステップをいくつか紹介します。

  • 公式ドキュメントやリファレンスを参照する: Ruby Tkの公式ドキュメントやTcl/Tkのリファレンスは、利用可能なすべてのウィジェット、オプション、メソッドに関する詳細な情報源です。特に特定のウィジェットの詳細なオプションや、特殊なイベントシーケンスなどを調べたい場合に役立ちます。
  • 既存のサンプルコードを読む: Ruby Tkで書かれたオープンソースのアプリケーションや、Tkライブラリのサンプルコードを読むことは、実践的なテクニックや慣用的なパターンを学ぶのに非常に効果的です。
  • 小さなアプリケーションをたくさん作る: 学んだ知識を使って、実際に様々なGUIアプリケーションを作成してみてください。電卓やTODOリストだけでなく、ファイル操作ツール、シンプルなゲーム、データ可視化ツールなど、興味のあるテーマで手を動かすことが、習得への一番の近道です。
  • Ttkのスタイルやテーマを詳しく調べる: アプリケーションの見た目を向上させたい場合は、Ttkのスタイルシステムをさらに深く掘り下げてみてください。カスタムスタイルを定義したり、既存のスタイルを変更したりすることで、より洗練されたUIを作成できます。

読者へのメッセージ

これで、あなたはRubyを使ったGUIプログラミングの世界への第一歩を踏み出しました。GUI開発は、ユーザーが直接触れるインターフェースを作るため、すぐに結果が目に見え、非常にやりがいのある分野です。ぜひ、この記事で学んだ知識を活かして、あなたのアイデアをGUIアプリケーションとして実現させてみてください。

最初は戸惑うこともあるかもしれませんが、ウィジェットの配置、イベントのハンドリング、データの連携といった基本的なパターンを繰り返すうちに、自然とGUIプログラミングの考え方が身についていきます。

さあ、あなただけのRuby Tkアプリケーション開発を楽しんでください!

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール