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

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

PyGTKでツリービューにリストのデータ(ListStore)を表示(モデルの内容を取り出す)

ここではPyGTKのListStore形式のデータから内容を取り出す手順についてを主に扱う。

取り出し方

基本的にはgtk.TreeModelオブジェクトメンバ関数get_value()あるいはget()を用いて、取り出したい項目を指し示すgtk.TreeIterオブジェクトを渡すことになる。前者はコラム番号を渡してそのコラム項目だけを取り出す方法で、後者は複数のコラム番号を可変長引数で渡してタプルとしてそれぞれのコラム項目の内容を取り出す形となる。
データを取り出すのに必要となるgtk.TreeIterオブジェクトを得るには幾つかの方法があり、「PyGTKでツリービューにリストのデータ(ListStore)を表示(データを変更可能にする・コード例)」や「PyGTKでツリービューにリストのデータ(ListStore)を表示(項目をGUI上で追加/削除、データの並べ替えや入れ替えなど)」ですでにツリービュー上の選択範囲から得る方法をコード中に書いている。
本記事においては、他の方法として以下の2つの方法を試している。

ListStoreの全てのデータを順番に取り出す

gtk.TreeModelオブジェクトメンバ関数get_iter_first()で先頭の項目を指し示すgtk.TreeIterオブジェクトを得た後、while文のループ内で各項目を取り出し、ループ内の最後にiter_next()を記述する(最後まで到達するとNoneが返るためループを抜ける)。

項目番号により任意のデータを取り出す

gtk.TreeModelオブジェクトメンバ関数get_iter()に、ツリーパスと呼ばれる形式のデータを渡す。
ツリーパスはツリーモデルにおける任意の項目を指定するための表記で、タプルもしくは文字列の形をとる。
もしこのときにデータの範囲外の値が指定された場合は例外ValueErrorが発生する。
http://www.pygtk.org/pygtk2tutorial/sec-TreeModelInterface.html#sec-TreePaths
も参照。

その他メモ(ラジオボタン)

ラジオボタンgtk.RadioButtonオブジェクトとなり、グループの指定については以下のようになる。

値はgtk.ToggleButtonオブジェクトメンバ関数get_active()で取得する。

