TreeViewウィジェットの概要 - Gtk::TreeView、Gtk::TreeModel、およびその関連クラスについて

※ このページは、GTK+ 2 Reference Manual: Tree and List Widget Overviewを翻訳し、Ruby-GNOME2向けに書き換えたものですが、サンプルコードに使う言語の違いのせいもあって、大幅に文章の追加・改変をしています。後から追加した部分は背景色を変えていますが、厳密に区別できていないところもあります。

このページの目次

ツリー/リスト表示を実現するための主な部品
"モデル"を作成する

ストアオブジェクトの生成
ストアオブジェクトにデータを追加する

"ビュー"部を組み立てる

TreeViewColumnとCellRenderer
ユーザによる選択操作への対応

(比較的)シンプルなサンプル
参考リンク
このページの更新履歴
脚注

ツリー/リスト表示を実現するための主な部品

 GTK+でツリーやリストのような"表(テーブル)"型のユーザインターフェースの表示を行うには、Gtk::TreeViewウィジェットとGtk::TreeModelインターフェース(注1)を連携させる必要があります。

 他のGUIライブラリではリストビューと呼ばれることが多いユーザインターフェースも、GTK+ではGtk::TreeViewクラスで作成します。

 GTK+でのツリー/リスト表示の仕組みは、"モデル/ビュー/コントローラ(MVC)"パターン(注2)に則ってデザインされており、主に次の4つのパーツから成り立っています。

Gtk::TreeViewクラス

"表"全体または枠を表すクラス。他のすべてのクラスを活用するための基盤、ベースとしての入れ物。

Gtk::TreeViewColumnクラス

"表"の列を表すクラス。

Gtk::CellRendererクラス

"表"のセルを表すクラス(ただしTreeView専用というわけではありません)。このクラスは抽象クラスなので、実際にTreeViewを使うときには派生クラスのGtk::CellRendererText、Gtk::CellRendererPixbuf、Gtk::CellRendererToggleなどを利用します。

Gtk::TreeModelモジュールをインクルードしたクラス

"表"に表示するデータを表すクラス。組み込みで2種類のクラスが用意されています。TreeViewをリストビュー形式で利用する時にはGtk::ListStoreを使い、ツリービュー形式で利用する時にはGtk::TreeStoreを使います。

MVCパターンで言えば、上の3つのクラスが"ビュー"を構成し、最後の一つが"モデル"になります。

 MVCパターンを用いるメリットはいくつかありますが、大きなものを一つ挙げると、単一の"モデル"を元に複数の"ビュー"を作れるというのがあります。

例えば、ファイルシステムをマッピングした"モデル"を作り、それを活用してファイル管理アプリケーションを作るという場合です。("モデル"と"ビュー"を分離しておくことで、)ファイルシステムを構成するさまざまな要素を、データとしては一つだけメモリ内に持っておきつついろいろな形で表示する、などといったことが簡単にできます。

 CellRendererは、ウィジェットに一種の拡張性を提供します。CellRendererによって、単一の型のデータを表示するのに、複数の描画方式から選んで実行することが可能になっています。

例として、変数に入っている真偽値をどう描画するかを考えてみましょう。"True"か"False"あるいは"On"か"Off"といった文字列として描画したい場合もあるでしょうし、チェックボックスを置きたいと思う時もあるでしょう。このような選択が可能です。

"モデル"を作成する

 ここからは具体的なクラスの使い方になります。まずは表示データを準備します。

ストアオブジェクトの生成

 GTK+では、"モデル"として利用できるシンプルなクラスが2つ用意されています。Gtk::ListStoreクラスとGtk::TreeStoreクラスです。ListStoreはリストビューのための"モデル"、TreeStoreはツリービューのための"モデル"です。これら以外に新しい型の"モデル"を作ることもできますが、よほど特別な場合を除いては既存の"モデル"で足りるはずです。

 ストアオブジェクトを生成するコードは例えば以下のようになります。

model = Gtk::ListStore.new( String, Object )

この例では、1行分のデータとしてStringオブジェクトとObjectオブジェクトの2つの項目を持つストアオブジェクトを生成しています。TreeStoreを利用する場合でも、newメソッドの呼び出し方は同じです。

 ここで言う行とは、"表"の構成要素としての行(row=横列)のことです。ただし後で詳しく説明しますが、ここでの項目数と、プログラムユーザが目にする"表"の列(column=縦列)の数は一致しませんので注意してください。(注3)

 このnewメソッドでは、引数で行データの定義をしているわけですが、実データはこの後、全行分このストアオブジェクトに格納します。

 newメソッドの引数には、型(クラス)名を指定します。Object型はデータとして真偽値を扱いたい場合などに指定します。真偽値が必要な例としては、チェックボックスの状態を表す値に使うなどがあります。例に挙げた以外にも、もちろんIntegerやGdk::Pixbufなども指定できます。

