NSCell

NSCell

Application Kit - NSCell

NSCell とは

Keywords: NSCell

Cocoa には、たくさんの GUI のコントローラになる部品があるよね。たとえばボタン(NSButton)、たとえばスライダー(NSSlider)、たとえばテキストフィールド(NSTextField)。こいつらを実際に作るには、ユーザからの入力を受け付けて、画面にコントローラを描いて、適当なオブジェクトにメッセージを送る、っていう処理が必要になるよな。このうち、画面に描く、っていう部分を担当するのが NSCell クラスだ。残りの部分(ユーザからの入力を受け付ける、と、メッセージを送る)は、NSControl っていうクラスが担当する。別の言い方をすると、NSCell がコントローラの見栄えを、NSControl がコントローラの動作を担当すると言えるんだ。

さて、NSCell の成り立ちは上のような感じだけど、その役割には 2 つの側面がある。まず、軽量な描画オブジェクトっていう役割。普通、画面になにか描くときは NSView を使うじゃない。だけど、NSView って重いでしょ?ここでいう重いって意味は、インスタンスのメモリサイズがでかい、って意味と、responder chain に入れられたり View の親子関係があったりして関係が複雑、っていうこと。それに対して、NSCell は NSObject から直接継承しているし、インスタンス変数もそんなにないから、軽量なんだ。

もう 1 つは、コントローラの値を保持するオブジェクトという役割。たとえばラジオボタンなら、On か Off、または On か Off か Mixed っていう値をとるでしょ。それを保持しておくのが NSButtonCell なんだ。




Application Kit - NSCell

NSCell のサブクラス

Keywords: NSCell subclass

Cocoa で実装されている NSCell のサブクラスを抜き出してみた。自前の NSCell を作るときは、これを見てどこから継承するか考えるといいかも。

・NSCell

・NSActionCell

・NSButtonCell

・NSMenuItemCell

・NSPopUpButtonCell

・NSFormCell

・NSSliderCell

・NSStepperCell

・NSTextFieldCell

・NSComboBoxCell

・NSSecureTextFieldCell

・NSTableHeaderCell

・NSBrowserCell

・NSImageCell

・NSTextAttachmentCell

Application Kit - NSCell

NSCell が保持する値

Keywords: object value, cell states, image, represented object

セルには、値を持たせておくことができるんだ。それが、セルの役割の 1 つだからね。NSCell では、4 通りの値を持たせておくことができる。

・object value

・cell state

・image

・represented object

順に見ていこう。

object value

object value が、メインになるものだ。基本的に、セルはこの値を表示することになる。たとえば、NSFormCell は、NSString 型の object value を表示するんだ。ここにアクセスするには、以下のメソッドを使う。

Application Kit/NSCell.h

- (id <NSCopying>)objectValue;
- (void)setObjectValue:(id <NSCopying>)obj;
- (NSString *)stringValue;
- (void)setStringValue:(NSString *)aString;
- (int)intValue;
- (void)setIntValue:(int)anInt;
- (float)floatValue;
- (void)setFloatValue:(float)aFloat;
- (double)doubleValue;
- (void)setDoubleValue:(double)aDouble;

NSString や int や double やら好きな型でアクセスできる。でも、NSCell は一度に 1 つしか保持できないから気をつけてくれ。

cell state

次に cell state は、ラジオボタンなんかで使われる。state には、on、off の 2 つの state を持つものと、on、off、mixed の 3 つの state を持つものがある。アクセスするには、以下のメソッドを使う。

Application Kit/NSCell.h

- (int)state;
- (void)setState:(int)value;
- (void)setAllowsMixedState:(BOOL)flag;
- (BOOL)allowsMixedState;
- (int)nextState;
- (void)setNextState;

image

次は image。NSCell は image を、別に、保持しておくことができる。アクセスするには、以下のメソッドを使う。

Application Kit/NSCell.h

- (NSImage *)image;
- (void)setImage:(NSImage *)image;

ちょっと混乱するけど、これを使って保持する image は、object value とは別のものになるんだ。ヘッダを見ると、こんな感じになってる。

Application Kit/NSCell.h

@interface NSCell : NSObject <NSCopying, NSCoding>
{
 /*All instance variables are private*/
 id _contents;
 _CFlags _cFlags;
@private
 // This variable should *only* be accessed
 // through the following methods:
 // setImage:, image, setFont:, and font
 id _support;
 ...

}

つまり、NSCell には _contents と _support というフィールドがある。_contents の方が object value を保持しておくもので、_support の方は image か font を保持しておくものだ。だから、1 つのセルに、たとえば、テキストと画像の両方を持たせておくことができる。ただ、疑問なのは、image と font を両方同時に持つことはできないのか?だとしたら、ちょっと不便。

represented object

最後、represented object。これは、ドキュメントによると、NSCell が表すオブジェクトのこと。アクセスするには、以下のメソッドを使う。

Application Kit/NSCell.h

- (id)representedObject;
- (void)setRepresentedObject:(id)anObject;

この値は、セルの挙動とは関係ないので、デベロッパが好きに使えるらしい。気をつけてほしいのは、object value とごっちゃにしないでくれ、っていうことだ。object value は、セルが、自分を表示するために使われる値。それに対して represented object は、セルとは関係なく、情報の保存場所として使える値なんだ。




Application Kit - NSCell

セルの中身を描く

