試験運用中なLinux備忘録・旧記事

はてなダイアリーで公開していた2007年5月-2015年3月の記事を保存しています。

PyGTKでツリービューに階層を持ったデータ(TreeStore)を表示

これまでにListStoreというリスト形式の(階層を持たない)データをツリービューに関連付けて表示してきたが、ここでは階層を持ったTreeStoreという形式のデータを用いてツリービューに表示してみる。

メモ

  • TreeStore(gtk.TreeStoreオブジェクト)へのappend()には2つの引数を取り、2つ目の引数はListStoreのときと同様にタプルの形で追加するデータを指定するのだが、1つ目の引数には親(その下にブラ下げたい)項目を指し示すgtk.TreeIterオブジェクト*1を指定することになる・最上位の階層に追加する(親項目がない)のであれば「None」を指定
  • そのgtk.TreeStoreオブジェクトメンバ関数append()の戻り値として新しく作成された項目を指し示すgtk.TreeIterオブジェクトが得られるので、そのすぐ下に子をブラ下げたい場合には次のappend()の1番目の引数として使用できる
  • グループ名のような項目にそれぞれのデータをブラ下げたい場合、各データはコラムに表示させたいデータを持っているが、グループ名の項目自身にはデータがない(文字列だけ)なので、それ以外のデータについては、文字列型のTreeStore項目の場合は空文字列をデータに持たせればよく、Pixbuf(画像)の場合も「None」のデータを持たせればよいのだが、数値型の場合はそのままでは非表示にできず、TreeStoreに数値の表示/非表示に関する真偽値を用意しつつコラムの設定で、セルレンダラに「visible=[用意した真偽値の項目番号]」の指定を追加する・逆に各データにはグループ名の項目はない(表示されないようにしたい)ため、そこには空文字列を入れておくことになる

コード例

今回はTreeStoreのデータ追加のみに焦点を当てているため、「PyGTKでツリービューにリストのデータ(ListStore)を表示(データを変更可能にする・コード例)」のようなデータ編集機能は付けていない。
別途、画像アイコンの表示のために「PyGTKでツリービューにリストのデータ(ListStore)を表示(データを変更可能にする・コード例)」のsexmark.pyが必要。
なお、例によって、この中で使用した名前は架空のものであり、実在の個人名や団体名などと一致するものがあったとしても関係はない。

#! /usr/bin/python
# -*- encoding: utf-8 -*-

import sys
try:
  import pygtk
  pygtk.require('2.0')
except:
  pass
try:
  import gtk
except:
  print >> sys.stderr, 'Error: PyGTK is not installed'
  sys.exit(1)
try:
  import sexmark
except:
  print >> sys.stderr, 'Error: sexmark.py required'
  sys.exit(1)


class TreeViewWithColumn(gtk.TreeView):
  """
  コラムを含んだツリービュー
  """
  (
    COLUMN_GROUP,
    COLUMN_NUM,
    COLUMN_SHOWNUM,
    COLUMN_SEX,
    COLUMN_FAMILY,
    COLUMN_GIVEN,
  ) = range(6)
  def __init__(self, *args, **kwargs):
    gtk.TreeView.__init__(self, *args, **kwargs)
    # コラムの設定
    self.col_group = gtk.TreeViewColumn('Group',
                                        gtk.CellRendererText(),
                                        text=self.COLUMN_GROUP)
    self.col_num = gtk.TreeViewColumn('No.',
                                      gtk.CellRendererText(),
                                      text=self.COLUMN_NUM,
                                      visible=self.COLUMN_SHOWNUM)
    self.col_sex = gtk.TreeViewColumn('Sex',
                                      gtk.CellRendererPixbuf(),
                                      pixbuf=self.COLUMN_SEX)
    self.col_family = gtk.TreeViewColumn('Family name',
                                         gtk.CellRendererText(),
                                         text=self.COLUMN_FAMILY)
    self.col_given = gtk.TreeViewColumn('Given name',
                                        gtk.CellRendererText(),
                                        text=self.COLUMN_GIVEN)
    # コラムを追加
    self.append_column(self.col_group)
    self.append_column(self.col_num)
    self.append_column(self.col_sex)
    self.append_column(self.col_family)
    self.append_column(self.col_given)

