タイトル今から始めるCocoaプログラミング》ドラッグ&ドロップでファイルを開く(3)ダブルクリック起動と区別カテゴリーCocoa, 今から始めるCocoaプログラミング
作成日2001/5/8 15:30:11作成者新居雅行
Cocoaアプリケーションで、Finderからのドラッグ&ドロップを受け付ける方法を解説する続きをお届けしよう。まずは簡単に復習したいが、1回目はDelegateという概念を説明した。あるオブジェクトでの処理を別のオブジェクトにまかせるという考え方であり、Cocoaのフレームワークではいろいろなクラスでこの手法を使っている。アプリケーションであるNSApplicationでもこのDelegeteを使っている。また、何かが起こったときには、Notificationというメカニズムで通知が行われるが、その通知の受け取りオブジェクトもDelegeteで別のオブジェクトに振り分けることができる。従って、独自にプログラムしたクラスに対して、アプリケーション(File’s Owner)からDelegeteしておくことで、自分のプログラム側で、アプリケーション内に発生したイベントやNotificationに対応する処理ができるというわけである。こうした対応付けは、Interface Builderで行うことができる。
Finderでアプリケーションに対して文書ファイルをドラッグ&ドロップした場合や、あるいはDock上のアプリケーションアイコンにドラッグ&ドロップした場合、NSApplicationではapplicationOpenFileというメソッドが呼び出される。しかしながら、別のオブジェクトにDelegateしておくと、そのオブジェクトのapplicationOpenFileメソッドが呼び出される。だから、自分で定義したクラスにapplicationOpenFileメソッドを記述すれば、ドラッグ&ドロップしたファイルを処理するプログラムが書けるということになる。前回までに説明したことの趣旨は以上の通りだ。

では次の課題に入りたいが、ちょっとしたツール的なアプリケーションを作りたいとしよう。ファイルをドラッグ&ドロップしてきたときの処理はapplicationOpenFileメソッドに書くとして、単にダブルクリックしたときと別の処理に切り分けたいとする。そのときに使える手法が、NSApplicationクラスに定義されたNotificationである。Notificationは、メカニズム的にはNotificationCenterというところからオブジェクトに対して送信されるものであり、ある状況で送信されるように設定などを行うのが通常である。だけども、NSApplicationにあるNotificationはそうした基本的な設定は全部クラスの中でやってくれる。使う側としては、単にある状況で決められた名前のメソッドが呼び出されるという事実だけを使えばいいので、Notificationについての詳しい説明は割愛させていただく。
具体的には、NSApplicationからDelegateされたオブジェクト(これまでのサンプルではDocOpenHanlderクラスのインスタンス)に対して、アプリケーションの起動処理をする前と、起動処理を終了したときにNotificationが発生する。つまり、そのタイミングで、Delegateされたオブジェクトの規定のメソッドが呼び出されるのである。アプリケーションの起動前はapplicationWillFinishLaunching、起動後はapplicationDidFinishLaunchingというメソッドが呼び出される。もちろん、定義に従って引数などを記述しなければならないが、具体的には次のように、DocOpenHanlder.javaの中身を記述する。Delegationの動作を見るだけなので、まずは標準出力にテキストだけを書き出してみよう。

/* DocOpenHanlder */

import com.apple.cocoa.foundation.*;
import com.apple.cocoa.application.*;

public class DocOpenHanlder extends NSObject {

public boolean applicationOpenFile(
NSApplication theApplication,
String filename) {
System.out.println("applicationOpenFile:"+filename);
return true;
}
public void applicationWillFinishLaunching(
NSNotification aNotification) {
System.out.println("applicationWillFinishLaunching:");
}
public void applicationDidFinishLaunching(
NSNotification aNotification) {
System.out.println("applicationDidFinishLaunching:");
}
}

