ポップアップメニューには、Aqua の枠が付いているけど、たまにそれがないやつがあるじゃない?下の図の右側のようなやつだ。

これを作るにはどうするか?setBordered: を使うんだ。
Application Kit/NSCell.h
- (void)setBordered:(BOOL)flag;
NSCell の API なんで注意。これを、NSPopUpButtonCell に対して呼んでやるんだ。
(sample)
NSPopUpButton* popUp;
[[popUp cell] setBordered:NO];
これで枠が消えるよ。
Cocoa バインディングを利用して、エンコーディングを選択するためのポップアップメニューを作ってみよう。このポップアップメニューは、選択できるエンコーディングの一覧を表示するんだ。で、エンコーディングを選択すると、その値が Cocoa バインディングを利用して、他のオブジェクトに通知される。また、他のオブジェクトからのエンコーディングの変更通知を受け取ると、メニューの選択項目が変わるんだ。
こういうメニューを実装するには、2 つの機能が必要になる。1 つは、エンコーディングのメニューを作るもの。もう 1 つは、選択されたエンコーディングの値を保持するモデルクラスを提供することだ。今回は、この 2 つの機能を 1 つの Encoding というクラスで実装しよう。
Encoding クラスの説明の前に、ポップアップメニューをどうバインディングするのかを考えよう。ポップアップメニューである NSPopUpButton は、Interface Builder で調べてみると、選択された項目に対するバインディングは、selectedIndex、selectedObject、selectedTag、selectedValue といったものがあることが分かる。このメニューでは、ポップアップメニューで選択された項目である NSMenuItem と、エンコーディングを表す NSStringEncoding を関連づける必要がある。NSStringEncoding は、実際のところ unsigned 型なので、ここでは NSMenuItem のタグを使うことにしよう。だから、バインディングする項目は、selectedTag になる。
Encoding クラスは、unsigned 型の encodingValue というプロパティを提供することにしよう。そして、ポップアップメニューは、selectedTag を encodingValue にバインディングする。この設定は、次の図のようになるんだ。

次に、エンコーディングのメニューの作成を考えよう。これはバインディングを使うと、NSString でしか設定できなかったり何かと面倒くさいので、手で作ることにする。Encoding クラスには、ポップアップメニューへの参照を持たせる。そして、awakeFromNib メソッドの中で、メニューを作るんだ。たとえば、次のようにする。
EncodingsMenu/Encodings.m
// メニューに表示するエンコーディングです
static CFStringEncodings _cfEncodings[] = {
kCFStringEncodingShiftJIS, // Japanese (Shift JIS)
kCFStringEncodingISO_2022_JP, // Japanese (ISO 2022-JP)
kCFStringEncodingEUC_JP, // Japanese (EUC)
kCFStringEncodingShiftJIS_X0213_00, // Japanese (Shift JIS X0213)
kCFStringEncodingInvalidId,
kCFStringEncodingUTF8, // Unicode (UTF-8)
kCFStringEncodingInvalidId,
kCFStringEncodingISOLatin1, // Western (ISO Latin 1)
kCFStringEncodingMacRoman, // Western (Mac OS Roman)
};
@implementation Encodings
- (void)awakeFromNib
{
// NSMenuのインスタンスを作ります
NSMenu* encodingMenu;
encodingMenu = [[[NSMenu alloc]
initWithTitle:@"encoding"] autorelease];
int i;
for (i = 0; i < sizeof(_cfEncodings) / sizeof(CFStringEncodings); i++) {
id <NSMenuItem> menuItem;
if (_cfEncodings[i] == kCFStringEncodingInvalidId) {
// セパレータアイテムを作ります
menuItem = [NSMenuItem separatorItem];
}
else {
// CFStringEncodingをNSStringEncodingに変換します
NSStringEncoding encoding;
encoding = CFStringConvertEncodingToNSStringEncoding(
_cfEncodings[i]);
// エンコーディングの名前を取得します
NSString* encodingName;
encodingName = [NSString
localizedNameOfStringEncoding:encoding];
// NSMenuItemのインスタンスを作ります
// Actionには_popupItemAction:を指定します
menuItem = [[[NSMenuItem alloc]
initWithTitle:encodingName
action:@selector(_popUpItemAction:)
keyEquivalent:@""] autorelease];
// タグとして、encodingを設定します
[menuItem setTag:encoding];
}
// メニューにアイテムを追加します
[encodingMenu addItem:menuItem];
}
// ポップアップメニューに設定します
[popupButton setMenu:encodingMenu];
}
ここでは、表示するエンコーディングを、あらかじめ static の配列として定義しておく。そして、NSMenu のインスタンスを作り、NSMenuItem を追加していくんだ。NSMenuItem のインスタンスを作るときのポイントは、アクションに _popupItemAction: を設定することだ。これを設定すると、アクションが NSPopUpButtonCell に受け取られて、バインディングの処理が行われるようだ。
こうすると、エンコーディングメニューが実現できる。メニューを選択すると、バインディングを使って、モデルクラスである Encoding の encodingValue プロパティが設定される。
サンプルとして、encodingValue の値を、IANA 名に変換してテキストフィールドに設定するアプリケーションを作ってみた。この変換は、NSValueTransformer のサブクラスを作って実装してみた。メニューからエンコーディングを選択すると、その IANA 名がテキストフィールドに設定される。また、テキストフィールドに適切な IANA エンコーディング名を入れると、ポップアップメニューの選択が変更されるんだ。

ソースコードはこちらから。
■サンプルダウンロード:
EncodingsMenu.tar.gz