[戻る]

複数のGtkTreeViewで一つのモデルを別々のソート順で表示する

モデルデータを共有し、ソート項目・順序はそれぞれ別のものを使用します。
ちょっと凝ったことをするにはいろいろ気をつけないといけないようです。

GtkTreeModelSortを使用したモデルデータの共有

モデルデータを共有するだけであれば複数のGtkTreeViewで同じモデルを使用するだけで済むのですが、その場合はソート項目と順序も同じになってしまいます。
同じデータを使用するけれどソート順は別にしたい場合にはGtkTreeView毎に同じモデルを持つGtkTreeModelSortを作成して、それをGtkTreeModelとして使用します。
このGtkTreeModelSortが間に入ることにより(プロクシとして動作し)データ共有と別々のソート順の設定を可能にします。

サンプルです。gtktreemodelsort.c
最初にボタンだけを持ったウィンドウを表示します。このボタンを押す毎に、追加・削除ボタンとGtkTreeViewを持ったウィンドウを表示します。
追加ボタンを押すとGtkTreeViewに連続した数と乱数の組を1行追加し、削除ボタンで選択中の行を削除します。
GtkTreeViewの項目ヘッダをクリックするとソート項目・順序を変更できます。
全てのウィンドウで同じモデルを使用しているので、1つのウィンドウで追加・削除をした結果が他のウィンドウにも反映されますが、ソート項目・順序はウィンドウ毎に別の設定が可能になっています。
文字コードはUTF-8です。
[TOP]

GtkTreeModelSortの作成

GtkTreeModelSortの作成は下記関数を呼び出すだけです。
GtkTreeModel * gtk_tree_model_sort_new_with_model ( GtkTreeModel *child_model );
child_modelにあらかじめ作成しておいたGtkListStoreGtkTreeStoreを渡します。
サンプルではcreate_tree_view_window()の最初でこれを行っています。
GtkTreeViewに作成したGtkTreeModelSortGtkTreeModelとして渡した後、GtkTreeViewの作成はこれといって特別なことをしていません。
[TOP]

モデルデータの取得

GtkTreeModelSortGtkTreeModelインターフェイルを実装しているので、GtkTreeViewにセットした後はいつもと同じようにgtk_tree_view_get_model()gtk_tree_selection_get_selected_rowsでセットしたGtkTreeModelSortを取得し、gtk_tree_model_get()GtkTreeModelSortから指定した行と列のデータを取得できます。

サンプルでもGtkTreeSelectionchangedシグナルハンドラ内でGtkTreeModelSortから数値データを取得しています。
ただし、GtkTreeSelectionchangedシグナルハンドラを使用する場合は少し注意が必要です。
[TOP]

行の追加

モデルにデータを追加するときは、GtkTreeModelSortから内部のモデルを取得し、そこに対して追加処理を行います。
GtkTreeModel * gtk_tree_model_sort_get_model ( GtkTreeModelSort *tree_model );
モデルを取りだしたらそれを実際の型、例えばGtkListStoreGtkTreeStoreに変換して、そのデータ追加関数を使用すれば、全てのGtkTreeViewに変更が反映されます。
サンプルでは追加ボタンのclickedシグナルハンドラadd_model_data()gtk_tree_model_sort_get_model()を使用し、取得したモデルでgtk_list_store_append()gtk_list_store_set()を呼んで新規行の追加を行っています。
[TOP]

行の削除

モデルからデータを削除する場合は少し複雑になります。
削除データを指定するにはGtkTreeViewが表示している行のGtkTreePathGtkTreeIterを使用することになると思いますが、今回これらはGtkTreeModelSortの行を指しています。
実際の削除はその内側にあるGtkTreeModelに対して行う必要があり、GtkTreeModelSortの行を指すGtkTreePathGtkTreeIterはそのまま使用できません。
そこで以下の変換関数を使用して、GtkTreePathあるいはGtkTreeIterGtkTreeModelSortのものから、内側のGtkTreeModelのものへと変換します。
GtkTreePath * gtk_tree_model_sort_convert_path_to_child_path ( GtkTreeModelSort *tree_model_sort,
                                                               GtkTreePath      *sorted_path );

void          gtk_tree_model_sort_convert_iter_to_child_iter ( GtkTreeModelSort *tree_model_sort,
                                                               GtkTreeIter      *child_iter,
                                                               GtkTreeIter      *sorted_iter );
gtk_tree_model_sort_convert_path_to_child_path()sorted_pathGtkTreeModelSortGtkTreePathを渡すと、GtkTreeModelSortの内側のモデルのGtkTreePathが返されます。
gtk_tree_model_sort_convert_iter_to_child_iter()の場合はsorted_iterGtkTreeModelSortGtkTreeIterを渡すと、child_iterに変換されたGtkTreeIterがセットされます。

サンプルでは削除ボタンのclickedシグナルハンドラremove_model_data()gtk_tree_selection_get_selected_rows()で取得したGtkTreePathを、実際のモデルを指すものへ変換しています。
また、データの削除を行うと取得済のGtkTreePathは無効になるので、削除対象の行を全てGtkTreeRowReferenceに変換して、削除する直前にGtkTreePathに戻すという事をしています。
実際のモデルのGtkTreePathが取得できたら後はGtkTreeIterに変換して、gtk_list_store_remove()を使用すれば同じモデルを使用しているGtkTreeModelSortを表示しているGtkTreeViewに変更が反映されます。

サンプルでは削除の前後でGtkTreeSelectionchangedシグナルハンドラの呼び出しを一時的にブロックしています。
これは注意点で説明します。



