NSTableView

NSTableView

Application Kit -NSTableView

NSTableView に値を設定する

Keywords: numberOfRowsInTableView, objectValueForTableColumn

テーブルに値を設定するにはどうするか?dataSource のメソッドをオーバーライドするんだ。かならず実装しなきゃいけないのは、以下の 2 つ。

Application Kit/NSTableView.h

- (int)numberOfRowsInTableView:(NSTableView *)aTableView;
- (id)tableView:(NSTableView *)aTableView
   objectValueForTableColumn:(NSTableColumn *)aTableColumn
   row:(int)rowIndex;

最初の numberOfRowsInTableView: は、行がいくつあるのかを指定するんだ。2 つなら 2 を返せばいい。当然か。

次の tableView:objectValueForTableColumn:row: は、テーブルに入るオブジェクトを返すんだ。そのオブジェクトはテーブルのどこに入るのか?このメソッドの引き数で指定されているんだ。行は rowIndex で、列は aTableColumn で指定されている。うん?ちょっと待った。行は int 型だから分かるよね。じゃ、列は?いったい何列目なんだ。列は NSTableColumn で指定されるんだけど、こいつには identifier っていう便利なものがある。

まず Interface Builder に戻って、NSTableColumn に identifer を指定するんだ。テーブルのヘッダのところをダブルクリックすると、NSTableColumn が Inspector に現れる。それの identifer に、適当な文字列を指定するんだ。

そうやって準備した後、このメソッドで、NSTableColumn の identifier メソッドを使う。

Application Kit/NSTableColumn.h

- (id)identifier;

これで比較すれば、テーブルのどのセルを指定しているのかがわかるぜ。

じゃあ、サンプルだ。


TableController.m (Sample)

- (int)numberOfRowsInTableView:(NSTableView *)aTableView
{
 return 5;
}

- (id)tableView:(NSTableView *)aTableView
   objectValueForTableColumn:(NSTableColumn *)aTableColumn
   row:(int)rowIndex
{
 // 'on' の列
 if([[aTableColumn identifier] isEqualToString:@"no"]) {
   return [NSNumber numberWithInt:rowIndex];
 }
 // 'no' の列
 else if([[aTableColumn identifier] isEqualToString:@"name"]) {
   switch(rowIndex) {
     case 0: return @"A Perfect Day";
     case 1: return @"Uncle Wiggily";
     case 2: return @"Just Before the War";
     case 3: return @"The Laughing";
     case 4: return @"Dinghy";
   }
 }

 // ここには来ないはず
 return nil;
}

このサンプルでは、テーブルに 2 つの列があるんだ。それぞれに "no" と "name" という identifer を指定しておく。

上で示した 2 つのメソッドが実装されているんだ。まず、numberOfRowsInTableView: では、行の数を返す。ここでは、とりあえず、決め打ちで 5 を返してる。

そして、tableView:objectValueForTableColumn:row: では、NSTableColumn の identifier を調べて、返すオブジェクトを決定している。まず、"no" のときは、rowIndex から NSNumber オブジェクトを作って返す。行番号になるわけだ。次に "name" のときは、用意してある文字列を返すんだ。





Application Kit - NSTableView

NSTableView に NSButton を入れる

Keywords: setDataCell

なるほど。上で、tableView:objectValueForTableColumn: で、オブジェクトを返せば、そこに値が入るのは分かった。これは、テーブルのそれぞれのマスに、テキストを表示する NSCell がある、っていうことなんだ。じゃ、たとえば、そのテキストのセルの代わりに、ボタンの Cell、つまり、NSButtonCell を入れるにはどうしたらいいのか?それには、NSTableColumn の setDataCell: メソッドを使う。

Application Kit/NSTableColumn.h

- (void)setDataCell:(NSCell *)aCell;

これを使うと、その列に表示するセルを設定することができる。NSButtonCell を指定すれば、ボタンが表示されるってわけだ。

さっそく、サンプルを。

TableController.m (Sample)

- (void)awakeFromNib
{
 NSTableColumn*  tableColumn;
 NSButtonCell*   buttonCell;

 tableColumn = [_tableView tableColumnWithIdentifier:@"on"];
 buttonCell = [[NSButtonCell alloc] init];
 [buttonCell setButtonType:NSSwitchButton];
 [buttonCell setTitle:@""];
 [tableColumn setDataCell:buttonCell];

 [buttonCell release];
}

