1

我有一个 NSTableView 的经典设置,其中列绑定到 NSArrayController 的排列对象的各种 keyPaths,并且 NSArrayController 绑定到字典数组。

使用此设置,在 tableview 中选择一行或多行会自动工作。表格视图和数组控制器一起工作,很容易查询 NSArrayController 以获取选定对象的列表。

我的表格列之一包含 NSButtonCells,复选框类型。有没有办法使用 Cocoa Bindings 将每行中的复选框绑定到该行的选择状态?我知道我可以向代表每一行的 NSDictionary 添加另一个值,但这会复制 NSArrayController 中已经可用的选择信息。

如果有必要这样做,还将感谢您的实施的快速草图。

谢谢

4

1 回答 1

1

所以,这个问题的答案不适合胆小的人。原因是你试图让 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 通知,因此您必须在计时器上运行一个函数来实现新的选择。

于 2013-09-05T16:33:15.397 回答