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

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

PyGTKでツリービューの各テキストセルに対してスタイルを適用する(後半)

PyGTKでツリービューの各テキストセルに対してスタイルを適用する(前半)」の続き。
前半と同様、本記事の中に貼り付けた例の中で使用した名前は架空のものであり、実在の個人名や団体名などと一致するものがあったとしても関係はない。

背景色を付けたときにすき間ができる件についてとその対処

背景色を指定して付けたときに

このようにセルの端にすき間ができる場合があるのを調整するためには

  1. そのすき間に関する情報がどこに設定されている何という項目か
  2. その項目を変更するにはどうすれば良いか

の両方が分かっている必要がある。幸い、偶然にも別のところで行っていた実験の中で分かったことがあるので、この問題には簡単に対処が行え、以下のようになった。

  1. 項目はgtk.TreeViewオブジェクトの「horizontal-separator」と「vertical-separator」の2つの「スタイルプロパティ」と呼ばれる類の属性*1である
  2. スタイルプロパティを変更するにはGTK+の外観に関する設定ファイル(gtkrcと呼ばれるもの)と同一の書式を持ったスタイル記述を用意してgtk.rc_parse()という関数に読み込ませ、必要に応じてGUI部品(ウィジェット)のnameというGObjectプロパティを変更する*2

実際としては

gtk.rc_parse_string('''
style "custom-treeview-style"
{
  GtkTreeView::vertical-separator = 0
  GtkTreeView::horizontal-separator = 0
}
widget "*.custom-treeview" style "custom-treeview-style"
''')

このようなコードを実行しておき、

[スタイルを変更したいgtk.TreeViewオブジェクト].props.name = 'custom-treeview'

のようにする。*3gtk.rc_parse()の中の記述については、「style」の部分でスタイル定義を行い、「widget」の行では条件にマッチしたウィジェットに対してそのスタイルを適用するという処理となっている。
スタイル定義部分の書式については
http://library.gnome.org/devel/pygtk/stable/class-gtkrcstyle.html
の「Description」にある「Within a style declaration, the possible elements are:」以下の一覧に書かれており、ここで用いたのは「class::property = value」形式のものとなる。
また、

gtk.rc_parse_string('''
style "custom-treeview-style"
{
  GtkTreeView::vertical-separator = 0
  GtkTreeView::horizontal-separator = 0
}
widget "*.GtkTreeView" style "custom-treeview-style"
''')

のように記述した場合は(そのプログラム内の)全てのツリービューに対してこのスタイルが適用され、個別に(適用したいスタイル記述を)指定することはできないが、GUI部品側でnameプロパティを指定する必要はない。

前半の例の修正版

[任意]ファイル名: treeviewliststoretestwithstyle2.py

#! /usr/bin/python
# -*- coding: 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)


class CellRendererTextWithFont(gtk.CellRendererText):
  """
  フォントを指定したテキストセルレンダラ
  """
  def __init__(self):
    gtk.CellRendererText.__init__(self)
    self.props.font = 'Serif Bold Italic 10'

class CellRendererFamily(CellRendererTextWithFont):
  """
  Family name用セルレンダラ
  """
  def __init__(self):
    CellRendererTextWithFont.__init__(self)
    self.props.background = '#ffcccc'
    # background_gdkプロパティはテキスト以外のセルレンダラの背景色にも使える
#    self.props.background_gdk = gtk.gdk.Color('#ffcccc')

class CellRendererGiven(CellRendererTextWithFont):
  """
  Given name用セルレンダラ
  """
  def __init__(self):
    CellRendererTextWithFont.__init__(self)
    self.props.foreground = '#0000ff'