内側のGtkTreeModelにセットしたモデルの行を指すGtkTreePathGtkTreeIterからGtkTreeModelSortのものへ変換するには以下の関数が使用できます。
GtkTreePath * gtk_tree_model_sort_convert_child_path_to_path ( GtkTreeModelSort *tree_model_sort,
                                                               GtkTreePath      *child_path );

void          gtk_tree_model_sort_convert_child_iter_to_iter ( GtkTreeModelSort *tree_model_sort,
                                                               GtkTreeIter      *sort_iter,
                                                               GtkTreeIter      *child_iter );
gtk_tree_model_sort_convert_child_path_to_path()child_pathに実際のモデルを指すGtkTreePathをセットするとGtkTreeModelSortGtkTreePathが返されます。
gtk_tree_model_sort_convert_child_iter_to_iter()child_iterに実際のモデルのGtkTreeIterを渡して呼びだすとsort_iterに変換されたものがセットされます。
先程のものとちょっと名前が違うだけなので慣れないとどちらを使えば良いのか迷うかもしれません。
[TOP]

注意点

GtkTreeModelFilterのときにも似たような現象に遭遇したのですが、 GtkTreeSelectionchangedシグナルハンドラを使用しているとき、 別のGtkTreeViewで行なった行削除により選択状態が変更になるとchangedシグナルハンドラが呼び出されますが、 このときgtk_tree_selection_get_selected_rows()を使用すると取得したGtkTreePathのリストに無効な値が含まれるようです。
内部状態が複雑になる為か一時的に整合性の破綻した状態でシグナルハンドラが呼び出されているのかもしれません。
普通であれば取得したGtkTreePathgtk_tree_model_get_iter()GtkTreeIterに変換し、 gtk_tree_model_get()を呼び出せばその行のデータが取得できますが、無効なGtkTreePathを使用するとコアダンプして落ちます。

やっかいな事にこの無効な値を簡単には判別できませんでした。
GtkTreeIterに変換後、gtk_tree_model_sort_iter_is_valid()で検査してもTRUEしか返らず正常な値であると判定されます。
試行錯誤したところ不正な値の場合、gtk_tree_model_sort_convert_path_to_child_path()で内側のモデルのGtkTreePathに変換し、 さらにgtk_tree_row_reference_new()GtkTreeRowReferenceに変換しようとするとNULLが返されるようです。
しかし、このようなちょっと実装が変れば使えなくなるような方法は使うべきではないですし、もっと簡単な方法がありました。
削除処理中にはchangedシグナルハンドラの呼び出しをブロックするだけです。

ブロックするのは同じモデルから作成したGtkTreeModelSortを使用しているGtkTreeViewに関連するGtkTreeSelectionchangedシグナルハンドラ全てです。
サンプルではcreate_window()の最後、changedシグナルハンドラを登録している所でg_object_set_data()を使用して GtkTreeSelectionにシグナルハンドラのIDを、モデルにGtkTreeSelectionのリストを登録して、後で参照できるようにしています。
ここで登録した値を削除ボタンのclickedシグナルハンドラで使用します。
void     g_object_set_data ( GObject     *object,
                             const gchar *key,
                             gpointer    data );

gpointer g_object_get_data ( GObject     *object,
                             const gchar *key );
g_object_set_data()keyに関連するデータを登録し、 g_object_get_data()で登録したデータを取り出します。
keyは任意の文字列を指定できます。

サンプルの削除ボタンのclickedシグナルハンドラでは削除対象の行のGtkTreeRowReferenceを取得した後、実際に削除する前に g_signal_handler_block()で全てのchangedシグナルハンドラをブロックし、 全て削除した後にg_signal_handler_unblock()を呼び出してブロック解除しています。
(実際のブロックとブロック解除はblock_changed_signal()unblock_changed_signal()で行っています)
void g_signal_handler_block ( gpointer instance,
                              gulong   handler_id );

void g_signal_handler_unblock ( gpointer instance,
                                gulong   handler_id );
instanceにブロックまたはブロック解除するオジェクトを、handler_idg_signal_connect()でハンドラ登録した時の戻り値をセットして呼び出します。
同じinstancehandler_idに対してg_signal_handler_block()を複数回呼び出した時には、同じ回数だけg_signal_handler_unblock()を呼び出さないとブロック解除されません。

最後に、ブロック解除した後、ブロックしている間に変更された選択状態を反映させる為、GtkTreeSelectionchangedシグナルハンドラを直接呼び出しています。

この症状は環境依存かもしれません。
FreeBSD 7.1-RELEASE-p5でportsコレクションからインストールしたGTK+ 2.16.1で発生しました。 GTK+が同じバージョンでも他の環境では発生しないかもしれません。
[TOP]

GtkIconViewでGtkTreeModelSortを使用すると…

GtkIconViewGtkTreeModelSortを使用したところ、削除の反映が正しく行なわれませんでした。
見かけ上は最後のアイテムがいくつか複製された状態になりますが、実際には存在しないアイテムなので値の取得や削除はできません。
GtkTreeViewに比べてGtkIconViewはまだまだといった感はありますが、ここでもそういった結果になりました。
解決方法は調べていません。
サンプルです。gtktreemodelsort_iconview.c

この症状は環境依存かもしれません。
FreeBSD 7.1-RELEASE-p5でportsコレクションからインストールしたGTK+ 2.16.1で発生しました。 GTK+が同じバージョンでも他の環境では発生しないかもしれません。
[TOP]
[戻る]