1

私はNSArrayControllerのarrangedObjectsのさまざまなkeyPathにバインドされた列を持つNSTableViewの古典的なセットアップと、辞書の配列にバインドされたNSArrayControllerを持っています。

このセットアップでは、テーブルビューで 1 つまたは複数の行を選択すると、自動的に機能します。テーブル ビューと配列コントローラーは連携して動作し、NSArrayController にクエリを実行して、選択したオブジェクトのリストを取得するのは簡単です。

テーブル列の 1 つに、チェックボックスの種類である NSButtonCells が含まれています。Cocoa Bindings を使用して、各行のチェックボックスをその行の選択状態にバインドする方法はありますか? 各行を表す NSDictionary に別の値を追加できることはわかっていますが、そうすると NSArrayController で既に利用可能な選択情報が複製されます。

それが必要な場合は、実装の簡単なスケッチもいただければ幸いです。

ありがとう

4

1 に答える 1

1

したがって、これに対する答えは、気弱な人向けではありません。その理由は、NSTableView に自然にやりたくないことをさせようとしているからです。

cocoa のネイティブ NSTableView 複数選択動作を使用することから始めます。

次に、チェックボックスの列を追加します。この行では、ルールが異なります: - チェックボックスをクリックすると、その行のみの選択状態が切り替わります

チェックボックスへのクリックをキャプチャして自分で処理できれば、これは簡単です。今はできますが、問題は、それらを処理した後も NSTableView に転送され、通常の方法で選択が変更されることです。[注: この転送を回避する方法があるかもしれません - 知っている場合はお知らせください]

したがって、これを (最終的に) 達成する方法は次のとおりです。 - 基になるオブジェクト配列の各オブジェクトに「selectedInView」フィールドを追加します。keyPath: "selectedObjects" の関連付けられた NSArrayController にオブザーバーを追加します。選択が変更されたら、各オブジェクトに応じて 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 のチェックボックスがクリックされました。結果: 行 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];
      }
    }
  }
}

テーブルビューが選択処理を行った後、選択をあるべきものに設定したいと思います。実際に選択を行う前に、テーブルビューの選択を変更できる有望な機能があります。次のように変更できます。

- (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 デリゲート関数が呼び出される順序に問題があります。明らかに、一時配列をセットアップする最初の関数が、情報を使用する2番目の関数の前に呼び出される必要があります。

何らかの理由で、チェックボックスを選択解除すると、これが機能することがわかりますが、チェックボックスを選択すると、反対のことが起こります。したがって、この場合、上記の 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 に答える