使用CheckBoxes管理NSTableView中的选择

时间:2013-08-29 18:31:49

标签: checkbox nstableview cocoa-bindings nsarraycontroller

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

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

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

如果有必要这样做,也希望快速草拟您的实施。

由于

1 个答案:

答案 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通知,因此您必须在计时器上运行一个函数来实现新选择