Cocoa Programming Tips 1001
Application Kit
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.gz
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.gz
Copyright © 2002-2006 HMDT. All rights reserved.