ストアオブジェクトにデータを追加する

 ストアオブジェクトにデータを追加するには、まずストアオブジェクトのappendメソッド(Gtk::ListStore#appendまたはGtk::TreeStore#append)を呼び出して新しい行(データの格納スペース)を作成します。

このメソッドは、ListStoreから呼び出す場合引数はなく、戻り値として、新しく作った行のGtk::TreeIterオブジェクトが返ります。TreeIterはストアオブジェクト内の特定の行を指し示すためのクラスです。

TreeStoreから呼び出す場合には、ツリーの親子関係を明示するために、引数にツリーの親を示すTreeIterオブジェクトを指定します。親がない行を追加するには、引数にnilを指定します。戻り値として、新しく作った行のGtk::TreeIterオブジェクトが返るのは、ListStoreと同じです。子として追加した行は、TreeView上では親がオープン状態の時のみ表示されます。

 データの格納スペースを用意したら、ストアオブジェクトのset_valueメソッドを呼び出してデータを格納します。このメソッドには、追加するデータの他に、appendメソッドの戻り値のTreeIterオブジェクトと、追加する項目位置を渡します。

 以下はツリービューを作る場合のコードのサンプルです。

MODEL_FIELD_NAME = 0
MODEL_FIELD_NAME_COLOR = 1
MODEL_FIELD_TITLE = 2
MODEL_FIELD_CHECKED = 3

model = Gtk::TreeStore.new( String, String, String, Object )

iter = model.append( nil )
model.set_value( iter, MODEL_FIELD_NAME, 'Michael Jackson' )
model.set_value( iter, MODEL_FIELD_NAME_COLOR, 'blue' )
iter = model.append( iter )
model.set_value( iter, MODEL_FIELD_TITLE, 'BAD' )
model.set_value( iter, MODEL_FIELD_CHECKED, 'true'=='true' )

 上の例では、追加するデータを直書きしていますが、実際にプログラムを書く時には、例えばファイルから配列などに読み込んだものを指定することになるでしょう。

 Gtk::TreeStore.newメソッドで指定している行あたりのデータ項目は4つありますが、このうちMODEL_FIELD_NAME_COLOR番目のデータは、TreeView上で独立した列(column)として表示されるものではなく、MODEL_FIELD_NAME番目のデータを表示する時の文字色として使われるものという想定でセットしています。

このページの最後にあるサンプルスクリプトを実行すると、ストアオブジェクトの行あたりのデータ項目数とウィンドウに表示されるTreeViewの列(column)数が一致しないケースの動作を確認できます。

 ツリービューを作る時に注意する必要があるのは、Gtk::TreeStore.newメソッドの引数に、ツリーの親で表示する項目も、子で表示する項目もすべて並べておくことです(親でも子でも使うものを重複して登録する必要はありません)。その上で、行ごとに必要な項目のみset_valueメソッドで値をセットします。

 MODEL_FIELD_CHECKED番目のデータを、==メソッドを使って真偽値に変換しているのは、チェックボックスの状態を表す値として使うという想定です。

"ビュー"部を組み立てる

 ここからはデータ表示を担当する部分の作り方です。

 ここまでで説明したように、"モデル"(ストアオブジェクト)は複数の種類を使い分ける必要がありますが、"ビュー"部分には常に同じクラスを使います。これでリストビューもツリービューも作ることができます。

 まずは、表示したいストアオブジェクトを指定してベース部分のTreeViewオブジェクトを生成します。

view = Gtk::TreeView.new( model )

TreeViewColumnとCellRenderer

 後は、データをどこにどう表示するかに関わる部品をTreeView上に並べていきます。それがGtk::CellRenderer系クラスとGtk::TreeViewColumnクラスです。

 セルを表すCellRendererは、データの表示方法ごとに派生クラスがあり、GTK+2では、Gtk::CellRendererText、Gtk::CellRendererPixbuf、Gtk::CellRendererToggleなど、さまざまなものが利用できます。独自のCellRendererを作ることもそれほど難しくはありません。

 TreeViewColumnは、TreeViewの列(column)を管理するためのクラスです。オブジェクト生成時に、TreeViewで表示するための名前、データの表示に使うCellRenderer、ストアオブジェクトのデータのどの項目を取得してそれをCellRendererのどのプロパティにセットするか、の3種類の情報を引数として指定します。最後の3つ目の情報は複数指定できます。

 下のサンプルは、上のサンプルで出てきたストアオブジェクトを使う想定で書かれています。

renderer1 = Gtk::CellRendererText.new
renderer2 = Gtk::CellRendererText.new
renderer3 = Gtk::CellRendererToggle.new

column = Gtk::TreeViewColumn.new( 
  "Name", renderer1, 
  :text => MODEL_FIELD_NAME, 
  :foreground => MODEL_FIELD_COLOR )
view.append_column( column )

column = Gtk::TreeViewColumn.new( 
  "Title", renderer2, 
  :text => MODEL_FIELD_TITLE )
view.append_column( column )

column = Gtk::TreeViewColumn.new( 
  "Checked", renderer3, 
  :active => MODEL_FIELD_CHECKED )
view.append_column( column )

 TreeViewを作成してデータを表示する手順は以上です。"モデル"を生成してデータを格納し、TreeView、TreeViewColumn、CellRendererを用意して"ビュー"として組み立てました。

 動くプログラムとしてのサンプルは、このページの最後の(比較的)シンプルなサンプルを見てください。

ユーザによる選択操作への対応

 実際にユーザが使うアプリケーションでは、データを表示するだけでなく、ユーザからの入力を受け付ける必要があります。

TreeViewでの一例として、TreeViewのGtk::TreeSelectionオブジェクトを取得して、その“changed”シグナルと何らかの処理をコネクトする方法があります。

select = view.selection
select.mode = Gtk::SELECTION_SINGLE

select.signal_connect( 'changed' ) do |obj, *args|
  if obj.selected
    value = model.get_value( obj.selected, MODEL_FIELD_NAME )
    if value == nil
      puts 'TITLE node is selected.'
    else
      puts 'NAME node is selected.'
    end
  end
end

 このサンプルでは、TreeViewの選択項目が変わるごとに、ストアオブジェクトの選択行を調べて、MODEL_FIELD_NAME番目の項目がセットされているかどうかで親か子かを判定しています。

 このサンプルも上の方で出てきたストアオブジェクトを意識して読んでください。

(比較的)シンプルなサンプル

# coding: utf-8
require 'gtk2'
#require 'gtk3'

MODEL_FIELD_NAME = 0
MODEL_FIELD_COLOR = 1
MODEL_FIELD_TITLE = 2
MODEL_FIELD_CHECKED = 3

model = Gtk::TreeStore.new( String, String, String, Object )
iter = model.append( nil )
model.set_value( iter, MODEL_FIELD_NAME, 'Michael Jackson' )
model.set_value( iter, MODEL_FIELD_COLOR, 'blue' )
iter = model.append( iter )
model.set_value( iter, MODEL_FIELD_TITLE, 'BAD' )
model.set_value( iter, MODEL_FIELD_CHECKED, 'true'=='true' )
iter = model.append( nil )
model.set_value( iter, MODEL_FIELD_NAME, 'Janet Jackson' )
model.set_value( iter, MODEL_FIELD_COLOR, 'red' )
iter = model.append( iter )
model.set_value( iter, MODEL_FIELD_TITLE, 'Black Cat ' )
model.set_value( iter, MODEL_FIELD_CHECKED, 'false'=='true' )

view = Gtk::TreeView.new( model )

select = view.selection
select.mode = Gtk::SELECTION_SINGLE
# for gtk3
#select.mode = Gtk::SelectionMode::SINGLE
select.signal_connect( 'changed' ) do |obj, *args|
  if obj.selected
    value = model.get_value( obj.selected, MODEL_FIELD_NAME )
    if value == nil
      puts 'TITLE node is selected.'
    else
      puts 'NAME node is selected.'
    end
  end
end

renderer1 = Gtk::CellRendererText.new
renderer2 = Gtk::CellRendererText.new
renderer3 = Gtk::CellRendererToggle.new

column = Gtk::TreeViewColumn.new( 
  "Name", renderer1, 
  :text => MODEL_FIELD_NAME, 
  :foreground => MODEL_FIELD_COLOR )
view.append_column( column )

column = Gtk::TreeViewColumn.new( 
  "Title", renderer2, 
  :text => MODEL_FIELD_TITLE )
view.append_column( column )

column = Gtk::TreeViewColumn.new( 
  "Checked", renderer3, 
  :active => MODEL_FIELD_CHECKED )
view.append_column( column )

window = Gtk::Window.new
window.set_size_request( 400, 150 )
window.signal_connect( 'destroy' ) { Gtk.main_quit }
window.add( view )

window.show_all
Gtk::main
※ このコードはUTF-8エンコーディングで保存して実行してください。

参考リンク

 Ruby-GNOME2 Project Website (公式Wiki)

このページの更新履歴

2014/08/10

 ツリー/リスト表示を実現するための主な部品の項目を修正。

2014/06/01

 脚注1を修正。

2014/05/25

 公開


注1: インターフェース
 ここでは、「ウィジェット」という言葉と並列で、クラスの種類を表す言葉として使っているように思います。「GUI部品クラス」に対して「抽象クラス」という感じで。

注2: "モデル/ビュー/コントローラ(MVC)"パターン
 これについての知識がなければTreeViewが使えないわけではありません。他のウィジェットと同じで関連クラス同士の組み合わせ方が分かれば大丈夫です。

注3:
 余談ですが、原文ではストアオブジェクトの「項目」を表す言葉にもcolumnという単語を使っているため、英語に慣れていない人にとっては結構分かりにくい文章になっています。


Copyright (c) 2005-2014 The GNOME Project