validateUserInterfaceItem(1)
メニューとツールバーの有効/無効を制御するプロトコルとして”NSUserInterfaceValidations”がある。
これをNSButtonに対して拡張する手法がある。2006年〜2007年頃に書いたコードだ。
@interface MTLButton : NSButton
{
}
- (void) updateEnableState;
@end
@implementation MTLButton
- (id)initWithFrame:(NSRect)frameRect
{
self = [self initWithFrame:frameRect];
if(self)
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(updateEnableState)
name:NSWindowDidUpdateNotification
object:nil];
}
return self;
}
- (void) dealloc
{
[self subviews];
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
- (id) initWithCoder: (NSCoder*)aDecoder
{
self = [super initWithCoder: aDecoder];
if(self != nil)
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(updateEnableState)
name:NSWindowDidUpdateNotification
object:nil];
}
return self;
}
- (void) updateEnableState
{
// actionが設定されているかをチェック。(target == nil)の場合はfirstResponderだから有効なのでtargetのnilチェックはしない。
if( [self action] != nil && [self window] != nil && [self isHiddenOrHasHiddenAncestor] == NO)
{
id theValidator;
// レスポンダーチェーンをたどって実際にactionを処理するオブジェクトを探す。そのオブジェクトがメニューやボタンのenable/disableの値を決定出来るはず
theValidator = [NSApp targetForAction:[self action]
to:[self target]
from:self];
if([self infoForBinding:@"enabled"])
{
// enabledにCocoaBindingが設定されている場合は
// CocoaBindingの設定を優先させるために何もしない。
}
else if ((theValidator == nil) || ![theValidator respondsToSelector:[self action]])
{
// actionを実行できるレスポンダーが無い場合は、ボタンは押せない
[self setEnabled:NO];
}
else if ([theValidator respondsToSelector:@selector(validateToolbarItem:)])
{
// actionを実行できるレスポンダーが"validateToolbarItem:"を実装しているならば従う
[self setEnabled:
[theValidator validateToolbarItem:
(NSToolbarItem*)self]];
}
else if ([theValidator respondsToSelector:@selector(validateUserInterfaceItem:)])
{
// actionを実行できるレスポンダーが"validateUserInterfaceItem:"を実装しているならば従う
[self setEnabled:
[(id<NSUserInterfaceValidations>)theValidator validateUserInterfaceItem:
(id<NSValidatedUserInterfaceItem>)self]];
}
else
{
// レスポンダーがあるけど、"validateXXXX"系のメソッドが無い場合は、ボタンは押せる。
[self setEnabled:YES];
}
}
}
@end
最近、仕事でiOSでなくCocoaのコードを書く事があり気がついた。 このコードのNotificationを登録する箇所と値が間違っている。
- 登録する場所は、 “[MTLButton viewDidMoveToWindow]”で行われるべき。
- 登録解除する場所は、”[MTLButton viewWillMoveToWindow:newWindow]”で行われるべき。
- Notification登録時にButtonが乗っているWindowでフィルタリングするべき。
そんな訳で、
- (id)initWithFrame:(NSRect)frameRect;
- (void) dealloc;
- (id) initWithCoder: (NSCoder*)aDecoder;
は削除され、以下のコードで置換えるべきだと思うのだが、まだ実装してません。
- (void)viewDidMoveToWindow
{
[super viewDidMoveToWindow];
NSWindow* theWindow = [self window];
if( theWindow != nil )
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(updateEnableState)
name:NSWindowDidUpdateNotification
object:theWindow];
}
}
- (void)viewWillMoveToWindow:(NSWindow *)newWindow
{
[super viewWillMoveToWindow:newWindow];
NSWindow* theWindow = [self window];
if( theWindow != nil )
{
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSWindowDidUpdateNotification
object:theWindow];
}
}
ひょっとしたら、windowが放棄される時に”viewWillMoveToWindow:newWindow”が呼ばれないかもしれない。 が、もう寝る。