class TreeViewWithColumn(gtk.TreeView):
  """
  コラムを含んだツリービュー
  """
  # コラム内の項目番号(連番)をrange()で作成する
  (
    COLUMN_NUM,
    COLUMN_FAMILY,
    COLUMN_GIVEN,
  ) = range(3)  # 実際の値は上から順に0,1,2が入る
  def __init__(self, *args, **kwargs):
    gtk.TreeView.__init__(self, *args, **kwargs)  # 必須
    # コラムの設定
    # ListStore/TreeStore上の項目とTreeView上に表示する項目・その位置とを
    # 関連付ける(コラム自体の設定としてのデータを持つこともできる)
    # 今回はセルレンダラはgtk.CellRendererTextを生成と同時に渡している
    # また、設定も表示するテキストを「text=」で指定しているだけ
    # セルのスタイルはgtk.CellRendererTextクラスのGObjectプロパティを用いて
    # gtk.TreeViewColumnオブジェクトのコンストラクタ引数として設定する
    # http://library.gnome.org/devel/pygtk/stable/class-gtkcellrenderertext.html
    # 「gtk.CellRendererText Properties」を参照
    self.__col_num = gtk.TreeViewColumn('No.',
                                        CellRendererTextWithFont(),
                                        text=TreeViewWithColumn.COLUMN_NUM)
    self.__col_family = gtk.TreeViewColumn('Family name',
                                           CellRendererFamily(),
                                           text=TreeViewWithColumn.COLUMN_FAMILY)
    self.__col_given = gtk.TreeViewColumn('Given name',
                                          CellRendererGiven(),
                                          text=TreeViewWithColumn.COLUMN_GIVEN)

    # コラムを追加
    self.append_column(self.__col_num)
    self.append_column(self.__col_family)
    self.append_column(self.__col_given)

