Python GTK+3 チュートリアル

12. ツリーとリストウィジェット③ Sorting / Filtering

翻訳して勉強するGtkチュートリアル第12章 Tree and List Widgets のパート3。ソートとフィルタリングを学びます。


12.4. ソート

ソートはツリービューにとって重要な機能であり、Gtk.TreeSortable インターフェースを実装した標準のツリーモデル ( Gtk.TreeStore Gtk.ListStore ) でサポートされています。

12.4.1. 列をクリックして並べ替え

Gtk.TreeView のカラムは Gtk.TreeViewColumn.set_sort_column_id() を呼び出すことで簡単にソート可能になります。その後、そのカラムのヘッダをクリックしてソートできます。

まず、シンプルな Gtk.TreeView とモデルとして Gtk.ListStore が必要です。

model = Gtk.ListStore(str)
model.append(["Benjamin"])
model.append(["Charles"])
model.append(["alfred"])
model.append(["Alfred"])
model.append(["David"])
model.append(["charles"])
model.append(["david"])
model.append(["benjamin"])

treeView = Gtk.TreeView(model)

cellRenderer = Gtk.CellRendererText()
column = Gtk.TreeViewColumn("Title", cellRenderer, text=0)

次のステップはソートを有効にすることです。column_id (例では0)はモデルのカラムを指しており、TreeViewのカラムを指していないことに注意してください。

column.set_sort_column_id(0)

※ このモデルをウィンドウに表示

動作テスト - ソート
# tut12.py
# ツリーとリストウィジェット③ Sorting

import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk

model = [
    ["Benjamin"],
    ["Charles"],
    ["alfred"],
    ["Alfred"],
    ["David"],
    ["charles"],
    ["david"],
    ["benjamin"],
]


class MyWindow(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self, title="動作テスト - ソート")
        self.set_border_width(10)
        self.set_default_size(250, 200)
        self.set_border_width(10)

        # データを一列のListStoreモデルに格納
        self.listmodel = Gtk.ListStore(str)
        for i in range(len(model)):
            self.listmodel.append(model[i])

        # モデルに格納されたデータを見るためのツリービュー
        treeview = Gtk.TreeView(model=self.listmodel)

        # セルレンダラ
        cellRenderer = Gtk.CellRendererText()
        
        # カラム作成
        col = Gtk.TreeViewColumn("Title", cellRenderer, text=0)
        col.set_sort_column_id(0)
 
        # ツリービューにカラムを収納
        treeview.append_column(col)

        # ウィンドウにツリービューを収納
        self.add(treeview)

win = MyWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()

12.4.2. カスタムソート機能の設定

ソートの動作を変更するために、カスタム比較関数を設定することも可能です。例として、大文字と小文字を区別してソートする比較関数を作成します。上の例では、ソートされたリストは次のようになります。

Alfred
Benjamin
Charles
David
alfred
benjamin
charles
david

大文字小文字を区別してソートされたリストは次のようになります。

alfred
Alfred
benjamin
Benjamin
charles
Charles
david
David

まず最初に比較関数が必要です。この関数は2つの行を取得し、最初の行が2番目の行よりも前に来る場合は負の整数、等しい場合は0、2番目の行が最初の行よりも前に来る場合は正の整数を返します。

def compare(model, row1, row2, user_data):
    sort_column, _ = model.get_sort_column_id()
    value1 = model.get_value(row1, sort_column)
    value2 = model.get_value(row2, sort_column)
    if value1 < value2:
        return -1
    elif value1 == value2:
        return 0
    else:
        return 1

そして、ソート関数は Gtk.TreeSortable.set_sort_func() で設定する必要があります。

model.set_sort_func(0, compare, None)

12.5. フィルタリング

ソートとは異なり、フィルタリングは先ほど見た2つのモデルではなく、Gtk.TreeModelFilter クラスで処理されます。このクラスは Gtk.TreeStore Gtk.ListStore と同様に Gtk.TreeModel です。それは “本物の"モデル( Gtk.TreeStore Gtk.ListStore )の間のレイヤーとして機能し、いくつかの要素をビューに隠します。実際には、Gtk.TreeModel Gtk.TreeView に基礎となるモデルのサブセットを提供します。Gtk.TreeModelFilter のインスタンスを重ねることで、同じモデル上で複数のフィルタを使うことができます (SQL リクエストで “AND” 節を使うのと同様)。また、Gtk.TreeModelSort インスタンスと連結することもできます。

