我有一个NSTableView的经典设置,其中的列绑定到NSArrayController的arrangeObjects的各种keyPath,并且NSArrayController绑定到一个字典数组。
使用此设置,在tableview中选择一行或多行将自动运行。表视图和数组控制器协同工作,很容易查询NSArrayController以获取所选对象的列表。
我的一个表列包含NSButtonCells,即复选框类型。有没有办法使用Cocoa Bindings将每行中的复选框绑定到该行的选择状态?我知道我可以在代表每一行的NSDictionary中添加另一个值,但这会复制NSArrayController中已有的选择信息。
如果有必要这样做,也希望快速草拟您的实施。
由于
答案 0 :(得分:1)
所以,对此的答案不适合胆小的人。原因是你试图让NSTableView做一些自然不想做的事情。
首先使用cocoa的本机NSTableView多选行为: - 单击一行选择它并取消选择其他行 - 保持控件并单击列仅切换该行的选择状态
现在,添加一列复选框。对于此行,规则是不同的: - 单击复选框仅切换该行的选择状态
如果我们可以捕获复选框的点击并自行处理,这将很容易。现在我们可以,但问题是,在我们处理它们之后,它们仍然被转发到NSTableView,以通常的方式改变选择。 [注意:可能有一些方法可以避免这种转发 - 如果你知道一个,请告诉我]
所以这就是你如何(最终)完成这个: - 为基础对象数组中的每个对象添加“selectedInView”字段。将观察者添加到关键路径的关联NSArrayController:“selectedObjects”。当选择更改时,为每个对象相应地设置selectedInView字段。像这样:
if([keyPath isEqualToString:@"selectedObjects"]) {
// For table view checkbox's: keep "selectedInView" of song dictionaries up to date
[_arrayController.arrangedObjects enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
BOOL sel = [_arrayController.selectedObjects containsObject:obj];
if([[obj objectForKey:@"selectedInView"] boolValue] != sel)[obj setValue:[NSNumber numberWithBool:sel] forKey:@"selectedInView"];
}];
现在出现了棘手的部分:复选框出现故障的唯一时间是存在选择。以下是案例类型:
设置:选择行1,2,3。复选框单击第4行。 结果:选中第四行的复选框。第四行被选中。行的1,2,3被取消选择(因为这是NSTableView自然做的)
要解决此问题,只要单击一个复选框,您就需要创建一个临时数组来记住当前选择,加上或减去刚刚点击的复选框:
- (void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
if([tableColumn.identifier isEqualToString:@"CheckBox"]) {
NSMutableDictionary *song = [_arrayController.arrangedObjects objectAtIndex:row];
if(!_tempSelectedSongs && _arrayController.selectedObjects) _tempSelectedSongs = [[NSMutableArray arrayWithArray:_arrayController.selectedObjects] retain];
if(_tempSelectedSongs) {
if([_tempSelectedSongs containsObject:song]) {
[_tempSelectedSongs removeObject:song];
} else if(![_tempSelectedSongs containsObject:song]) {
[_tempSelectedSongs addObject:song];
}
}
}
}
现在,在tableview完成选择处理后,我们希望将选择设置为它应该是什么。有一个很有前途的功能,允许您在实际进行选择之前修改tableview选择。您可以像这样修改它:
- (NSIndexSet *)tableView:(NSTableView *)tableView selectionIndexesForProposedSelection:(NSIndexSet *)proposedSelectionIndexes {
NSMutableIndexSet *newSet = [NSMutableIndexSet indexSet];
if(_tempSelectedSongs) {
NSMutableIndexSet *indexSet = [NSMutableIndexSet indexSet];
[_tempSelectedSongs enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSUInteger index = [_arrayController.arrangedObjects indexOfObject:obj];
if(index != NSNotFound) [indexSet addIndex:index];
}];
proposedSelectionIndexes = indexSet;
[_tempSelectedSongs release]; _tempSelectedSongs = nil; [_tempSelectedSongsTimer invalidate]; [_tempSelectedSongsTimer release]; _tempSelectedSongsTimer = nil;
}
[proposedSelectionIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
NSProgressIndicator *progress = ((BDDiscreteProgressCell *)[[_arrayController.arrangedObjects objectAtIndex:idx] objectForKey:@"BDCell"]).progress;
if(!progress)
[newSet addIndex:idx];
}];
return newSet;
}
这很好用,但是调用NSTableView委托函数的顺序有问题。显然,我们需要第一个函数 - 我们设置临时数组 - 在第二个函数之前调用 - 我们使用这些函数。
无论出于何种原因,事实证明当你选择一个复选框时,这就是事情的运作方式, 但是当你选择一个复选框时,会发生相反的情况。因此,对于这种情况,您可以向上面的keyPath观察者添加更多代码:
if([keyPath isEqualToString:@"selectedObjects"]) {
if(_tempSelectedSongs) {
_arrayController.selectedObjects = _tempSelectedSongs;
[_tempSelectedSongs release]; _tempSelectedSongs = nil;
}
// For table view checkbox's: keep "selectedInView" of song dictionaries up to date
[_arrayController.arrangedObjects enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
BOOL sel = [_arrayController.selectedObjects containsObject:obj];
if([[obj objectForKey:@"selectedInView"] boolValue] != sel)[obj setValue:[NSNumber numberWithBool:sel] forKey:@"selectedInView"];
}];
}
编辑:结果是另外一种情况:如果选择了单行并且它的复选框是“未单击”,则不会自动触发selectedObjects通知,因此您必须在计时器上运行一个函数来实现新选择