- (int)numberOfRowsInTableView:(NSTableView *)aTableView
{
 return 5;
}

- (id)tableView:(NSTableView *)aTableView
objectValueForTableColumn:(NSTableColumn *)aTableColumn
row:(int)rowIndex
{
 // 'on' の列
 if([[aTableColumn identifier] isEqualToString:@"on"]) {
  return [NSNumber numberWithInt:(rowIndex % 2)];
 }
 // 'no' の列
 else if([[aTableColumn identifier] isEqualToString:@"no"]) {
  return [NSNumber numberWithInt:rowIndex];
 }
 // 'name' の列
 else if([[aTableColumn identifier] isEqualToString:@"name"]) {
  switch(rowIndex) {
    case 0: return @"A Perfect Day";
    case 1: return @"Uncle Wiggily";
    case 2: return @"Just Before the War";
    case 3: return @"The Laughing";
    case 4: return @"Dinghy";
  }
 }

 // ここには来ないはず
 return nil;
}

上のソースと似ているけど、いくつか違うところがある。まず、インスタンス変数に NSTableView へのポインタを持っているんだ。これは、Interface Builder で設定しておく。

次に、awakeFromNib が追加されている。この中で、ボタンを設定しているんだ。まず _tableView から NSTableColumn を取得する。NSButtonCell のインスタンスを作成する。そして、NSTableColumn に setDataCell: で設定するんだ。

ボタンが選択されているか、いないかは、:objectValueForTableColumn: で設定されるよ。




Application Kit - NSTableView

NSTableView の中のボタンを機能させる

Keywords: setObjectValue

上の例では、ボタンとして、NSSwitchButton を設定してみた。このボタンは、トグル的に動くはずなんだけど、上のコードだと、トグルしないことが分かるはずだ。だめじゃん。と、いうわけで、これを動作するようにしてみよう。

作戦は、こうだ。ボタンを押すと、ボタンの持っている値が変わる。すると、NSButtonCell は NSTableView の tableView:setObjectValue:: を呼び出すんだ。

Application Kit/NSTableView.h

- (void)tableView:(NSTableView *)tableView
        setObjectValue:(id)object
        forTableColumn:(NSTableColumn *)tableColumn
        row:(int)rowIndex;

このメソッドは、テーブルの中の値が変更されたときに呼び出されるんだ。ここから、ボタンの値を取り出して、保存しておく。そして、tableView:objectValueForTableColumn: で、その値を返してやればいいんだ。

じゃ、コードだ。このコードは「NSTableView に NSButton を入れる」に追加をしたものだ。

TableController.m (Sample)

- (void)awakeFromNib
{
 ...

 // _on[] を初期化する
 memset(_on, 0, sizeof(_on));
}

- (id)tableView:(NSTableView *)aTableView
   objectValueForTableColumn:(NSTableColumn *)aTableColumn
   row:(int)rowIndex
{
 // 'on' の列
 if([[aTableColumn identifier] isEqualToString:@"on"]) {
    return [NSNumber numberWithInt:_on[rowIndex]];
 }

 ...
}

- (void)tableView:(NSTableView *)tableView
    setObjectValue:(id)object
    forTableColumn:(NSTableColumn *)tableColumn
    row:(int)rowIndex;
{
 id identifier;

 identifier = [tableColumn identifier];
 if([identifier isEqualToString:@"on"]) {
    _on[rowIndex] = [object intValue];
 }
}

まず、インスタンス変数 _on[] を追加している。これは、ボタンの状態を保存しておくためのものだ。もちろん、NSMutableArray に変更してもいいよ。これを awakeFromNib で初期化している。

そして、まずボタンから値を取り出す。それが、最後の関数、tableView:setObjectValue: だ。このメソッドの引き数 object が、新しく設定される値だ。これを int 型にして、_on[] に入れておく。

順番が前後するけど、メソッド tableView:objectValueForTableColumn:row: で、その値を返してやる。

これで、ボタンが機能するようになるよ!ボタンの現在の値は、_on[] からとりだすことになる。





Application Kit - NSTableView

NSTableView にアイコンつきテキストを埋め込む

Keywords: IconedCell, dataCell