class SexIcon:
  """
  性別のアイコンデータのPixbufを保持
  各データは「[本クラス名].[メンバ変数]」で取り出す
  """
  male = gtk.gdk.pixbuf_new_from_xpm_data(sexmark.male_icon_xpm)
  female = gtk.gdk.pixbuf_new_from_xpm_data(sexmark.female_icon_xpm)

class MainWindow(gtk.Window):
  """
  メインウィンドウ
  """
  # 直接ウィンドウとは関係ないが、データは便宜上ここに用意しておくことにする
  data_group_a = \
  [
    ('', 1, True, SexIcon.male, 'Tanaka', 'Ichiro'),
    ('', 22, True, SexIcon.male, 'Komatsu', 'Taro'),
    ('', 2, True, SexIcon.female, 'Yamana', 'Hanako'),
    ('', 3, True, SexIcon.male, 'Urashima', 'Saburo'),
    ('', 11, True, SexIcon.female, 'Ashigaka', 'Yui'),
    ('', 31, True, SexIcon.male, 'Sato', 'Toshio'),
    ('', 40, True, SexIcon.male, 'Hara', 'Tatsuo'),
  ]
  data_group_b = \
  [
    ('', 4, True, SexIcon.male, 'Kurusu', 'Santa'),
    ('', 9, True, SexIcon.male, 'Hoshi', 'Kintaro'),
    ('', 35, True, SexIcon.male, 'Hattori', 'Shinobu'),
    ('', 10, True, SexIcon.female, 'Shirayuki', 'Himeko'),
    ('', 5, True, SexIcon.male, 'Handa', 'Fuuta'),
    ('', 28, True, SexIcon.male, 'Matsuno', 'Sarunosuke'),
    ('', 20, True, SexIcon.male, 'Kouno', 'Torio'),
  ]
  data_group_c = \
  [
    ('', 42, True, SexIcon.female, 'Baba', 'Nana'),
    ('', 14, True, SexIcon.male, 'Kubota', 'Mochio'),
    ('', 23, True, SexIcon.male, 'Kondo', 'Musashi'),
    ('', 19, True, SexIcon.male, 'Imai', 'Takeo'),
    ('', 16, True, SexIcon.male, 'Hirai', 'Shin'),
    ('', 6, True, SexIcon.female, 'Umeno', 'Tsubomi'),
    ('', 41, True, SexIcon.female, 'Mizuno', 'Shizuku'),
  ]
  data_group_d = \
  [
    ('', 13, True, SexIcon.male, 'Torino', 'Kenta'),
    ('', 18, True, SexIcon.female, 'Nakano', 'Anko'),
    ('', 15, True, SexIcon.female, 'Kuroi', 'Sora'),
    ('', 17, True, SexIcon.female, 'Akai', 'Midori'),
    ('', 33, True, SexIcon.female, 'Ishimaru', 'Denko'),
    ('', 21, True, SexIcon.male, 'Yoshino', 'Yasu'),
    ('', 8, True, SexIcon.female, 'Kawai', 'Nuko'),
  ]
  data_group_e = \
  [
    ('', 24, True, SexIcon.male, 'Ono', 'Ken'),
    ('', 39, True, SexIcon.male, 'Okusa', 'Ben'),
    ('', 36, True, SexIcon.female, 'Kago', 'Yuri'),
    ('', 25, True, SexIcon.male, 'Mochida', 'Usuichi'),
    ('', 26, True, SexIcon.female, 'Mochida', 'Kineko'),
    ('', 29, True, SexIcon.female, 'Nishi', 'Minami'),
    ('', 38, True, SexIcon.male, 'Kai', 'Dankichi'),
  ]
  data_group_f = \
  [
    ('', 12, True, SexIcon.female, 'Ageyanagi', 'Masako'),
    ('', 27, True, SexIcon.female, 'Honma', 'Kayo'),
    ('', 7, True, SexIcon.male, 'Yoshi', 'Yaruzo'),
    ('', 30, True, SexIcon.female, 'Usui', 'Hikaru'),
    ('', 32, True, SexIcon.male, 'Doi', 'Tsubasa'),
    ('', 34, True, SexIcon.female, 'Usami', 'Mimi'),
    ('', 37, True, SexIcon.male, 'Takeda', 'Ingen'),
  ]
  data_toplevel = \
  [
    # グループ名と各グループのデータを保持するが、グループ名以外は表示しない
    # 番号の「0」については非表示にするための真偽値を設けて
    # それをFalseにしている(上の各データではTrueにしている)
    (('Group A', 0, False, None, '', ''), data_group_a),
    (('Group B', 0, False, None, '', ''), data_group_b),
    (('Group C', 0, False, None, '', ''), data_group_c),
    (('Group D', 0, False, None, '', ''), data_group_d),
    (('Group E', 0, False, None, '', ''), data_group_e),
    (('Group F', 0, False, None, '', ''), data_group_f),
  ]
  def __init__(self, *args, **kwargs):
    gtk.Window.__init__(self, *args, **kwargs)
    # ショートカットキー(アクセラレータ)
    self.accelgroup = gtk.AccelGroup()
    self.add_accel_group(self.accelgroup)
    # メニュー項目
    self.item_quit = gtk.ImageMenuItem(gtk.STOCK_QUIT, self.accelgroup)
    self.menu_file = gtk.Menu()
    self.menu_file.add(self.item_quit)
    self.item_file = gtk.MenuItem('_File')
    self.item_file.set_submenu(self.menu_file)
    self.menubar = gtk.MenuBar()
    self.menubar.append(self.item_file)
    # ツリービュー
    # 3番目の真偽値は番号部分を表示するかどうかを指定
    self.treeview = TreeViewWithColumn(model=gtk.TreeStore(str, int, bool, gtk.gdk.Pixbuf, str, str))
    self.treeview.set_rules_hint(True)
    # ツリービュー向けスクロールウィンドウ
    self.sw = gtk.ScrolledWindow()
    self.sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
    self.sw.add(self.treeview)
    # レイアウト用コンテナ
    self.vbox = gtk.VBox()
    self.vbox.pack_start(self.menubar, expand=False, fill=False)
    self.vbox.pack_start(self.sw)
    # シグナル
    self.connect('delete_event', gtk.main_quit)
    self.item_quit.connect('activate', gtk.main_quit)
    # データ追加
    for group, data_child in self.data_toplevel:
      # append()の1番目にNoneを指定すると最上位に配置される
      parent_iter = self.treeview.get_model().append(None, group)
      # 子項目は親項目を指し示すgtk.TreeIterを指定してそこにブラ下げる
      for child_rec in data_child:
        # ここではしていないが、子項目を追加するappend()の戻り値として得られる
        # gtk.TreeIterを次のappend()の1番目に指定すると
        # 更に次の子を作ってブラ下げることができる
        self.treeview.get_model().append(parent_iter, child_rec)
    self.treeview.expand_all()  # ツリーの子を全て展開する
    # ウィンドウ
    self.add(self.vbox)
    self.set_size_request(400, 300)

class PyGTKTreeViewTreeStoreTest:
  """
  階層を持ったデータを用いたツリービューのテスト
  """
  def main(self):
    """
    アプリケーションのメイン処理
    """
    win = MainWindow()
    win.show_all()
    gtk.main()


if __name__ == '__main__':
  app = PyGTKTreeViewTreeStoreTest()
  app.main()

関連記事:

参考URL:

*1:以前にも書いているが、TreeStoreやListStoreのデータを読み書きする(データ内の項目を指し示す)カーソル(キャレット)のようなオブジェクト