Geometry 関数
NSBundle
NSDictionary
NSFileManager
NSMutableAttributedString
NSNumber
NSScanner
NSString
NSTimer
Graphic 関数
NSApplication
-実行中のインスタンスを取得する
-NSApplicationMain の内部
-マウスドラッグをトラッキングする
-モーダルダイアログ
-アプリケーション・モーダルダイアログを表示する
-ドキュメント・モーダルダイアログ(シート)を表示する
-2 つのダイアログの違い
-簡単にダイアログを表示する
-NSApplication のサブクラスを使う
NSBezierPath
NSCell
NSColor
NSCursor
NSCustomImageRep
NSDocument
NSDocumentController
NSDragging
NSEvent
NSFontManager
NSGraphicsContext
NSImage
NSMenu
NSOutlineView
NSPanel
NSPopUpButton
NSResponder
NSScrollView
NSString 追加
NSTableColumn
NSTableView
NSTextStorage
NSTextView
NSToolbar
NSView
NSWindow
NSWorkspace
その他
.nib ウィンドウ
Views パレット
クラス
インスタンス変数
メソッド
CFXML
Carbon Event
Carbon Graphics
Cocoa で日本語
メソッド
その他

- 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: を使って、イベントを取得している。maskNSLeftMouseDraggedMaskNSLeftMouseUpMask を指定してるんだ。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;

このメソッドを使うときは、呼び出しは同期的だということに注意してくれ。。どういうことかっていうと、このメソッドを呼ぶとダイアログが表示される。そして、そのダイアログを処理している間は、イベントループはその中で回るんだ。メソッドの呼び出しは、処理中は返ってこないで、ブロックされるんだ。

ここから抜けるには、stopModalstopModalWithCode: を、そのダイアログ処理の中で呼ぶ。外から止めたい場合には abortModal を使う。

Application Kit/NSApplication.h
- (void)stopModal;
- (void)stopModalWithCode:(int)returnCode;
- (void)abortModal;

これらを呼ぶと、runModalForWindow: のモーダルループから抜け出す。その際の返り値で、どのメソッドが呼ばれたかを判断することができるんだ。stopModalNSRunStoppedResponse を返す。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 つのボタンを持つ。defaultButtonalternateButtonotherButton に表示させたい文字を指定するんだ。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 とかで作ってやれば、すぐできる。ただ、そのサブクラスをアプリケーションに使わせるには、次の設定を忘れてはいけないんだ。

  1. MainMenu.nib の File's owner として指定する
  2. ターゲットの「Cocoa 固有の設定」のところで、主要クラスとして指定する

これで新しく作ったサブクラスが NSApplication の代わりに使われるぜ。



[Home] [Download] [Archives] [BBS] [Cocoa Programming Tips 1001] [Core Foundation の秘密] [Safari Developer Center] [はじめてのブラウザのつくり方] [Sketch BP] [スクリーンセイバーを作ろう] [Objective-C 最適化] [Authorization API 完全理解] [Mac OS X Programming Books Review] [オブジェクト指向の言語比較論] [panther-dev]

mailto: mkino@xd5.so-net.ne.jp