NSTableView に画像付きテキストを埋め込んでみよう。ほら、Finder にあるような、アイコンがあって、ファイル名が書いてあるようなやつ。あれを作ってみよう。

それには、画像とテキストを表示できる NSCell を使う。これの作り方はこちらを見てくれ。ここでは、この IconedCell を使うことを前提に話を進めるよ。

まず、初期化のところで、NSTableColumn の dataCell として、この IconedCell を設定する。そして、dataSource の objectValueForTableColumn:: のところで、画像を設定して、テキストを objectValue として返すんだ。

今回のサンプルコードは 2 つのソースを使う。1 つは、NSTableView を継承したクラス。もう 1 つは、NSTableView の dataSource となるクラスだ。あ、もちろん、IconedCell のコードも必要だよ。

IconedTableView.m (Sample)

- (void)awakeFromNib
{
 NSTableColumn* tableColumn;

 // IconedCell を 'name' の列に設定する
 IconedCell* iconedCell;
 iconedCell = [[[IconedCell alloc] init] autorelease]; 

 tableColumn = [self tableColumnWithIdentifier:@"name"];
 [tableColumn setDataCell:iconedCell];
}

IconedTableDataSource.m (Sample)

- (id)tableView:(NSTableView *)tableView
   objectValueForTableColumn:(NSTableColumn *)tableColumn
   row:(int)rowIndex
{
 id identifier;
 NSString* path = @"/Application/Sherlock.app";

 identifier = [tableColumn identifier];
 if([identifier isEqualToString:@"name"]) {
   NSCell* cell;
   cell = [tableColumn dataCell];
   [cell setImage:[[NSWorkspace sharedWorkspace]
       iconForFile:path]];

   return [path lastPathComponent];
 }

 // ここには来ないはず
 return @"";
}

まず、NSTableView のクラスでは、awakeFromNib をオーバーライドして、ここで初期化を行っている。ここでは、"name" っていう identifier のついた NSTableColumn を取り出すんだ。そして、IconedCell を dataCell として、設定する。

次に、dataSource の objectValueForTableColumn:: では、IconedCell に値を設定している。ここでは、画像として、Sherlock のアイコンを、テキストして、"Sherlock.app" を設定しているよ。まず、identifier を調べる。"name" だったら、dataCell を取り出す。これが、実は IconedCell になっているんだ。そして、setImage: で画像を設定する。最後に、"Sherlock.app" をテキストとして返す。

あとは、IconedCell が描画をしてくれるんだ。





Application Kit - NSTableView

テーブルの構造

Keywords: structure

Interface Builder を使ってテーブルを作ると、もう、一式揃っているよね。スクロールバーが付いていて、ヘッダが付いてて、テーブルもある。これは、いったいどれだけの View から構成されているのか?

一番トップにあるのは NSScrollView だ。NSTableView じゃないので注意。そこから View の階層構造を探ってみると、

・NSScrollView

・NSClipView

・NSTableView

・NSScroller

・NSScroller

・NSClipView

・NSTableHeaderView

・_NSCornerView

という構造になってる。NSScrollView の中に、NSTableView と NSTableHeaderView があるんだ。_NSCornerView っていうのは、右上の部分ね。




Application Kit - NSTableView

テーブルのセルに属性を設定する

Keywords: willDisplayCell

NSTableView を使っていると、セルに動的に属性を設定したいときがあるでしょ。たとえば、テキストの内容によってフォントを変えたいとか、バックグラウンドの色を変えたいとか。それには NSTableView の delegate のメソッドである tableView:willDisplayCell:forTableColumn:row: を使おう。

Application Kit/NSTableView.h

- (void)tableView:(NSTableView *)tableView
   willDisplayCell:(id)cell
   forTableColumn:(NSTableColumn *)tableColumn
   row:(int)row;

このメソッドはセルを表示する前に呼び出される。引数の cell から値を取り出して、適当な属性を設定してやればいい。

ということで、サンプル。このサンプルでは、テーブルの列ごとに色をつけてみた。偶数の列は白で、奇数の列は淡いグレーだ。

stripe.jpg

このサンプルの tableView:willDisplayCell:forTableColumn:row: の実装はこうなっている。

(sample)

static NSColor* _whiteColor = nil;
static NSColor* _stripeColor = nil;

