![]() | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() | ||
![]() | ![]() | ![]() | ![]() | ![]() |
|
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
以下は、Mac OS X 10.4.10 上の Xcode 2.5 で説明しています。
| 1 アプリケーション 2 ファイル構成 3 MainMenu.nib 4 DataSource クラス4.1 インターフェイス 4.2 データソースとしてのメソッド |
4.3 委任としてのメソッド 5 FileSystemItem クラス5.1 インターフェイス 5.2 初期化 5.3 他のメソッド 6 まとめ |
アプリケ−ションを起動すると、次のようなウインドウが表示されます。
![]() |
最初は、ルートディレクトリが「/」によって示されます。左向きの矢印は、その項目にサブ項目があることを示しています。矢印をクリックして項目を開くと、次のような表示に変わます。
![]() |
このサンプルは、アウトラインビューを使って、コンピュータにつながっている記憶装置やサーバーのディレクトリをブラウズできます。Finder のリスト表示のように表示できます。
プロジェクトを構成しているファイルを見てみましょう。プロジェクトファイルは次のようになっています。
![]() |
DataSource.h と .m がありますが、ここで実装されているクラスは NSObject のサブクラスで、アウトラインビューに表示されるデータを供給するデータソースです。また FileSystemItem.h と .m がありますが、ここで実装されているクラスは NSObject のサブクラスで、DataSource クラスから呼び出され、ファイルマネージャとのやりとりを担当します。
AppIcon.icns という画像がありますが、これはアプリケーションのアイコンです。Info-OutlineView.plist 内でアプリケーションのアイコンファイルとして指定されています。MainMenu.nib には、ユーザーインターフェイス等の定義があります。
MainMenu.nib は次のようになっています。
![]() |
MainMenu.nib の MyWindow のなかに、OutlineView があります。これはスクロールビュー内に入れられているので注意してください。インスペクタパネルで見てわかるように、一度クリックしただけではスクロールビューが選択されます。アウトラインビューの部分をダブルクリックすると、アウトラインビューが選択されます。テーブルビューやアウトラインビューのような構造の複雑なインスタンスの各部を選択するには、nib ウインドウ自体をアウトラインモードで表示されるのも効率的です。nib ウインドウの右上のほうにあるボタンで切り替えます。また、DataSource クラスのインスタンスがありますが、これが NSOutlineView の dataSource、delegate(委任)に接続されています。これを設定することで、データの供給を要求するメソッドと、委任メソッドが自動的に送られます。。アウトラインビューでのすべての操作は、この2つの接続によって実行されています。
DataSource クラスまず、DataSource クラスについて調べてみます。その名のとおり、これは NSOutlineView の dataSource として働くように作られています。DataSource.h では、NSObject のサブクラスであること以外、インスタンス変数やメソッドが一切定義されていません。。
メソッドも宣言されていませんが、これは nib ファイル内でデータソースや委任に指定されたため、必要なときに各メソッドが呼び出されるからです。
DataSource.m では、メソッドが定義されています。定義されているのは、outlineView: numberOfChildrenOfItem:、outlineView: isItemExpandable:、outlineView: child:(int)index ofItem:、outlineView: objectValueForTableColumn: byItem:、outlineView: shouldEditTableColumn: item: です。最初の 4 つがデータソースとしてのメソッドで、最後の1つがアウトラインビューの委任メソッドです。
アウトラインビューのデータソースが満たすべき動作は、NSOutlineViewDataSource というプロコトルで宣言されています。このプロトコルには、ここで定義されている以外にもメソッドがありますが、最低限、ここで定義されている 4 つは実装しなければなりません。
まずある項目の子の数を知らせるメソッドとして、outlineView: numberOfChildrenOfItem: があります。
ここでは、item が nil であれば(つまり一番上の項目であるルートであれば)1 を、そうでなければ、item に numberOfChildren メソッドを送っています。このメソッドは、FileSystemItem クラスで定義されています。このクラスについては、後で説明します。項目の子の項目の数を返すメソッドです。
次にある項目が開けるかどうかを返すメソッドとして、outlineView: isItemExpandable: があります。
これは上のメソッドとほとんど同じで、子の数が -1 でなければ YES を返すようになっています。
次に、ある項目の子のうち特定の番号のものを返すメソッドとして、outlineView: child: ofItem: があります。
ここでは、項目がルート項目なら、FileSystemItem の rootItem メソッド、そうでなければ、childAtIndex: index: メソッドを送って、返ってきたものをそのまま返しています。
最後に、指定された項目のデータを返すメソッドとして、outlineView: objectValueForTableColumn: byItem: があります。
ここでは、ルート項目であれば、「/」という文字列を、そうでなければ、FileSystemItem クラスの relativePath メッセージを送り、その結果を返しています。
DataSource はアウトラインビューの委任としても設定されています。アウトラインビューの委任メソッドは、たくさんあります。項目を閉じたり開いたり、選択されたり、セルを表示するときや、列の移動やリサイズが行われたとき、列が編集されるとき、テーブルコラムがマウス操作されたときなどにメッセージが送られます。
このサンプルでは、セルが編集できるかどうかを指定する、次の委任メソッドを実装しています。
これはつねに NO を返します。すなわち、このアウトラインビューのセルは一切編集できません。
FileSystemItem クラスFileSystemItem は、NSObject のサブクラスとして、以下のように定義されています。
このクラスは、アウトラインビューで使われる項目1つ1つを表します。インスタンス変数は 3 つあります。relativePath はパス文字列を保存します。parent は親となる項目を保存します。children は子のリストを保存しておく可変配列です。また、次の静的変数とマクロが定義されています。
初期化メソッドは、ヘッダに宣言されておらず、内部的に利用されます。これは、次のようになります。
初期化は、NSString で与えられるパス文字列と、親のオブジェクトによって行なわれます。インスタンス変数の relativePath には、パス文字列の最後の成分がコピーされます。これはコピーメソッドの暗黙の了解として保持されていることに注意してください。そして、parent に親オブジェクトを格納しています。今度は単に参照を格納しているだけで、保持 (retain) はしていないことに注意してください。あとで見るように、子配列の中の子は配列によって保持されるので、親が子を保持するものの、子は親を保持しないようにして、保持の循環が発生するのを防いでいます。ここで children は初期化されていませんが、これは子のオブジェクトが与えられたときに保存され、維持されることになります。また、上で説明した保持オブジェクトの 解放を dealloc メソッドで行っています。
このクラスには、クラスメソッドとして、rootItem が定義されています。
これは、単純に静的変数である rootItem を返します。rootItem が nil だった(まだ一度も参照されていない)場合、rootItem を初期化しています。これは、アプリケーション内で単一のインスタンスであることが保証されている必要があるオブジェクト(シングルトン)を作成するときに使われるパターンです。AppKit における共有オブジェクトも、クラスオブジェクトによって返され、たいていこのような形で実装されていると思われます。
さて、このクラスが所持しているパス名は最後の成分だけなので、そのため、パスを扱うメソッドが2種類定義されています。
relativePath は単純に自分の持っているパス名の成分を返します。通常、これはファイル名やディレクトリ名です。fullPath は、3 項 if 演算子で親があるかどうかを調べ、あるなら親に自分のパス名の前にパスを加えてもらいます。ないなら、自身の相対パスを返します。これは、ルート項目に到達するまで再帰的に呼び出されることになるので、結果としてルート項目からのパス、つまり絶対パス(または完全パス)が返ることになります。
次に、子の項目を返す children ですが、このメソッドが呼び出されたとき、ファイルマネージャーを利用して、子の項目が作られます。
子の項目があった場合、それを返します。そうでなければ、デフォルトのファイルマネージャーを取得して、自分の完全パスを指定して、それがディレクトリであるかどうかを問い合わせています。1 で問い合わせが行われています。戻り値は存在するかどうかで、ディレクトリかどうかは isDir のなかに間接的に(変数のアドレスを渡し、メソッド終了後にそこに値が入れられる)返されます。
存在して、かつディレクトリなら、ファイルマネージャーを利用して、ディレクトリ内の項目を NSArray として取得したあと、NSMutableArray にそれらのファイルに対応する FileSystemItem クラスを作成して格納しています。これで、子のリストが作成されます。ディレクトリでない場合、children には IsALeafNode(つまり -1)が格納される。これで children を調べるだけで、葉ノードかどうかがわかります。
子の項目に関するメソッドは、上記の children のほかに、次の2つのメソッドが定義されています。
childAtIndex は、特定の番号位置にある項目を返します。このメソッドは、NSMutableArray であるインスタンス変数 children に、objectAtIndex: メッセージを送って結果を返しているだけです。
numberOfChildren も同様に子があれば、children に対して、NSMutableArray の count メッセージを送っているだけです。
Cocoa に慣れない人にとっては、最初はテーブルビューやアウトラインビューにどのようにデータを表示するか疑問に思うかもしれません。テーブルビュー内で、そのようなメソッドが定義されていたほうがいいように思いますが、このようにデータの供給を切り離すことで、それを切り替えたり、さまざまな可能性に対応しやすくなっています。また、このおかげでバインディング等に対する新しい技術の導入も、それほど違和感なく行えます。
各メソッドの説明をしましたが、どう動くかを知りたい人は、データソースメソッドや委任メソッドなどにブレークポイントを設定するなどして、デバッガで動かしてアウトラインビューを操作してみてください。どういうタイミングで呼ばれているのかがわかるはずです。
管理人:神吉 秀典 E-mail: