SSブログ

Mac用USBデバイス-100 Cocoaバインディング [Mac用USBデバイス工作]

昨日は種類の違うクラスを子要素に追加するようなモデルではNSTreeControllerを使うことがなかなか難しいことがわかった。こういうときは蛸壺サブクラス化を考えるのが常套手段。

ところでこれがUSBデバイスネタのちょうど百個目。いや、だからそれは十進法でキリがいいだけだってば、何度も言ってるように。でも、なんとなくうれしい。

5.3.3  別の方法 - NSTreeControllerのサブクラス化

これではうまくいかない。せっかくNSTreeControllerを使うなら、NSTreeControllerのadd:メソッドを経由しないとあまり意味がない。そのために一番簡単なのはNSTreeControllerのサブクラスを作ってadd:やaddChild:を上書きすることだろう。例えばこのメソッドのシグネチャは
- (void)add:(id)sender;
のような形になっている。これをボタンのアクションとして起動したらこのメソッドの引数のsenderとしてそのボタンが渡される。これでどのボタンが押されたかを知ってそれで追加するインスタンスを振り分ける、ということが考えられる。最終的にはsuperのadd:やaddChild:メソッドを呼び出す必要がある。

例えばNSTreeControllerのサブクラスを作って
- (void)addChild:(id)sender
{
    [super setObjectClass:NSClassFromString([sender title])];
    [super addChild:sender];
}
とする。setObjectClass:メソッドはNSObjectControllerが持っているもので新しい要素はこのクラスにallocとinitが投げられることで作られる、とreferenceに書いてある。また、NSClassFromString()と言う関数はクラス名の文字列(NSString)からクラスオブジェクトを返すRuntimeの関数で、この例の場合、ボタンのタイトルはクラスの名前そのままだとしている。こうすればボタンに書かれた名前のクラスのインスタンスが追加されることになり、しかもNSOutlineViewと同期もとれる。

もちろん、ボタンそれぞれに対応したadd???メソッドがあって、それぞれがクラスを設定したあと、同じ親クラスのaddChild:メソッドを呼ぶことにしてもいい。まあ、メソッドの数が増えるよりこっちの方がシンプルに見えるだろう。どっちみちサブクラス化は必要になる。

ちなみにNSClassFromString関数はNSObjCRuntime.hに宣言があって、これを使えばオブジェクトコードだけがあってヘッダのないクラスのインスタンスも作れてしまう。C++などと較べると非常に不思議な動作で、これだけ見てもObjective-CのRuntimeは普通の言語なら実行時には落としてしまうようないろんな情報を文字列の形のまま保持していることがわかる。

まあ、とりあえず、これでよし。

5.3.4  Availability

選択されているデスクリプタによって追加できるデスクリプタに制限があるので、追加できないデスクリプタを作るボタンは実行不能でなければならない。そのときはdim(薄い)表示になる必要がある。これはターゲット/アクションを使った専用のコントローラクラスで実装しようとするとちょっとした細工をする必要があり、案外めんどくさいが、Cocoaバインディングを使うと非常に楽になる。

たしかに、NSTreeControllerにはcanAddやcanAddChildといった、オブジェクトの種類に制限のないAvailabilityへの応答用のメソッドはある。しかしこれでは今回のような、子オブジェクトの種類を選別して追加可能/不能を振り分けるというようなことはできない。ならどうするか、というとさっきと同じようにNSTreeControllerのサブクラスで専用のcanAdd???などというメソッドを実装しないといけないのか。

実はこの場合、Interface BuilderのバインディングパネルのAvailabilityにある「Enabled」にその条件となるキーを追加するだけでできる。今回の場合例えば図-9のようにNSTreeControllerへバインドして「Controller Key」を「selection」に、「Model KeyPath」を「canAddExtra」にしたとする。
0513fig09.png
この場合、NSTreeControllerのContentの選択されている要素に対して問い合わせがいく。例えば
- (BOOL)canAddExtra;
というメソッドが実装されていれば、この結果が反映される。もちろん、要素となるオブジェクトがこのメソッドを持っていないとvalueForUndefinedKey:メソッドが呼ばれて(デフォルト動作では)例外が発生する。

要素の追加の場合と違って、この問い合わせはキー値コーディングで行われるので任意のセレクタを指定することができる。NSTreeControllerは図-10のようにUI部品とモデルとなる要素との仲介役を果たしていると考えられる(コントローラとしての機能そのものである)。選択された要素が変わればNSTreeControllerが要素オブジェクトを切り替えて、キー値監視のメカニズムを使ってボタンの表示を変更する。
0513fig10.png
この場合はどの要素に対しても同じメソッドを投げることになるので、子要素を作るときのようにサブクラス化して、それぞれのメソッドを実装するといった手間は必要ない。

これをCocoaバインディングとNSController(あるいはそのサブクラス)を使わずにやろうとすると、要素オブジェクトがどんなメソッドを実装しているかをあらかじめコントローラは知っている必要がある(従って当然これ専用のコントローラオブジェクトになる)。この場合、コントローラオブジェクトは要素オブジェクトがcanAddExtraというメソッドを(ヘッダファイルを通じて)知っていて、Extraオブジェクトを追加するためのボタンであるならこのメソッドに問い合わせてボタンをdim表示するかどうかを決める必要がある。

今回の場合はNSTreeControllerはボタンから呼ばれたキーをただ要素オブジェクトに左から右へ渡すだけで中身については関知していない。ではNSTreeControllerは何をしているかというと、どの要素オブジェクトにキーを渡すか、を決めるという仕事に集中している。コントローラ無しにはこの要素選択をボタン自身がしなければならない。キー値コーディングというランタイムの機能を使うことでオブジェクト間の切り分けがきれいにできて、簡単になっていることがわかる。
nice!(0)  コメント(0)  トラックバック(0) 

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

献立05/13Mac用USBデバイス-101 Coco.. ブログトップ

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。