所以,这个问题的答案不适合胆小的人。原因是你试图让 NSTableView 做一些它自然不想做的事情。
从使用 cocoa 的原生 NSTableView 多选行为开始: - 单击一行选择它并取消选择其他行 - 按住控件并单击一列仅切换该行的选择状态
现在,添加一列复选框。对于这一行,规则不同: - 单击复选框仅切换该行的选择状态
如果我们可以捕获对复选框的点击并自己处理它们,这将很容易。现在我们可以了,但问题是在我们处理它们之后,它们仍然被转发到 NSTableView,以通常的方式改变选择。[注意:可能有一些方法可以避免这种转发 - 如果你知道,请告诉我]
因此,您可以(最终)做到这一点: - 为基础对象数组中的每个对象添加一个“selectedInView”字段。为 keyPath:“selectedObjects”添加一个观察者到关联的 NSArrayController。When the selection changes, set the selectedInView field accordingly for each object. 像这样的东西:
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 行的复选框。结果:选择了第 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 通知,因此您必须在计时器上运行一个函数来实现新的选择。