Gtk.TreeModelFilter の新しいインスタンスを作成してフィルタリング対象のモデルを与えることもできますが、最も簡単な方法は Gtk.TreeModel.filter_new() メソッドを使ってフィルタリングされたモデルから直接生成することです。

filter = model.filter_new()

ソート関数と同じように、Gtk.TreeModelFilter は “visibility” 関数を必要とします。これは、基礎となるモデルの行が与えられた場合、その行がフィルタリングされるべきかどうかを示す boolean 値を返します。これは Gtk.TreeModelFilter.set_visible_func() によって設定されます。

filter.set_visible_func(filter_func, data=None)

Gtk.ListStore - Gtk.TreeModelFilter - Gtk.TreeView スタック全体を使った例を見てみましょう。

スタック全体を使った例
# tut12a.py
# ツリーとリストウィジェット③ Filtering

import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk

# ソフトウェア名、初期リリース、使用されている主なプログラミング言語を含む、各ソフトウェアのタプルのリスト
software_list = [
    ("Firefox", 2002, "C++"),
    ("Eclipse", 2004, "Java"),
    ("Pitivi", 2004, "Python"),
    ("Netbeans", 1996, "Java"),
    ("Chrome", 2008, "C++"),
    ("Filezilla", 2001, "C++"),
    ("Bazaar", 2005, "Python"),
    ("Git", 2005, "C"),
    ("Linux Kernel", 1991, "C"),
    ("GCC", 1987, "C"),
    ("Frostwire", 2004, "Java"),
]


class TreeViewFilterWindow(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self, title="Treeview Filter Demo")
        self.set_border_width(10)

        # 要素を配置する self.grid の設定
        self.grid = Gtk.Grid()
        self.grid.set_column_homogeneous(True)
        self.grid.set_row_homogeneous(True)
        self.add(self.grid)

        # ListStoreモデルの作成
        self.software_liststore = Gtk.ListStore(str, int, str)
        for software_ref in software_list:
            self.software_liststore.append(list(software_ref))
        self.current_filter_language = None

        # ListStoreモデルから提供されたデータにフィルタをつける
        self.language_filter = self.software_liststore.filter_new()
        # フィルタ関数を設定する
        self.language_filter.set_visible_func(self.language_filter_func)

        # ツリービューを作成し、フィルタをモデルとして使用し、カラムを追加する
        self.treeview = Gtk.TreeView.new_with_model(self.language_filter)
        for i, column_title in enumerate(
            ["Software", "Release Year", "Programming Language"]
        ):
            renderer = Gtk.CellRendererText()
            column = Gtk.TreeViewColumn(column_title, renderer, text=i)
            self.treeview.append_column(column)

        # プログラミング言語でフィルタリングするためのボタンを作成し、イベントを設定する
        self.buttons = list()
        for prog_language in ["Java", "C", "C++", "Python", "None"]:
            button = Gtk.Button(prog_language)
            self.buttons.append(button)
            button.connect("clicked", self.on_selection_button_clicked)

        # レイアウトを設定し、ツリービューをスクロールウィンドウに入れ、ボタンを横に並べる
        self.scrollable_treelist = Gtk.ScrolledWindow()
        self.scrollable_treelist.set_vexpand(True)
        self.grid.attach(self.scrollable_treelist, 0, 0, 8, 10)
        self.grid.attach_next_to(
            self.buttons[0], self.scrollable_treelist, Gtk.PositionType.BOTTOM, 1, 1
        )
        for i, button in enumerate(self.buttons[1:]):
            self.grid.attach_next_to(
                button, self.buttons[i], Gtk.PositionType.RIGHT, 1, 1
            )
        self.scrollable_treelist.add(self.treeview)

        self.show_all()

    def language_filter_func(self, model, iter, data):
        """行内の言語がフィルタの言語であるかどうかを判定する"""
        if (
            self.current_filter_language is None
            or self.current_filter_language == "None"
        ):
            return True
        else:
            return model[iter][2] == self.current_filter_language

    def on_selection_button_clicked(self, widget):
        """いずれかのボタンがクリックされて呼び出される"""
        # 現在の言語フィルタをボタンのラベルに設定する
        self.current_filter_language = widget.get_label()
        print("%s language selected!" % self.current_filter_language)
        # フィルタの更新があるたびにビューが更新される
        self.language_filter.refilter()


win = TreeViewFilterWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()


関連記事