class MainWindow(gtk.Window):
  """
  メインウィンドウ
  """
  # 直接ウィンドウとは関係ないが、データは便宜上ここに用意しておくことにする
  __data = \
  [
    (1, 'Tanaka', 'Ichiro'),
    (2, 'Yamana', 'Hanako'),
    (3, 'Urashima', 'Saburo'),
    (4, 'Kurusu', 'Santa'),
    (5, 'Handa', 'Fuuta'),
    (6, 'Umeno', 'Tsubomi'),
    (7, 'Yoshi', 'Yaruzo'),
    (8, 'Kawai', 'Nuko'),
    (9, 'Hoshi', 'Kintaro'),
    (10, 'Shirayuki', 'Himeko'),
    (11, 'Ashigaka', 'Yui'),
    (12, 'Ageyanagi', 'Masako'),
    (13, 'Torino', 'Kenta'),
    (14, 'Kubota', 'Mochio'),
    (15, 'Kuroi', 'Sora'),
    (16, 'Hirai', 'Shin'),
    (17, 'Akai', 'Midori'),
    (18, 'Nakano', 'Anko'),
    (19, 'Imai', 'Takeo'),
    (20, 'Kouno', 'Torio'),
    (21, 'Yoshino', 'Yasu'),
    (22, 'Komatsu', 'Taro'),
    (23, 'Kondo', 'Musashi'),
    (24, 'Ono', 'Ken'),
    (25, 'Mochida', 'Usuichi'),
    (26, 'Mochida', 'Kineko'),
    (27, 'Honma', 'Kayo'),
    (28, 'Matsuno', 'Sarunosuke'),
    (29, 'Nishi', 'Minami'),
    (30, 'Usui', 'Hikaru'),
    (31, 'Sato', 'Toshio'),
    (32, 'Doi', 'Tsubasa'),
    (33, 'Ishimaru', 'Denko'),
    (34, 'Usami', 'Mimi'),
    (35, 'Hattori', 'Shinobu'),
    (36, 'Kago', 'Yuri'),
    (37, 'Takeda', 'Ingen'),
    (38, 'Kai', 'Dankichi'),
    (39, 'Okusa', 'Ben'),
    (40, 'Hara', 'Tatsuo'),
    (41, 'Mizuno', 'Shizuku'),
    (42, '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.props.submenu = self.__menu_file
    self.__menubar = gtk.MenuBar()
    self.__menubar.append(self.__item_file)
    # ツリービュー
    # model(ListStore)はツリービューに連動させるリストデータ
    # 初期化時に型を項目ごとに指定する
    # 使用できる型は下のURLを参照・GObjectの各形式も使用可能
    # http://www.pygtk.org/pygtk2tutorial/sec-TreeModelInterface.html#sec-CreatingTreeStoreAndListStore
    # 今回は「[整数] + [文字列1] + [文字列2]」の形
    # データとの結び付けは初期化時もしくはプロパティmodelかset_model()で行う
    self.__treeview = TreeViewWithColumn(model=gtk.ListStore(int, str, str))
    self.__treeview.props.rules_hint = True  # 背景色のシマシマを付ける
    self.__treeview.props.name = 'custom-treeview'  # スタイル指定のための名前
    # ツリービュー向けスクロールウィンドウ
    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 rec in self.__data:
      # 結び付けられたモデル(gtk.ListStore)のメンバ関数append()に*タプル*を渡す
      self.__treeview.props.model.append(rec)
    # ウィンドウ
    self.add(self.__vbox)
    self.set_size_request(300, 300)

class PyGTKTreeViewListStoreTestWithStyle2:
  """
  リストを用いたツリービューのテスト・スタイル指定付き 2
  """
  def main(self):
    """
    アプリケーションのメイン処理
    """
    # スタイルの微調整を行うためにGTK+のテーマ(gtkrc)と同じ書式で
    # 独自にスタイルを定義し、マッチするウィジェットの外観に適用させる
    # 最終行「widget」の「*.」の後ろの部分の文字列を
    # ウィジェットのnameプロパティに指定するとスタイルが適用される
    gtk.rc_parse_string('''
style "custom-treeview-style"            # スタイル定義
{
  # 書式は
  # http://library.gnome.org/devel/pygtk/stable/class-gtkrcstyle.html の
  # 「Within a style declaration, the possible elements are:」以下の一覧
  # 今回用いているのは
  # 「[ウィジェットクラス名]::[スタイルプロパティ名] = [値]」による
  # スタイルプロパティ値の変更
  # スタイルプロパティはリファレンスの「Style Properties」と書かれている項目で
  # ウィジェットクラスごとに用意されている(ない場合もある)
  GtkTreeView::vertical-separator = 0    # 縦方向のセル間の幅 既定値2
  GtkTreeView::horizontal-separator = 0  # 横方向のセル間の幅 既定値2
}
# ウィジェットクラスやウィジェット名に対してスタイルを関連付ける記述
# 全てのツリービューに対して適用する場合はnameプロパティを用いずに
# 「widget "*.GtkTreeView" style "custom-treeview-style"」を記述するだけでOK
widget "*.custom-treeview" style "custom-treeview-style"
''')
    win = MainWindow()
    win.show_all()
    gtk.main()


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


セルごとに異なるスタイルを指定できるようにした例

[任意]ファイル名: treeviewliststoretestwithstyle3.py

#! /usr/bin/python
# -*- coding: 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)


class TreeViewWithColumn(gtk.TreeView):
  """
  コラムを含んだツリービュー
  """
  # コラム内の項目番号(連番)をrange()で作成する
  (
    COLUMN_NUM,
    COLUMN_FAMILY,
    COLUMN_GIVEN,
    COLUMN_FONT,
    COLUMN_FAMILY_BACKGROUND,
    COLUMN_GIVEN_FOREGROUND,
  ) = range(6)  # 実際の値は上から順に0,1,2,3,4,5が入る
  def __init__(self, *args, **kwargs):
    gtk.TreeView.__init__(self, *args, **kwargs)  # 必須
    # コラムの設定
    # ListStore/TreeStore上の項目とTreeView上に表示する項目・その位置とを
    # 関連付ける(コラム自体の設定としてのデータを持つこともできる)
    # 今回はセルレンダラはgtk.CellRendererTextを生成と同時に渡している
    # また、設定も表示するテキストを「text=」で指定しているだけ
    # セルのスタイルはgtk.CellRendererTextクラスのGObjectプロパティを用いて
    # gtk.TreeViewColumnオブジェクトのコンストラクタ引数として設定する
    # http://library.gnome.org/devel/pygtk/stable/class-gtkcellrenderertext.html
    # 「gtk.CellRendererText Properties」を参照
    self.__col_num = gtk.TreeViewColumn('No.',
                                        gtk.CellRendererText(),
                                        text=TreeViewWithColumn.COLUMN_NUM,
                                        font=TreeViewWithColumn.COLUMN_FONT)
    self.__col_family = gtk.TreeViewColumn('Family name',
                                           gtk.CellRendererText(),
                                           text=TreeViewWithColumn.COLUMN_FAMILY,
                                           font=TreeViewWithColumn.COLUMN_FONT,
                                           background=TreeViewWithColumn.COLUMN_FAMILY_BACKGROUND)
    self.__col_given = gtk.TreeViewColumn('Given name',
                                          gtk.CellRendererText(),
                                          text=TreeViewWithColumn.COLUMN_GIVEN,
                                          font=TreeViewWithColumn.COLUMN_FONT,
                                          foreground=TreeViewWithColumn.COLUMN_GIVEN_FOREGROUND)

    # コラムを追加
    self.append_column(self.__col_num)
    self.append_column(self.__col_family)
    self.append_column(self.__col_given)

class MainWindow(gtk.Window):
  """
  メインウィンドウ
  """
  # 直接ウィンドウとは関係ないが、データは便宜上ここに用意しておくことにする
  __data = \
  [
    (1, 'Tanaka', 'Ichiro'),
    (2, 'Yamana', 'Hanako'),
    (3, 'Urashima', 'Saburo'),
    (4, 'Kurusu', 'Santa'),
    (5, 'Handa', 'Fuuta'),
    (6, 'Umeno', 'Tsubomi'),
    (7, 'Yoshi', 'Yaruzo'),
    (8, 'Kawai', 'Nuko'),
    (9, 'Hoshi', 'Kintaro'),
    (10, 'Shirayuki', 'Himeko'),
    (11, 'Ashigaka', 'Yui'),
    (12, 'Ageyanagi', 'Masako'),
    (13, 'Torino', 'Kenta'),
    (14, 'Kubota', 'Mochio'),
    (15, 'Kuroi', 'Sora'),
    (16, 'Hirai', 'Shin'),
    (17, 'Akai', 'Midori'),
    (18, 'Nakano', 'Anko'),
    (19, 'Imai', 'Takeo'),
    (20, 'Kouno', 'Torio'),
    (21, 'Yoshino', 'Yasu'),
    (22, 'Komatsu', 'Taro'),
    (23, 'Kondo', 'Musashi'),
    (24, 'Ono', 'Ken'),
    (25, 'Mochida', 'Usuichi'),
    (26, 'Mochida', 'Kineko'),
    (27, 'Honma', 'Kayo'),
    (28, 'Matsuno', 'Sarunosuke'),
    (29, 'Nishi', 'Minami'),
    (30, 'Usui', 'Hikaru'),
    (31, 'Sato', 'Toshio'),
    (32, 'Doi', 'Tsubasa'),
    (33, 'Ishimaru', 'Denko'),
    (34, 'Usami', 'Mimi'),
    (35, 'Hattori', 'Shinobu'),
    (36, 'Kago', 'Yuri'),
    (37, 'Takeda', 'Ingen'),
    (38, 'Kai', 'Dankichi'),
    (39, 'Okusa', 'Ben'),
    (40, 'Hara', 'Tatsuo'),
    (41, 'Mizuno', 'Shizuku'),
    (42, '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.props.submenu = self.__menu_file
    self.__menubar = gtk.MenuBar()
    self.__menubar.append(self.__item_file)
    # ツリービュー
    # model(ListStore)はツリービューに連動させるリストデータ
    # 初期化時に型を項目ごとに指定する
    # 使用できる型は下のURLを参照・GObjectの各形式も使用可能
    # http://www.pygtk.org/pygtk2tutorial/sec-TreeModelInterface.html#sec-CreatingTreeStoreAndListStore
    # 今回は「[整数] + [文字列1] + [文字列2]」の形
    # データとの結び付けは初期化時もしくはプロパティmodelかset_model()で行う
    self.__treeview = TreeViewWithColumn(model=gtk.ListStore(int, str, str, str, str, str))
    self.__treeview.props.rules_hint = True  # 背景色のシマシマを付ける
    self.__treeview.props.name = 'custom-treeview'  # スタイル指定のための名前
    # ツリービュー向けスクロールウィンドウ
    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 (i, rec) in enumerate(self.__data):
      # 結び付けられたモデル(gtk.ListStore)のメンバ関数append()に*タプル*を渡す
      # 今回は3行ごとにスタイルを繰り返すように指定
      if i % 3 == 0:
        self.__treeview.props.model.append((rec[0], rec[1], rec[2], 'Serif 10', '#ffcccc', '#00cc00'))
      elif i % 3 == 1:
        self.__treeview.props.model.append((rec[0], rec[1], rec[2], 'Serif Bold 10', '#ccccff', '#ff0000'))
      else:
        self.__treeview.props.model.append((rec[0], rec[1], rec[2], 'Serif Bold Italic 10', '#ccffcc', '#0000cc'))
    # ウィンドウ
    self.add(self.__vbox)
    self.set_size_request(300, 300)

class PyGTKTreeViewListStoreTestWithStyle3:
  """
  リストを用いたツリービューのテスト・スタイル指定付き 3
  """
  def main(self):
    """
    アプリケーションのメイン処理
    """
    # スタイルの微調整を行うためにGTK+のテーマ(gtkrc)と同じ書式で
    # 独自にスタイルを定義し、マッチするウィジェットの外観に適用させる
    # 最終行「widget」の「*.」の後ろの部分の文字列を
    # ウィジェットのnameプロパティに指定するとスタイルが適用される
    gtk.rc_parse_string('''
style "custom-treeview-style"            # スタイル定義
{
  # 書式は
  # http://library.gnome.org/devel/pygtk/stable/class-gtkrcstyle.html の
  # 「Within a style declaration, the possible elements are:」以下の一覧
  # 今回用いているのは
  # 「[ウィジェットクラス名]::[スタイルプロパティ名] = [値]」による
  # スタイルプロパティ値の変更
  # スタイルプロパティはリファレンスの「Style Properties」と書かれている項目で
  # ウィジェットクラスごとに用意されている(ない場合もある)
  GtkTreeView::vertical-separator = 0    # 縦方向のセル間の幅 既定値2
  GtkTreeView::horizontal-separator = 0  # 横方向のセル間の幅 既定値2
}
# ウィジェットクラスやウィジェット名に対してスタイルを関連付ける記述
# 全てのツリービューに対して適用する場合はnameプロパティを用いずに
# 「widget "*.GtkTreeView" style "custom-treeview-style"」を記述するだけでOK
widget "*.custom-treeview" style "custom-treeview-style"
''')
    win = MainWindow()
    win.show_all()
    gtk.main()


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

関連記事:

参考URL:

使用したバージョン:

*1:上下もしくは左右のスペースの合計が指定された値の幅となるように(半分ずつ)振り分けるため、0以外の値を設定する場合は偶数にするのが好ましい・既定値も2となっている

*2:もしくは(gtk.Widgetクラスの)メンバ関数set_name()を呼ぶことで変更する

*3:もちろん、GObjectプロパティを設定するのにset_propertyメンバ関数(関連記事)を用いてもよい