- (void)tableView:(NSTableView*)tableView
        willDisplayCell:(id)cell
        forTableColumn:(NSTableColumn*)tableColumn
        row:(int)row
{
 if (!_whiteColor) {
  _whiteColor = [NSColor whiteColor];
  _stripeColor =
    [[NSColor colorWithCalibratedWhite:0.95 alpha:1.0]
          retain];
 }

 // セルの背景色を設定する
 [cell setDrawsBackground:YES];
 if (row % 2 == 1) {
  [cell setBackgroundColor:_stripeColor];
 }
 else {
  [cell setBackgroundColor:_whiteColor];
 }
}

引数として渡される row の値から偶数列か奇数列かを判断して、cell に色を付けているんだ。

■サンプルダウンロード:
StringTokenizer.tar



Application Kit - NSTableView

テーブルからドラッグ・アンド・ドロップでファイルを保存する

Keywords: namesOfPromisedFilesDroppedAtDestination, setDraggingSourceOperationMask

NSTableViewに、ファイル一覧とかを表示しているとしよう。そのファイルをドラッグして、Finderにドロップしたら、そこにファイルを作成したくなるよね。それを実現してみよう。ただし、ここで説明するのは、Mac OS X 10.4以降で使える方法だ。

まず、NSTableViewでドラッグ・アンド・ドロップするには、データソースでtableView:writerRowsWithIndexes:toPasteboard:というメソッドを実装する。この中で、NSPasteboardに対して設定を行うんだ。(10.4より前では、tableView:writeRows:toPasteboard:というメソッドだった。)

ここの設定で、ペーストボードのタイプとして、NSFilesPromisePboardTypeを指定するのがポイント。これは、あとでファイルを作成する(ファイルの作成を約束する)ということを意味する。

このタイプを指定しておくと、ドロップしたときに、テーブルデータソースのtableView:namesOfPromisedFilesDroppedAtDestination:forDraggedRowsWithIndexes:というメソッドが呼ばれる。これが、10.4から登場した。この中で、ファイルを作成して、好きな中身を書き込んでやればいいんだ。

それと、もう一つ。あらかじめNSTableViewのsetDraggingSourceOperationMask:forLocal:メソッドも呼んでおく必要がある。これは、ドラッグできるオペレーションの種類の指定するんだけど、ドロップのターゲットが自分だけか他のアプリケーションにもできるようにするか、も設定できる。ここで、forLocalにNOを指定してやると、他のアプリケーション(含むFinder)へのドラッグの種類を決定できる。これを呼ばないと、Finderへのドラッグはできないので注意。

では、ソースコードを。

ApController.m (sample)

- (void)awakeFromNib
{
 _contents = [[NSArray arrayWithObjects:@"Cocoa", @"Objective-C", @"NSTableView", nil] retain];

 [_tableView setDraggingSourceOperationMask:NSDragOperationAll forLocal:NO];
}

- (BOOL)tableView:(NSTableView*)tableView
    writeRowsWithIndexes:(NSIndexSet*)rowIndexes
    toPasteboard:(NSPasteboard*)pboard
{
 // Declare NSFilesPromisePboardType
 [pboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self];
 [pboard setPropertyList:[NSArray arrayWithObject:@"txt"] forType:NSFilesPromisePboardType];

 return YES;
}

- (NSArray*)tableView:(NSTableView*)tableView
    namesOfPromisedFilesDroppedAtDestination:(NSURL*)dropDestination
    forDraggedRowsWithIndexes:(NSIndexSet*)indexSet
{
 NSMutableArray* fileNames;
 fileNames = [NSMutableArray array];

 unsigned index;
 index = [indexSet firstIndex];
 do {
    // Get content
    NSString* content;
    content = [_contents objectAtIndex:index];

    // Create file path
    NSString* fileName;
    NSString* path;
    fileName = [content stringByAppendingPathExtension:@"txt"];
    path = [[dropDestination path] stringByAppendingPathComponent:fileName];

    // Write data into path
    NSData* data;
    data = [content dataUsingEncoding:NSUTF8StringEncoding];
    [data writeToFile:path atomically:YES];

    [fileNames addObject:fileName];
 } while ((index = [indexSet indexGreaterThanIndex:index]) != NSNotFound);

 return fileNames;
}

中心となるところだけ抜き出した。こんな感じで、Finderへのドラッグを実現できるよ。

■サンプルダウンロード:
TableViewPromisedFile.zip

Application Kit