由于所有这些日历样式NSDatePicker
占用大量空间,我决定将它们放在视图控制器中,使用按钮显示日期本身,并在单击时使用 anNSPopover
在弹出窗口中显示日历。
然后我使用绑定将NSButton
子类的date
属性与NSArrayController
当前选择的NSDate
类型属性链接起来。然后传播到日历样式的日期选择器中,反之亦然。对于“其他方式”,我没有使用 KVO,而是简单地实现了自定义设置器,所以如果有人date
在按钮或日历视图控制器上设置值(也许绑定到数组控制器设置按钮的值,所以按钮设置日历视图控制器的值;也许日历上的更改设置视图控制器的值;谁知道)——然后我传播更改。
一切都很好地结束了……除了一件事。
多选。
您会看到,在用户执行多项选择后,系统立即正确地决定告诉我的按钮选择现在nil
。为了传播更改,我告诉所有绑定的观察者该值已更改为nil
. 这些绑定的观察者之一是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:@"-"];
}