|
- Application Kit-
NSApplication
Application Kit - NSApplication
実行中のインスタンスを取得する
Keywords: sharedApplication, NSApp
NSApplication ってのは、その名の通り、アプリケーションの中心となるクラスだ。実行中のアプリケーションは NSApplication のインスタンスを 1 つ持つことになるんだ。では、そのインスタンにアクセスするにはどうすればいいのか?方法は 2 つある。1 つは、クラスメソッド sharedApplication を使う方法。
Application Kit/NSApplication.h
+ (NSApplication *)sharedApplication;
これを使うと、インスタンスが返ってくる。もう 1 つは、グローバル変数 NSApp を使う方法。この変数は、NSApplication.h で宣言されている。
Application Kit/NSApplication.h
APPKIT_EXTERN id NSApp;
ただ単に NSApplication のインスタンスへの参照が欲しいならば、NSApp を使うのが簡単だよ。
Application Kit - NSApplication
NSApplicationMain の内部
Keywords: NSApplicationMain, sharedApplication, NSApp
ProjectBuilder を使って、Cocoa アプリケーションを作ると、main.m っていうファイルができて、次のようなコードが書かれるよね。
main.m (created by Project Builder)
int main(int argc, const char *argv[]) {
return NSApplicationMain(argc, argv)
}
つまり、NSApplicationMain() っていう関数を呼び出しているんだ。これは何?NSApplication のドキュメントによれば、この関数は次のような関数と同値らしい(これそのものではない)。
Pseudo main (sample)
void NSApplicationMain(int argc, char *argv[]) {
[NSApplication sharedApplication];
[NSBundle loadNibNamed:@"myMain" owner:app];
[NSApp run];
}
まず、sharedApplication メソッドを呼ぶ。このメソッドは、初めて呼ばれたときは、NSApplication のインスタンスを作るんだ。そして、グローバル NSApp に代入して、その値を返す。2 回目以降呼ばれたときは、ただ単に前に作ったインスタンスを返す。次に nib をロードする。最後に run メソッドを呼ぶ。run メソッドではイベントループが回っていて、Window Server からイベントを受け取ったりするわけだ。
Application Kit - NSApplication
マウスドラッグをトラッキングする
Keywords: nextEventMatchingMask
マウスのクリックやドラッグは、NSResponder のメソッドである、mouseDown: や mouseDragged: でつかまえることができる。でも、同じコンテキストの中でドラッグを扱えたら便利じゃない?つまり、mouseDown: が呼び出されたら、そのメソッドの内部でマウスドラッグをトラッキングしてやる、というケースだ。それには nextEventMatchingMask:untilData:inMode:dequeue: を使えばできるんだ。
Application Kit/NSApplication.h
- (NSEvent*)nextEventMatchingMask:(unsigned int)mask
untilData:(NSData*)expiration
inMode:(NSString*)mode
dequeue:(BOOL)deqFlag;
このメソッドを呼ぶと、望みのイベントが来るまで、アプリケーションの実行をそこでブロックさせることができるんだ。だから、永遠に来ないイベントを待ってたりすると、そこで固まることになるので、注意。マウスドラッグをトラッキングするには、マウスドラッグイベントと、マウスアップイベントを補足すればいい。マウスドラッグだったら、このメソッドを呼び続けて、マウスアップだったら、そこでトラックを終える。次のようなコードになる。
(sample)
- (void)mouseDown:(NSEvent*)event
{
while(1) {
event = [NSApp nextEventMatchingMask:
(NSLeftMouseDraggeMaskt | NSLeftMouseUpMask)
untilDate:[NSDate distantFuture]
inMode:NSEventTrackingRunLoopMode
dequeue:YES];
NSLog("Current mouse point:%@¥n",
NSStringFromPoint([event locationInWindow]);
// Tracking process
if([event type] == NSLeftMouseUp) {
break;
}
}
}
ここでは、最初のマウスプレスを捕らえるために、mouseDown: をオーバーライドしている。そして、その中でループを回している。下手な処理を書くと、この中から抜け出れなくなるので注意。ここで、nextEventMatchingMask:untileDate:inMode:dequeue: を使って、イベントを取得している。mask に NSLeftMouseDraggedMask と NSLeftMouseUpMask を指定してるんだ。expiration には NSDate の distantFuture を指定することにより、永遠に待つ。ここで、マウスドラッグか、マウスアップが起こると、event が返ってくるんだ。そのイベントをもとに、適当な処理を行う。ここでは、とりあえず位置を表示させた。最後に、もしイベントがマウスアップだったら、このループを抜ける。
このメソッド使わなくても、mouseDragged: を使えば、マウスのトラッキングはできる。どちらを使った方がいいのかは、アプリケーション次第。
Application Kit - NSApplication
モーダルダイアログ
Keywords: application-modal dialog, document-modal dialog
Mac OS X では、モーダルダイアログは 2 つに分けられるんだ。1 つはアプリケーションからの警告を表示したりする、アプリケーション・モーダルダイアログ。もう 1 つは、ドキュメントに対するメッセージを出す(セーブすしますか?とか)、ドキュメント・シートダイアログだ。前者は、いままで通りのウィンドウ型のダイアログ。後者は、シートっていう、ウィンドウのタイトルから、巻いてあるのが降りてくるように出てくるやつのことだ。
この 2 つは、両方ともモーダルダイアログなんだけど、実装方法がかなり違う。比べてみよう。
Application Kit - NSApplication
アプリケーション・モーダルダイアログを表示する
Keywords: runModalForWindow
アプリケーション・モーダルダイアログは、Classic のモーダルダイアログと似た感じの、ウィンドウを表示するタイプだ。これを表示するには、runModalForWindow: を使う。
Application Kit/NSApplication.h
- (int)runModalForWindow:(NSWindow*)window;
このメソッドを使うときは、呼び出しは同期的だということに注意してくれ。。どういうことかっていうと、このメソッドを呼ぶとダイアログが表示される。そして、そのダイアログを処理している間は、イベントループはその中で回るんだ。メソッドの呼び出しは、処理中は返ってこないで、ブロックされるんだ。
ここから抜けるには、stopModal か stopModalWithCode: を、そのダイアログ処理の中で呼ぶ。外から止めたい場合には abortModal を使う。
Application Kit/NSApplication.h
- (void)stopModal;
- (void)stopModalWithCode:(int)returnCode;
- (void)abortModal;
これらを呼ぶと、runModalForWindow: のモーダルループから抜け出す。その際の返り値で、どのメソッドが呼ばれたかを判断することができるんだ。stopModal は NSRunStoppedResponse を返す。stopModalWithCode: は、引数として渡された値を返す。そして、abortModal は、NSRunAbortedResponder を返すんだ。
これを利用して、ダイアログで押されたボタンを知るサンプルを紹介しよう。
Dialog/AppDelegate.m (sample)
- (IBAction)showDialog:(id)sender
{
int result;
// Display modal dialog
result = [[NSApplication sharedApplication]
runModalForWindow:_modalDialog];
[_modalDialog orderOut:self];
if(result == DIALOG_CANCEL) {
// Cancel button was pushed
NSLog(@"Dialog is canceled");
return;
}
else if(result == DIALOG_OK) {
// OK button was pushed
NSLog(@"Dialog is accepted");
}
else if(result == NSRunAbortedResponse) {
// Aborted by external timer
NSLog(@"Dialog is aborted");
}
}
- (IBAction)dialogOk:(id)sender
{
// OK button is pushed
[[NSApplication sharedApplication]
stopModalWithCode:DIALOG_OK];
}
- (IBAction)dialogCancel:(id)sender
{
// Cancel button is pushed
[[NSApplication sharedApplication]
stopModalWithCode:DIALOG_CANCEL];
}
上のサンプルコードには、3 つのメソッドがある。
まず showDialog: は、ダイアログを表示するためのメソッドだ。runModalForWindow: を呼び出して、ダイアログを表示している。このダイアログには、OK ボタンと Cancel ボタンがあって、押すと、それぞれ、dialogOk:、dialogCancel: を呼び出すんだ。
それぞれのメソッドの中で、stopModalWithCode: を呼んでいる。OK のときは DIALOG_OK、キャンセルのときは DIALOG_CANCEL を引数として指定するんだ。これが呼ばれると、showDialog: の中の runModalForWindow: が戻ってくる。指定した引数が、返り値として得られるんだ。これにより、それぞれの返り血に応じた処理ができるわけだ。
■サンプルダウンロード:
GraphicFunctions.tar.gz
Application Kit - NSApplication
ドキュメント・モーダルダイアログ(シート)を表示する
Keywords: beginSheet
ドキュメント・モーダルダイアログは、Mac OS X で新しく導入されたダイアログだ。いわゆるシートダイアログってやつね。とにかく見た目が強力だったので、そっちばっかり強調されてるけど、それ以外にもいろいろおもしろいやつだ。
ドキュメント・モーダルダイアログとは、あるドキュメント、つまり、ある一つのウィンドウだけに関係するんダイアログなんだ。その状態をシートはたくみに表している。シートはウィンドウのタイトルバーから、にょろにょろっと出てきて、ウィンドウにくっついている。だから、シートと関連づけられているウィンドウは、とても明確に分かるんだ。さらに、ウィンドウを動かせばシートも動くし、後ろに持っていけばダイアログもいっしょに後ろに行く。他のウィンドウを選択することもできる。というわけで、かなりすぐれもののユーザインタフェースだと思う。
そんなシートを表示するには beginSheet:modalForWindow:modalDelegate:didEndSelector:contextInfo: を使う。
Application Kit/NSApplication.h
- (void)beginSheet:(NSWindow *)sheet
modalForWindow:(NSWindow *)docWindow
modalDelegate:(id)modalDelegate
didEndSelector:(SEL)didEndSelector
contextInfo:(void *)contextInfo;
このメソッドは、runModalForWindow: と違って非同期なんだ。つまり、これを呼ぶと、ダイアログを表示して、すぐ戻ってくる。ダイアログを閉じてないとしてもだ。そして、おおもとのイベントループで動くんだ。
シートダイアログを閉じるには、endSheet: を使う。実は、シートを表示させるときにセレクタを渡しているんだけど、endSheet: を呼ぶと、そのセレクタが呼び出されるんだ。そのメソッドの中で、ダイアログを閉じた後の処理を行う。
Application Kit/NSApplication.h
- (void)endSheet:(NSWindow *)sheet;
- (void)endSheet:(NSWindow *)sheet returnCode:(int)returnCode;
じゃ、サンプル。
Dialog/AppDelegate.m (sample)
- (IBAction)showSheet:(id)sender
{
// Display sheet dialog
[[NSApplication sharedApplication]
beginSheet:_sheetDialog
modalForWindow:_window
modalDelegate:self
didEndSelector:@selector(
sheetDidEnd:returnCode:contextInfo:)
contextInfo:nil];
}
- (void)sheetDidEnd:(NSWindow*)sheet
returnCode:(int)returnCode
contextInfo:(void*)contextInfo
{
[_sheetDialog orderOut:self];
// Check return code
if(returnCode == DIALOG_CANCEL) {
// Cancel button was pushed
NSLog(@"Sheet is canceled");
return;
}
else if(returnCode == DIALOG_OK) {
// OK button was pushed
NSLog(@"Sheet is accepted");
}
}
- (IBAction)sheetOk:(id)sender
{
// OK button is pushed
[[NSApplication sharedApplication]
endSheet:_sheetDialog returnCode:DIALOG_OK];
}
- (IBAction)sheetCancel:(id)sender
{
// Cancel button is pushed
[[NSApplication sharedApplication]
endSheet:_sheetDialog returnCode:DIALOG_CANCEL];
}
このサンプルでは、showSheet: を呼び出すとシートを表示する。その際に、sheetDidEnd:returnCode:contextInfo: を閉じたあとに呼び出されるセレクタとして設定している。sheetOk: と sheetCancel: を呼び出すと、ダイアログを閉じて登録されているメソッドが呼び出されるんだ。
■サンプルダウンロード:
GraphicFunctions.tar.gz
Application Kit - NSApplication
2 つのダイアログの違い
Keywords: application-modal dialog, document-modal dialog
アプリケーション・モーダルダイアログと、ドキュメント・モーダルダイアログ(シート)の違いをまとめてみよう。
プログラミング的な立場からいうと、アプリケーション・モーダルダイアログは、システムから制御を奪ってしまう。Aqua Human Inteface Guidelines の表現を借りると、システムを“ハイジャック”してしまうんだ。だから、アプリケーションがきちんとめんどうをみてやらないといけないし、不測の事態(よーするにフリーズね)も起こりやすい。
それに対して、シートは比較的安全。でも、ダイアログの表示から終了後の処理を、連続して書くことができない。ダイアログを表示させるところと、ダイアログが閉じた後の処理が、別のメソッドになってしまうんだ。これが、プログラム組むときめんどくさいんだよねー。
次にユーザインタフェース的な立場で考えてみよう。これまた Aqua Human Interface Guidlines によると、シートを使うのは次のような場合。
ある特定のドキュメントに対するダイアログの場合
シングルウィンドウのアプリケーションの場合
じゃ逆にシートを使わないのは?次のような場合。
複数のウィンドウに対するダイアログの場合
モードレスウィンドウとして使いたいとき。パレットとか
ウィンドウにタイトルバーがないとき
だ、そうな。
Application Kit - NSApplication
簡単にダイアログを表示する
Keywords: NSRunAlertPanel(), NSBeginSheet()
runForModalDialog: や、beginSheet:modalForWindow:modalDelegate:didEndSelector:contextInfo: を使ってダイアログを表示するときは、.nib を用意してやらないといけない。たんにアラートを出したいときとかは、めんどくさいよね。というわけで、関数呼び出し一発で表示させるものが用意されている。NSRunAlertPanel() と NSBeginAlerSheet() だ。
Application Kit/NSPanel.h
APPKIT_EXTERN int NSRunAlertPanel(
NSString *title,
NSString *msg,
NSString *defaultButton,
NSString *alternateButton,
NSString *otherButton,
...);
APPKIT_EXTERN void NSBeginAlertSheet(
NSString *title,
NSString *defaultButton,
NSString *alternateButton,
NSString *otherButton,
NSWindow *docWindow,
id modalDelegate,
SEL didEndSelector,
SEL didDismissSelector,
void *contextInfo,
NSString *msg,
...);
NSRunAlertPanel() は、アプリケーション・モーダルダイアログを表示させるんだ。最高で 3 つのボタンを持つ。defaultButton、alternateButton、otherButton に表示させたい文字を指定するんだ。nil を渡すと、ボタンは表示されない。
NSBeginAlertSheet() は、シートダイアログを表示ね。NSRunAlertPanel() と違うのは、関連する NSWindow を指定するのと、セレクタを指定すること。
使ってみると、こんな感じかな。
Dialog/AppDelegate.m (sample)
- (IBAction)runAlertPanel:(id)sender
{
int result;
// Display modal dialog
result = NSRunAlertPanel(
@"Normal Alert",
@"Normal Alert",
@"OK",
@"Cancel",
@"Huh?");
if(result == NSAlertDefaultReturn) {
// OK button was pushed
NSLog(@"Dialog is accepted");
}
else if(result == NSAlertAlternateReturn) {
// Cancel button was pushed
NSLog(@"Dialog is canceled");
}
else if(result == NSAlertOtherReturn) {
// Huh? button was pushed
NSLog(@",,, do you have any questions?");
}
}
- (IBAction)runAlertSheet:(id)sender
{
// Display sheet dialog
NSBeginAlertSheet(
@"Alert sheet",
@"OK",
@"Cancel",
@"Huh?",
_window,
self,
@selector(
runAlertSheetDidEnd:returnCode:contextInfo:),
nil,
nil,
@"Alert sheet");
}
■サンプルダウンロード:
GraphicFunctions.tar.gz
Application Kit - NSApplication
NSApplication のサブクラスを使う
Keywords: Cocoa own configuration
実は NSApplication のサブクラスを作る必要性ってのは低い。なぜなら NSApplication の機能を拡張したいときは、デリゲートを使えばたいていのことはできちゃうからだ。でも、ときにはサブクラスを作りたいときもある。
そんなときは素直に NSApplication のサブクラスを作ろう。サブクラス自体は Interface Builder とかで作ってやれば、すぐできる。ただ、そのサブクラスをアプリケーションに使わせるには、次の設定を忘れてはいけないんだ。
MainMenu.nib の File's owner として指定する
ターゲットの「Cocoa 固有の設定」のところで、主要クラスとして指定する
これで新しく作ったサブクラスが NSApplication の代わりに使われるぜ。
|