コード例

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_NUM,
    COLUMN_SEX,
    COLUMN_FAMILY,
    COLUMN_GIVEN,
  ) = range(4)
  def __init__(self, *args, **kwargs):
    gtk.TreeView.__init__(self, *args, **kwargs)
    # コラムの設定
    self.col_num = gtk.TreeViewColumn('No.',
                                      gtk.CellRendererText(),
                                      text=self.COLUMN_NUM)
    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_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 = \
  [
    (1, SexIcon.male, 'Tanaka', 'Ichiro'),
    (2, SexIcon.female, 'Yamana', 'Hanako'),
    (3, SexIcon.male, 'Urashima', 'Saburo'),
    (4, SexIcon.male, 'Kurusu', 'Santa'),
    (5, SexIcon.male, 'Handa', 'Fuuta'),
    (6, SexIcon.female, 'Umeno', 'Tsubomi'),
    (7, SexIcon.male, 'Yoshi', 'Yaruzo'),
    (8, SexIcon.female, 'Kawai', 'Nuko'),
    (9, SexIcon.male, 'Hoshi', 'Kintaro'),
    (10, SexIcon.female, 'Shirayuki', 'Himeko'),
    (11, SexIcon.female, 'Ashigaka', 'Yui'),
    (12, SexIcon.female, 'Ageyanagi', 'Masako'),
    (13, SexIcon.male, 'Torino', 'Kenta'),
    (14, SexIcon.male, 'Kubota', 'Mochio'),
    (15, SexIcon.female, 'Kuroi', 'Sora'),
    (16, SexIcon.male, 'Hirai', 'Shin'),
    (17, SexIcon.female, 'Akai', 'Midori'),
    (18, SexIcon.female, 'Nakano', 'Anko'),
    (19, SexIcon.male, 'Imai', 'Takeo'),
    (20, SexIcon.male, 'Kouno', 'Torio'),
    (21, SexIcon.male, 'Yoshino', 'Yasu'),
    (22, SexIcon.male, 'Komatsu', 'Taro'),
    (23, SexIcon.male, 'Kondo', 'Musashi'),
    (24, SexIcon.male, 'Ono', 'Ken'),
    (25, SexIcon.male, 'Mochida', 'Usuichi'),
    (26, SexIcon.female, 'Mochida', 'Kineko'),
    (27, SexIcon.female, 'Honma', 'Kayo'),
    (28, SexIcon.male, 'Matsuno', 'Sarunosuke'),
    (29, SexIcon.female, 'Nishi', 'Minami'),
    (30, SexIcon.female, 'Usui', 'Hikaru'),
    (31, SexIcon.male, 'Sato', 'Toshio'),
    (32, SexIcon.male, 'Doi', 'Tsubasa'),
    (33, SexIcon.female, 'Ishimaru', 'Denko'),
    (34, SexIcon.female, 'Usami', 'Mimi'),
    (35, SexIcon.male, 'Hattori', 'Shinobu'),
    (36, SexIcon.female, 'Kago', 'Yuri'),
    (37, SexIcon.male, 'Takeda', 'Ingen'),
    (38, SexIcon.male, 'Kai', 'Dankichi'),
    (39, SexIcon.male, 'Okusa', 'Ben'),
    (40, SexIcon.male, 'Hara', 'Tatsuo'),
    (41, SexIcon.female, 'Mizuno', 'Shizuku'),
    (42, SexIcon.female, 'Baba', 'Nana'),
  ]
  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)
    # ツリービュー
    self.treeview = TreeViewWithColumn(model=gtk.ListStore(int, 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)
    # ラジオボタン項目
    # groupを指定した場合、対象のラジオボタンのグループに入る
    # 値はgtk.ToggleButtonのget_active()で取得
    self.radio_all = gtk.RadioButton(label='Get all items')
    self.radio_num = gtk.RadioButton(group=self.radio_all, label='Get a single item:')
    # データ取り出しを実行するボタン
    self.button = gtk.Button('Get data')
    # 取り出す項目を指定する番号を入力するスピンボタン
    self.spinbtn_path = gtk.SpinButton(gtk.Adjustment(0, 0, 999, 1, 0, 0))
    # レイアウト用コンテナ
    self.hbox_num = gtk.HBox()
    self.hbox_num.pack_start(self.radio_num, expand=False, fill=False)
    self.hbox_num.pack_end(self.spinbtn_path, expand=False, fill=False)
    self.vbox = gtk.VBox()
    self.vbox.pack_start(self.menubar, expand=False, fill=False)
    self.vbox.pack_start(self.sw)
    self.vbox.pack_start(self.radio_all, expand=False, fill=False)
    self.vbox.pack_start(self.hbox_num, expand=False, fill=False)
    self.vbox.pack_start(self.button, expand=False, fill=False)
    # シグナル
    self.connect('delete_event', gtk.main_quit)
    self.item_quit.connect('activate', gtk.main_quit)
    self.button.connect('clicked', self.on_button_clicked)
    # データ追加
    for rec in self.data:
      self.treeview.get_model().append(rec)
    # ウィンドウ
    self.add(self.vbox)
    self.set_size_request(350, 300)
  def on_button_clicked(self, widget):
    """
    ボタンがクリックされたときの処理
    """
    model = self.treeview.get_model()
    if self.radio_all.get_active():
      # 全てのデータを取り出して表示することにする
      iter = model.get_iter_first()
      while iter:
        # gtk.TreeModelのget()では複数のコラムのデータを一括でタプルとして取り出せる
        (num, sexicon, familyname, givenname) = model.get(iter, self.treeview.COLUMN_NUM, self.treeview.COLUMN_SEX, self.treeview.COLUMN_FAMILY, self.treeview.COLUMN_GIVEN)
        if sexicon == SexIcon.male:
          sex = 'Male'
        else:
          sex = 'Female'
        print 'Num: %d\nSex: %s\nFamily name: %s\nGiven name: %s' % \
              (num, sex, familyname, givenname)
        iter = model.iter_next(iter)  # 次が無ければNoneでループを抜ける
    else:
      # 指定された番号のデータを取り出して表示することにする
      # パスは何番目のデータかを整数で表したものを並べたタプルとなる
      # (ListStoreの場合は整数だけでもOK)
      # 他に文字列表記のパスもある
      # http://www.pygtk.org/pygtk2tutorial/sec-TreeModelInterface.html#sec-TreePaths も参照
      path = (int(self.spinbtn_path.get_value()),)
      try:
        iter = model.get_iter(path)
      except ValueError:  # 範囲外の値が指定されTreeIterが得られない場合
        print '(invalid tree path)'
        return
      (num, sexicon, familyname, givenname) = model.get(iter, self.treeview.COLUMN_NUM, self.treeview.COLUMN_SEX, self.treeview.COLUMN_FAMILY, self.treeview.COLUMN_GIVEN)
      if sexicon == SexIcon.male:
        sex = 'Male'
      else:
        sex = 'Female'
      print 'Num: %d\nSex: %s\nFamily name: %s\nGiven name: %s' % \
            (num, sex, familyname, givenname)

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


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

「Get all items」のラジオボタンが選択されている状態でボタンを押すと

Num: 1
Sex: Male
Family name: Tanaka
Given name: Ichiro
Num: 2
Sex: Female
Family name: Yamana
Given name: Hanako
(中略)
Num: 42
Sex: Female
Family name: Baba
Given name: Nana

のようにリスト上の全ての項目が各コラム項目ごとに端末に表示され、「Get a single item」のラジオボタンが選択されている状態でボタンを押すと、内部的な項目番号(0から始まる)がその番号の項目が表示される。
例えば、「2」を入力してボタンを押すと

Num: 3
Sex: Male
Family name: Urashima
Given name: Saburo

のように表示される。
データ範囲外の値が指定されたときには

(invalid tree path)

と表示される。

関連記事:

参考URL: