あとFinder のリスト表示みたいに、階層表示をさせるための View が NSOutlineView でこれは NSTableView を継承している。これを使ってみたので、正しい使い方かどうかはおいておいてとりあえず忘れないうちにメモしておく。
テーブルを表示させるときは、numberOfRowsInTableView(tableView) と tableView_objectValueForTableColumn_row(tableView, col, row) という NSTableDataSource プロトコルを使ってデータを用意するんだけど、Outline View では階層表示がある分だけめんどくさい。用意するものは4つある。
outlineView_isItemExpandable(view,item) - 項目に下位要素(子ノード?)があってそれを開けるようにするか true/false で返す。
outlineView_numberOfChildrenOfItem(view,item) - 下位要素の数を返す。
outlineView_child_ofItem(view,index,item) - 下位要素を返す。
outlineView_objectValueForTableColumn_byItem(view,col,item) - テーブルと同じで、表示するデータを返す。
これが実は面倒くさい。あと、データは NSDictionary オブジェクトにするらしいんだけど、どういうフォーマットにすべきかよくわからない。というわけで、とりあえず適当な Hash オブジェクトを作ってそれを表示させてみることにした。
@displayItem = {"Children" => [{"Title" => "1","Children" => [{"Title" => "1-1"},{"Title" => "1-2"}]},{"Title" => "2"},{"Title" => "3"},{"Title" => "4"}]}
こんな適当なデータを表示させるために、次のようなスクリプトになった。これでいいのかよくわからんが、とりあえず表示はされる。
def outlineView_isItemExpandable(view,item)
item.nil? ? false : (item["Children"].nil? ? false : item["Children"].length != 0)
end
def outlineView_numberOfChildrenOfItem(view,item)
item.nil? ? 1 : item["Children"].length
end
def outlineView_child_ofItem(view,index,item)
item.nil? ? @displayItem : item["Children"][index]
end
def outlineView_objectValueForTableColumn_byItem(view,col,item)
item["Title"].nil? ? "Root" : item["Title"]
end
まあ、実際これを使うようなプログラムを作る時にでも(そんなことがあれば)もう少し詳しく調べる。
もう使うことがないかも、と思うのも、Table View に Array Controller があるように、Outline View には Tree Controller なるものがある(そのためだけにあるわけでもないと思うけど、主要な使い方の一つ)。
これを nib ファイルに追加して、outlet としてしまえばスクリプトからコントロールできる。
setContent(objects) でオブジェクトを追加できる。オブジェクトは、Array Controller みたいに key - value の組み合わせの配列。title という key で表示するデータを用意して、children で子要素を指定するばあいは、Interface Builder の inspector でこんな感じに設定する。Class Name はデフォルトのままで NSMutalbeDictionary にしておいて、Prepare Content にチェックを入れておく。
これができたら、Outline View を配置して、コラムの Value を NSTreeController にバインドする。今回コラムは一つだけ。このとき、ちゃんとコラムを選ぶ。Outline View 自体を選んでも何もできない。Tree Controller の名前は Item Tree としてある。
これで、次のようなデータを用意して setContent(object) で追加してみる。
@treeController.setContent([{"title" => "All","children" => [{"title" => "Group 1"},{"title" => "Group 2"}]},{"title" => "Unclassified"}])
実際は表示された時点では閉じているけど、こんな感じにデータは表示される。コラムが多いときは、データもそれに対応させる。
これだと楽なので、スクリプトで全部用意するのが面倒くさい。
Core Data アプリケーションでも使ってみる。
まず、データモデルを作る。とりあえずコラムは一つだけ。entry という属性を追加する。これに parent と children という関連も追加する。
それぞれデスティネーションを Group というか自信が含まれるエンティティにして対して parent から children へ、children から parent へと結んで、children から parent は対多関連にチェックを入れる。これで上のモデルのようになる。
これができてたら普通に Interface Builder のウィンドウなどに追加すると、Table View として追加されてしまう(しまった)ので、自前で Outline View を追加する。ついでに NSTreeController も追加する。
追加したら、NSTreeController の Attributes で Key Paths の Children に children を指定する。後の2つはそのままで。Mode は Entity にして、Entity Name にモデルで付けた名前を入れる。ここでは Group。そして、上でもあったように、Prepares Content にチェックを入れて、最後に Fetch Predicate に parent == nil と入れる。この最後のはよくわかってないけど、そうしろとあるのでそうした。場合によっては、なしの方がいいのかもしれないけど、とりあえずは基本に忠実に。
さらに、これは Core Data アプリケーションなので、この NSTreeController を AppDelegate もしくは Outline View を管理する NSObject のサブクラスに managedObjectContext を Model Key Path に選んでバインドする。これは、Core Data で NSArrayController をバインドしたときと同じはず。
最後に、Outline View の Value を entry というモデルで作った属性を Model Key Path にして Tree Controller にバインドする。
これで、Outline View が Core Data で使える。あとは、ボタンを配置して、Tree Controller の Received Actions に結びつけていく。ここではスクリプトからアクセスするために Outlets のところで AppDelegate の outlet として結びつけてある。
ほんとに最後に、ちょっともう一つ。デフォルトで、add や addChild を使うと何も入ってない要素が追加される。これだと、どこに追加されたかわからなくなるので、NSManagedObject のサブクラスを作り、そこで awakeFromInsert というメソッドで要素が追加されたときに何かしらの文字列が追加されるようにする。ここでは Untitled が追加されるようにしてる。
class MyData < OSX::NSManagedObject
include OSX
def awakeFromInsert
noteContents = NSString.stringWithString("Untitled")
self.setValue_forKey(noteContents,"entry")
end
end
この MyData をデータモデルエディタで Group エンティティのクラスに指定する。
これでちゃんと動くはず。