これらのカレンダースタイルはすべてNSDatePicker
多くのスペースを必要とするため、View Controllerに配置し、ボタンを使用して日付自体を表示し、クリックすると、を使用しNSPopover
てカレンダーをポップアップで表示することにしました。
次に、バインディングを使用して、NSButton
サブクラスのdate
プロパティをNSArrayController
'の現在の選択のNSDate
-typedプロパティにリンクします。次に、これはカレンダースタイルの日付ピッカーに伝播し、その逆も同様です。「別の方法」では、KVOを使用する代わりに、カスタムセッターを実装するだけなので、誰かがdate
ボタンまたはカレンダービューコントローラーでの値を設定すると(おそらく、配列コントローラーへのバインドによってボタンの値が設定されるため、ボタンがカレンダービューコントローラーの値を設定します。おそらくカレンダーの変更により、ビューコントローラーの値が設定されます。誰が知っているか)-次に、変更を伝達します。
それはすべてうまく機能することになります…1つのことを除いて。
複数選択。
ご覧のとおり、ユーザーが複数の選択を実行した直後に、システムは、選択が現在行われていることをボタンに通知することを正しく決定しますnil
。変更を伝播するために、バインドされたすべてのオブザーバーに、値がに変更されたことを通知しますnil
。これらのバインドされたオブザーバーの1つは、NSArrayController
現在複数のアイテムが選択されているです。
どうしたの?現在の選択範囲のバインドされたキーパスをnil
に設定すると、選択範囲内のすべての日付がnil
…データベースに変更を伝達するように設定されます。また、ユーザーは値をに設定していませんでした。nil
これは、選択を行わずに複数の選択を行った結果として伝播されたものであり、この値を表示する目的でのみ使用されることになっています。
ありがたいことに、今のところ、これらのカレンダーの複数選択をサポートする必要はありません。NSMultipleSelectionMarker
を使用して、監視対象オブジェクトの監視対象キーパスの値を比較することで、を検出するだけisEqual:
です。複数選択が行われる場合、値をivarに適用しますが、他のバインドされたオブジェクトに変更を「双方向に」通知しません。
しかし、ユーザーが複数のドキュメントを選択し、すべての発行日を一度に変更することをサポートしたい場合(ユーザーは、標準NSDatePicker
などで行われるバインディングの実装を使用して行うことができるためNSTextField
)、どうすればよいですか?複数の選択を作成するアクションなしでそうすることで、nil
選択されたすべてのオブジェクトへの設定などの誤った変更も伝播しますか?
(注:質問は、標準のコントロール(ボタン、ポップオーバー、日付ピッカー)を再利用する現在のニーズを反映するように書かれていますが、そのような再利用に限定されません。明日、完全にカスタムの関数グラフウィジェットを実装することをお勧めします。双方向のCocoaバインディングが必要です。)
以下に、関連するコードのチャンクをいくつか示しますIRDatePopoverEditorButton
。
+ (void)initialize
{
[super initialize];
[self exposeBinding:@"date"];
[self exposeBinding:@"minDate"];
[self exposeBinding:@"maxDate"];
}
- (void)awakeFromNib
{
[super awakeFromNib];
// …
[self establishBindings];
}
- (void)establishBindings
{
NSArray *keys = [NSArray arrayWithObjects:@"date", @"minDate", @"maxDate", nil];
NSDictionary * options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSContinuouslyUpdatesValueBindingOption,
[NSNumber numberWithBool:YES], NSConditionallySetsEnabledBindingOption,
[NSNumber numberWithBool:YES], NSValidatesImmediatelyBindingOption,
[NSNumber numberWithBool:NO], NSRaisesForNotApplicableKeysBindingOption,
[NSNumber numberWithBool:NO], NSAllowsEditingMultipleValuesSelectionBindingOption,
nil];
// This code allows creation of bindings via interface builder without actual
// support for custom bindings in IB. I hijack the 'user defined runtime attributes'
// and define binding keypath there, and I use an IBOutlet to define a binding
// target.
for(NSString *key in keys)
{
id bindingTarget = [self valueForKey:[key stringByAppendingString:@"BindingTarget"]];;
id bindingKeyPath = [self valueForKey:[key stringByAppendingString:@"BindingKeyPath"]];;
if (!bindingTarget || !bindingKeyPath)
continue;
[bindingTarget valueForKeyPath:bindingKeyPath];
[self bind:key
toObject:[self valueForKey:[key stringByAppendingString:@"BindingTarget"]]
withKeyPath:[self valueForKey:[key stringByAppendingString:@"BindingKeyPath"]]
options:options];
}
}
// one of the custom setters
- (void)setDate:(NSDate *)date
{
if(_date == date)
return;
[self willChangeValueForKey:@"date"];
[date retain];
[_date release];
_date = date;
[self didChangeValueForKey:@"date"];
NSDictionary * infoForBinding = [self infoForBinding:@"date"];
if(infoForBinding && [infoForBinding objectForKey:NSObservedObjectKey])
{
id observedObject = [infoForBinding objectForKey:NSObservedObjectKey];
NSString * observedKeyPath = [infoForBinding objectForKey:NSObservedKeyPathKey];
if([[observedObject valueForKeyPath:observedKeyPath] isEqual:NSMultipleValuesMarker])
{
// This is needed because, when performing multiple selection,
// we get called with setDate:nil. Then we blindly apply this to
// the entire selection.
// For our needs, it's enough to block applying values to multiple
// selection. But it's not a correct solution.
// FIXME: we currently block editing of multiple selection.
NSLog(@"Not applying value for keypath %@ to %@; currently multiple items are selected", observedKeyPath, observedObject);
}
else
{
[observedObject setValue:date forKeyPath:observedKeyPath];
}
}
if(self.date)
if(self.formatter)
[self setTitle:[self.formatter stringForObjectValue:date]];
else
[self setTitle:[date description]];
else
[self setTitle:@"-"];
}