これまでのところで、MainMenu.nibに環境設定のウインドウを作成してユーザインタフェースを構築し、そのクラスのソースファイルを生成した。生成したクラスファイルのPreferencesWindow.javaをProject Builderで見てみると、次の図のように、4つのOutletに対応したメンバー変数と、1つのActionに対応したメソッドが出来上がっている。
◇生成されたPreferencesWindow.java
このソースにプログラムを以下のように追加する。順次説明をしていこう。
◇プログラムを追加したPreferencesWindow.java
まず、Outletに対応したメンバー変数であるが、Interface Builderにソースを生成させた段階では、変数のクラスはObjectになっている。Objectはどんなオブジェクトでもいわばワイルドカードのようなものなのであるが、Javaではキャストをしないといけないなどの煩わしさがあるので、ここでは、ObjectではなくNSTextFieldクラスの変数として書き換える。こうしておいても、問題なくウインドウ上のテキストフィールドを参照できるようだ。 次に、環境設定ウインドウで設定した値を記憶するためのstaticな変数を定義する。それぞれ、int型でinitLeft、initTop、initWidth、initHeightという変数を定義し、ここでは初期値をとりあえず適当な値にしている。なお、NSWindowなどのCocoaのコンポーネントの座標値は本来はfloatになっているが、そういう細かいアプリケーションなので、そのあたりは目をつむっていただきたい。なお、システムに初期設定値を記憶させる方法は、別の回に改めて説明しよう。 そして、OKボタンをクリックして呼び出されるメソッドsetupInitValueでは、テキストフィールドから読み取った値を、static型の変数に記憶している。たとえば、高さのテキストフィールドは、initHeightTextFieldで参照できるので、intValueメソッドでテキストフィールドに入力された値を整数値として得る。それをinitHeight変数に記憶させておくのである。こうすれば、他のインスタンスから、環境設定の値を参照できるというわけだ。(ところで、OKボタンとしたが、むしろ「設定」ボタンの方がこうした動作の場合には適切かもしれない)なお、文字をテキストフィールドに入れた場合などのエラー処理は行っていない。
ここまでで、環境設定のユーザインタフェースを作り、それをウインドウとして表示して、値を保存できると言いたいところだが、実行すると、次のようなエラーが出るはずだ(前の図の、最後のコンストラクタがない状態で実行する〜メッセージの一部は割愛)。
2002-01-26 00:35:07.187 MOSAEditor[4110] AppKitJava: uncaught exception NSInvalidArgumentException (_BRIDGEUnmappedInitMethodImp: the java class PreferencesWindow does not implement any constructor that maps to the Objective C method initWithContentRect:styleMask:backing:defer:.)
どうやら、PreferencesWindowに必要なコンストラクタが組み込まれていないということのようだ。ここで、当然ながら、Javaなので、引数のないPreferencesWindowコンストラクタは存在するものとして扱われるが、実行時にエラーがでているので、そのコンストラクタうんぬんではない。結果的には前の図に示したようなコンストラクタを定義するのだが、こうした根拠は実はObjective-Cのドキュメントを参照しなければならない。Objective-CのAppKitのNSWindowのドキュメントを見ると、Creationのカテゴリで、- initWithContentRect:styleMask:backing:defer:というメソッドが紹介されている。エラーメッセージにもこのメソッドがマップされていないとでているが、ドキュメントではDesignated initializerであるとこのメソッドは紹介されている。つまり、Interface BuilderでNSWindowあるいはそのサブクラスのインスタンスが定義されていれば、initWithContentRect:styleMask:backing:defer:というイニシャライザメソッドが実行されてオブジェクトの生成を行うということである。 この事実をJavaに置き換えると、Interface Builderで定義されたNSWindowないしはそのサブクラスは、そこコンストラクタのうち、以下のものを呼び出すことで、インスタンス生成が行われるということになる。Java Bridgeのドキュメントでは、JavaのコンストラクタによってObjective-Cでのイニシャライザが呼び出されることが明記されていることと、エラーメッセージにでているメソッドの引数並びから判断して、以下のコンストラクタが呼び出されるものと判断できるわけだ。
public NSWindow( NSRect contentRect, int styleMask, int backingType, boolean defer)
したがって、NSWindowを継承したPreferencesWindowでも、同じ引数並びのコンストラクタを定義しておく必要がある。単に定義して、同じようにNSWindow側、つまりsuperのコンストラクタを呼び出しておけばそれでいいわけだ。 ただ、こうしたことが、Cocoa-Javaのドキュメントには明記されていないのはやはりなんとかしてほしいところである。
続いて、実際にウインドウを表示するところで、環境設定ウインドウで指定した位置とサイズにするということをしたいが、1つの方法は、NSDocumentクラスを継承しているMyDocumentクラスにあるwindowControllerDidLoadNibメソッドで、ウインドウのサイズを変更することである。たとえば、次のようなプログラムになるだろう。setFrameメソッドで、生成されているウインドウの位置やサイズを、環境設定のウインドウを管理しているクラスから取り出した数値で設定している。
public void windowControllerDidLoadNib(NSWindowController aController) { super.windowControllerDidLoadNib(aController); setupWindowFromData(); NSRect initialRect = new NSRect( PreferencesWindow.initLeft, PreferencesWindow.initTop, PreferencesWindow.initWidth, PreferencesWindow.initHeight); docTextView.window().setFrame(initialRect, true); docTextView.window().makeFirstResponder(docTextView); isNibLoaded = true; }
つまり、MyDocument.nibファイルをロードして、その定義に従ってクラスのインスタンス化が行われた後に、windowControllerDidLoadNibが呼び出される。つまり、文書ウインドウは生成されているのである。それは、ここではNSTextViewのwindowメソッドから参照できる。これに対して大きさを設定しているのであるが、環境設定ウインドウで入力している値はstaticな変数で参照できるようにしあるので、クラス名と変数名の記載で環境設定値が利用できるということである。ここで、staticにしていることで簡単にアクセスできるようになるということである。
それでは実際に実行させてみよう。起動して、アプリケーションメニューからPreferencesを選択する。すると、環境設定のウインドウが出るので、とりあえずCloseボタンを押してみよう。閉じるはずだが、これはNSWindowsのperformCloseメソッドに接続しているからだ。再度開いて数値を入力し、OKボタンをクリックする。ここでは環境設定のウインドウは閉じられるようには作っていないが、OKをクリックすることで、ウインドウの中の値を記録する。続いて、Command+Nで新しいウインドウを呼び出すと、環境設定で指定した値になっているのが分かる。
◇MOSAEditorを動かしてみた
なお、左上位置の座標は、MOSAEditorを起動したときに表示されるウインドウにしか反映しない。これは、NSWindowControllerが最終的にウインドウの位置を順々に重なるように変更するからのようだ。一般的な文書作成アプリケーションではそれで問題はないだろう。もちろん、「指示通り」にしたいというニーズもあるかもしれないが、今回はそこまでの突っ込みはしないことにしよう。 (この項、続く) |