Checkbox 使用复选框管理NSTableView中的选择

Checkbox 使用复选框管理NSTableView中的选择,checkbox,nstableview,cocoa-bindings,nsarraycontroller,Checkbox,Nstableview,Cocoa Bindings,Nsarraycontroller,我有一个NSTableView的经典设置,其中列绑定到NSArrayController的arrangedObjects的各种关键路径,NSArrayController绑定到一组字典 使用此设置,在tableview中选择一行或多行将自动工作。表视图和数组控制器一起工作,很容易查询NSArrayController以获得所选对象的列表 我的一个表列包含NSButtonCells,复选框类型。有没有办法使用Cocoa绑定将每行中的复选框绑定到该行的选择状态?我知道我可以向NSDictionary

我有一个NSTableView的经典设置,其中列绑定到NSArrayController的arrangedObjects的各种关键路径,NSArrayController绑定到一组字典

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

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

如果有必要这样做的话,我也希望您能简要介绍一下您的实现


谢谢

所以,这个问题的答案不适合胆小的人。原因是您试图让NSTableView执行它自然不想执行的操作

首先使用cocoa的原生NSTableView多选行为: -单击一行将选中该行并取消选择其他行 -按住控件并单击列仅切换该行的选择状态

现在,添加一列复选框。对于此行,规则不同: -单击复选框仅切换该行的选择状态

如果我们能够捕获复选框的点击并自己处理它们,这将很容易。现在我们可以了,但问题是在我们处理它们之后,它们仍然会被转发到NSTableView,以通常的方式更改选择。[注意:可能有一些方法可以避免此转发-如果您知道,请让我知道]

下面是您如何(最终)完成此任务的: -向基础对象数组中的每个对象添加“SelectedView”字段。将观察者添加到键路径的关联NSArrayController:“SelectedObject”。当选择更改时,为每个对象相应地设置SelectedView字段。大概是这样的:

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 observer添加更多代码:

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"];
  }];
}

编辑:结果发现还有一种情况:如果选中了一行且其复选框为“未选中”,则不会自动触发SelectedObject通知,因此您必须在计时器上运行一个函数来实现新的选择。

因此,这一问题的答案不适用于心脏虚弱的人。原因是您试图让NSTableView执行它自然不想执行的操作

首先使用cocoa的原生NSTableView多选行为: -单击一行将选中该行并取消选择其他行 -按住控件并单击列仅切换该行的选择状态

现在,添加一列复选框。对于此行,规则不同: -单击复选框仅切换该行的选择状态

如果我们能够捕获复选框的点击并自己处理它们,这将很容易。现在我们可以了,但问题是在我们处理它们之后,它们仍然会被转发到NSTableView,以通常的方式更改选择。[注意:可能有一些方法可以避免此转发-如果您知道,请让我知道]

下面是您如何(最终)完成此任务的: -向基础对象数组中的每个对象添加“SelectedView”字段。将观察者添加到键路径的关联NSArrayController:“SelectedObject”。当选择更改时,为每个对象相应地设置SelectedView字段。大概是这样的:

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委托函数的顺序有问题。显然,我们需要在第二个函数-w之前调用第一个函数(在这里设置临时数组)