Keywords: drawInteriorWithFrame

セルの中を自分の好きなように描くには、NSCell のサブクラスを作って、drawInteriorWithFrame:inView: をオーバーライドするんだ。

Application Kit/NSCell.h

- (void)drawInteriorWithFrame:(NSRect)cellFrame
      inView:(NSView *)controlView;
- (void)drawWithFrame:(NSRect)cellFrame
      inView:(NSView *)controlView;

セルの中身を描くメソッドには 2 種類あるんだ。drawWithFrame:inView: は枠を描くためのメソッド、drawInteriorWithFrame:inView: は枠の中身を描くためのメソッドだ。drawWithFrame:inView: で枠を描いたら、drawInteriorWithFrame:inView: を呼び出さなくてはいけないんだ。通常は drawInteriorWithFrame:inView: をオーバーライドするんだ。

実際の使用例は、次を見てくれ。

■関連リンク:
セルに画像とテキストを描く





Application Kit - NSCell

セルに画像とテキストを描く

Keywords: drawInteriorWithFrame, IconedCell

NSCell はデフォルトでテキストや画像を表示することができるんだ。じゃあ、テキストと画像を同時に表示したいときは?たとえば、ファイル名とアイコンを表示させたいときとかだ。こういうときは、NSCell を継承した、サブクラスを自分で作ることになる。さっそくやってみよう。

画像とテキストを取得するには、いくつか方法があるんだけど、ここでは、setImage: と setStringValue: を使う。

Application Kit/NSCell.h

- (NSImage *)image;
- (void)setImage:(NSImage *)image;
- (NSString *)stringValue;
- (void)setStringValue:(NSString *)aString;

これらを使うと、NSCell のインスタンス変数にある、画像とテキストにアクセスできる。したがって、最初に、どこかで setImage: と setStringValue: を使って、画像とテキストを設定する。その後、drawInteriorWithFrame:inView: の中で、image と stringValue を使って、それらを取り出すんだ。

ではコードだ。

IconOutlineView/IconedCell.m (sample)

- (void)drawInteriorWithFrame:(NSRect)cellFrame
        inView:(NSView*)controlView
{
 NSString* path;
 NSRect pathRect;

 NSImage* iconImage;
 NSSize iconSize;
 NSPoint iconPoint;

 // 画像を描く
 iconImage = [self image];
 iconSize = NSZeroSize;
 iconPoint.x = cellFrame.origin.x;
 iconPoint.y = cellFrame.origin.y;

 if(iconImage) {
  iconSize.width = ICON_SIZE_WIDTH;
  iconSize.height = ICON_SIZE_HEIGHT;
  iconPoint.x += MARGIN_X;

 if([controlView isFlipped]) {
    iconPoint.y += iconSize.height;
 }

 [iconImage setSize:iconSize];
 [iconImage compositeToPoint:iconPoint
    operation:NSCompositeSourceOver];
 }

 // テキストを描く
 path = [self stringValue];
 pathRect.origin.x = cellFrame.origin.x + MARGIN_X;
 if(iconSize.width > 0) {
  pathRect.origin.x += iconSize.width + MARGIN_X;
 }
 pathRect.origin.y = cellFrame.origin.y;
 pathRect.size.width = cellFrame.size.width
    - (pathRect.origin.x - cellFrame.origin.x);
 pathRect.size.height = cellFrame.size.height;

 if(path) {
  [path drawInRect:pathRect withAttributes:nil];
 }
}

まず、画像を描く。image メソッドを使って、画像のインスタンスを取り出して、描く位置を決める。このとき isFlipped を使って、controlView の座標軸を調べているんだ。これは、どうも、NSTableView の中でセルの描画をおこなうと、座標軸がひっくり返る(左上が原点)らしい。でも、NSImage を描画するときは左下が原点になるので、ここで合わせてやる。そして、compositeToPoint:operation: を使って、描くんだ。

次は、同じようにテキストを描く。これでオッケーだ。

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




Application Kit - NSCell

セルにテキスト属性を設定する

Keywords: drawInteriorWithFrame, IconedCell

NSCell は、デフォルトでは、NSString を objectValue として設定すると、その文字をセルに書くんだけど、それにフォントとか、色とかの属性を設定したいときは?はじめは objectValue に NSAttributedString を設定すればいいのか?と、思ったけど、違う。setAttributedStringValue: を使うんだ。

NSCell.h
- (void)setAttributedStringValue:(NSAttributedString *)obj;

これを、表示するセルに対して呼び出してやればいい。たとえば、NSTableView だったら、tableView:willDisplayCell:forTableColumn:row: でやってやるのが簡単だ。

AttributedCell/TableController.m

- (void)tableView:(NSTableView *)tableView
    willDisplayCell:(id)cell
    forTableColumn:(NSTableColumn *)tableColumn
    row:(int)row
{
 NSMutableAttributedString* attrStr;
 NSRange range = {0, 0};

 attrStr = [[NSMutableAttributedString alloc]
      initWithString:[cell objectValue]];
 [attrStr autorelease];
 range.length = [attrStr length];
 [attrStr addAttribute:NSFontAttributeName
      value:_font range:range];

 [cell setAttributedStringValue:attrStr];
}

これで、セルにテキスト属性を設定できる。表示する内容によって属性を変えたいときは、cell から objectValue を取り出して、それによって判断するんだね。

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


Application Kit