applicationWillFinishLaunchingおよびapplicationDidFinishLaunchingは、いずれも戻り値はなく、引数はNSNotificationクラスのものが1つあるだけだ。
こうしてビルドしたアプリケーションをそのまま単に起動するとしよう。たとえば、Project Builderで起動すると、コンソール出力には、次のように表示されるはずだ。つまり、それぞれのメソッドが順番通りに呼び出されたことになる。

applicationWillFinishLaunching:
applicationDidFinishLaunching:

では、次にビルドしたアプリケーションに対して、Finder上でファイルをドラッグ&ドロップして、アプリケーションを起動してみよう。プロジェクトがテンプレートから作成されたままなら、ドラッグ&ドロップしても反応しないかもしれないが、その場合はCommand+optionを押しながらドラッグ&ドロップすればよい。そして、/Applications/UtilitiesにあるConsoleというアプリケーションで、標準出力の結果を参照するとよいだろう。次のようになっている。

applicationWillFinishLaunching:
applicationOpenFile:/Users/msyk/Pictures/Shot0007.pict
applicationDidFinishLaunching:

つまり、アプリケーションが起動するときには、まずapplicationWillFinishLaunchingが呼び出される。そしてドラッグ&ドロップしたファイルを処理するためにapplicationOpenFileが呼び出される。そして、最後にapplicationDidFinishLaunchingが呼び出されるという順序になるわけだ。
ならば、インスタンス変数を適当に定義して、applicationOpenFileメソッドを通過したかどうかを判断できるようにしておき、通過していない場合には、applicationDidFinishLaunchingで単に起動した場合の処理を呼び出せばよいということになる。プログラムとしては以下のようにDocOpenHanlder.javaで記述すればよいだろう。変数isOpenDocumentは、applicationOpenFileを通過したかどうかを判断する変数だ。もし、単にアプリケーションがダブルクリックされて起動されたのなら、メソッドbootByDoubleClickが呼び出されるという風にしてみた。

/* DocOpenHanlder */

import com.apple.cocoa.foundation.*;
import com.apple.cocoa.application.*;

public class DocOpenHanlder extends NSObject {
private boolean isOpenDocument = false;

public boolean applicationOpenFile(
NSApplication theApplication,
String filename) {
System.out.println("applicationOpenFile:"+filename);
isOpenDocument = true;
return true;
}
public void applicationWillFinishLaunching(
NSNotification aNotification) {
System.out.println("applicationWillFinishLaunching:");
}
public void applicationDidFinishLaunching(
NSNotification aNotification) {
System.out.println("applicationDidFinishLaunching:");
if(! isOpenDocument)
bootByDoubleClick();
NSApplication thisApp = (NSApplication)aNotification.object();
thisApp.terminate(this);
}

private void bootByDoubleClick() {
System.out.println("bootByDoubleClick:");
}
}

ここで、applicationDidFinishLaunchingメソッドを見てもらいたい、状況に応じてbootByDoubleClickを呼び出すが、その後に、起動したアプリケーションを終了する処理を組み込んでいる。applicationWillFinishLaunchingやapplicationDidFinishLaunchingといった、NSApplicationクラスでのNotificationによって呼び出されるメソッドは、引数のNSNotificationオブジェクトのobjectメソッドで取り出されるのは、アプリケーションそのものへの参照になる。objectメソッドの戻り値はNSObjectなのでNSApplication型にキャストする。そして、アプリケーションではterminateメソッドを適用すれば、そのアプリケーションを終了させることができるのである。必ずしも終了させないといけないというわけではないが、NSNotification型で得られた引数の使い方を説明する一例として参照してもらいたい。

以上のように、Delegateという仕組みで、アプリケーションの機能をある意味では拡張できる。しかしながら、サブクラスといった手法ではなく、新たなクラスで単に必要なメソッドだけを用意してやればいいというだけのことなので、やり方は非常にシンプルなものとなっている。今回の一連の記事では、3つのメソッドだけを示したが、実はかなりたくさんのDelegate対応メソッドがある。NSApplicationのドキュメントを参照していただきたい。
関連リンク