ページ

2009年2月11日水曜日

Cocoa Bindingsを使ったメニューの排他選択

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

前回 SimpleViewerにコンテキストメニューを追加したが、この中で背景は Black, Checkboard, White の3つから一つ選択するようになっている。


例えば Blackにチェックがついている状態でCheckboardを選ぶと、Balckのチェックが外れ Checkboardにチェックが付く。これを実装するには少し工夫が必要で以前はTarget/Actionを使って力技で対処した。

Cocoaの日々: simpleCapへコンテキストメニューを追加 (2)


今回はTarget/Actionではなく Cocoa Bindings で対処してみた。

NSMenuItemの Value へモデルを紐付け(バインディイング)しておくと、その値に応じてメニューにチェックが付いたり、外れたりする。例えば下の図では NSMenuItem "Balck" に backgroundBlack というモデルを紐づけてある。モデルのオーナーは "Simple Viewer Controller" にした。これは InterfaceBuilder上でインスタンス化してあって扱いやすいため。同様に "Checkboard"には backgroundCheckboard、"White"には backgroundWhite という名前を紐づけておく。


次はモデルの実装を行う。Cocoa Bindings では Key-Value-Codingの仕組みを採用しているので実体はなんであれ setter/getter を用意してやるとそれが使われる。先ほどの backgroundBlack というモデルの場合、下記のようなインターフェイスを用意すればいい。

- (BOOL)backgroundBlack;
- (void)setBackgroundBlack:(BOOL)flag;

同様に他のモデルも用意してやる。
- (BOOL)backgroundWhite;
- (void)setBackgroundWhite:(BOOL)flag;
- (BOOL)backgroundCheckboard;
- (void)setBackgroundCheckboard:(BOOL)flag;

これでメニューが表示される時、もしくは選択された場合は自動的にこれらのメソッドが呼出される。

さて実装だが、背景の値はUserDefaultsで管理している。この実体は整数値で 0=Black, 1=Checkboard, 2=Whiteと割当てある。
それを踏まえた上で backgroundBlackの実装を見てみよう。まずメニューを表示する時には現在の値をチェックする為に getter である #backgroundBlack が呼出される。チェックを付けるか否かは背景値が 0(=Black)かどうかで判断できる。こんな感じ。
- (BOOL)backgroundBlack{
return ([[UserDefaults valueForKey:UDKEY_VIEWER_BACKGROUND] intValue] == 0);
}


次はメニューで選択された時。この場合は setterにあたる #setBackgroundBlack: が呼ばれる。
- (void)setBackgroundBlack:(BOOL)flag
{
if (flag) {
[UserDefaults setValue:[NSNumber numberWithInt:0] forKey:UDKEY_VIEWER_BACKGROUND];
[self setBackgroundCheckboard:NO];
[self setBackgroundWhite:NO];
[UserDefaults save];
} else if ([[UserDefaults valueForKey:UDKEY_VIEWER_BACKGROUND] intValue] == 0) {
[self setBackgroundCheckboard:YES];
}
}

もし flag==YES ならチェックされたことになるので、他の checkboard, white の値を NOにする。これが排他処理のポイント。そして後半の else if は、元々チェックが付いている状態で選択がされた時の処理を行っている。何もしないと見た目はチェックが外れてしまうので、今回は次の checkboardにチェックが付く様にしておいた。blackからチェックを外さないというインターフェイスもあるが、今回のBindingsだけを使う方法では難しい。特に害は無いと思うので今回はこれで行こう。

他の2つの設定についても同じ処理を書けばできあがり。

- - - -
もっとスマートな方法があると良いのだが
今はこれが精一杯か。
(良い方法があれば是非教えて欲しい)