GO BACK

Cocoa-Java 教室 その5

「オリジナル・コントロールの作成」



■オリジナル・コントロールを作る

 基本的なコントロール類の使い方については、少しずつわかってきたことでしょう。まだ触りだけしか説明していませんが、要するに「コントロールを配置して、必要なアウトレットとアクションをつなぐ」「コントロール内にあるメソッドを調べて呼び出す」——この2つのことさえしっかり頭に入れば、使うのはそれほど困難ではありません。

 が、「コントロールに用意されている機能を利用する」ということだけで、すべてのことが行なえるわけでもありません。例えば、先に「グラフィックの描画」について説明をしましたね。あれは、ボタンをクリックしてコントロールに何かを描画するというものでした。ところが実際に使ってみると、ウィンドウを他のものと切り替えたりリサイズしたりすると描いたグラフィックが消えてしまうことに気づいたはずです。これは、画面更新の要求がされると、コントロールが自動的に表示を更新し初期状態に戻してしまうからですね。

 もし、「常に何かを表示するコントロールが必要だ」となったなら、どうすればいいのでしょう。NSImageViewは、画面更新のイベントなどをアクションで設定することはできません。——その答えが、今回のテーマです。つまり、「画面更新の要求がされたら、オリジナルの表示をするようなコントロールを作ってしまう」のです。

 既に、皆さんはJavaの基本を理解していることと思います。Javaでは、例えばAWTを使ったウィンドウなどを作る際には、Frameクラスを継承した新たなクラスを作成しました。この考え方は、実はCocoa-Javaでも同じなのです。すなわち、独自の機能を持ったコントロールが必要ならば、そのコントロールを継承した新しいコントロールのクラスを作ってしまえばいい、というわけです。

 では、例として「オリジナルの表示をするコントロール」を作ってみましょう。先に、グラフィックの表示にNSImageViewを使いましたが、今回は「NSView」というものを使います。これはNSImageViewなどのスーパークラスにあるもので、画面に表示するコントロールの既定となるクラスといってよいでしょう。通常、「何かを表示するコントロール」を作りたいと思ったら、このNSViewを継承してクラスを作成します。AWTなどの「Panel」や「Canvas」クラスみたいなものを想像すればよいでしょう。では、作成の手順を説明しましょう。

1.まず、プロジェクトを用意したらMainMenu.nibを開いてInterface Builderを起動します。コントロール継承クラスは、Interface Builderで部品として使うので、Project BuilderではなくInterface Builderを使って作成するのが基本です。

2.Nibファイルウィンドウの「Classes」タブを選択し、「NSView」(NSObject → NSResponder → NSView)を選択します。そして「Classes」メニューから「Subclass NSView」を選びます。これでNSViewを継承したクラスが作成されます。

SubClassの作成


3.新たに作成したクラスを選択したまま、Infoウィンドウの「Attributes」で、Languageを「Java」にします。これを忘れると、JavaではなくObjective-Cのクラスとして作成されてしまうので注意して下さい。また名前は、ここでは「MyView」としておきます。

Infoの設定


4.そのまま、「Classes」クラスの「Create File for MyView」メニューを選びます。保存のパネルが現れますが、すべてデフォルトのまま保存すれば、MyViewを保存しプロジェクトのターゲットに設定してくれます。

 これで、NSViewを継承したMyViewクラスができました。Project Builderに戻って、「MyView.java」というクラスファイルが追加されていることを確認しましょう。これが、自作コントロールのクラスなのです。

 では、作成したMyViewを使ってみましょう。Interface Builderでデザインウィンドウを開いて下さい。——オリジナルのコントロールをデザインウィンドウなどに配置して使う場合、最初に「NSView」の部品として配置し、それから「この部品のクラスはこれです」ということを指定する、というやり方をします。

 NSViewは、パレットの 右から3番目にある「Cocoa Container Views」アイコンをクリックすると現れます。「NSView」と表示されたものがそれです。これをデザインウィンドウに配置して下さい。そして、Infoウィンドウから「Custom Class」を選び、そこにあるクラスの一覧から使用するクラス、すなわち「MyView」を選びます。これで、このNSViewの部品はMyViewの部品として認識されるようになります。

MyViewを配置する


 今回はNSViewを継承したクラスを使いましたが、基本的に他のクラスを継承したものであっても「NSViewとして配置し、Custom Classでクラスを変更する」というやり方は同じです。例えばNSButtonを継承したものであっても、「NSButtonを配置してからCustom Classで変更する」と、ビルド時にエラーになるようです。


■MyViewのコード

 さて、これで一応MyViewの作成と使い方はわかりました。後は肝心の「コードの作成」が残っています。Project BuilderでMyView.javaを開いてみて下さい。初期状態で、こんなコードが作成されているはずです。

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

public class MyView extends NSView {

}


 2つのパッケージがimportされている点は普通のクラスと同じですね。そしてNSViewをextendsした形でMyViewクラスが作成されています。この他には何もありません。では、これにメソッドを追加してクラスを完成させることにしましょう。

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

public class MyView extends NSView {

public MyView(NSRect rect) {
super(rect);
}

public void drawRect(NSRect rect) {
NSColor c0 = NSColor.whiteColor();
c0.set();
NSBezierPath r1 = NSBezierPath.bezierPathWithRect(rect);
r1.fill();
for (int i = 0; i < 30; i++) {
float n1 = (float)(Math.random() * rect.width());
float n2 = (float)(Math.random() * rect.height());
float n3 = (float)(Math.random() * 50);
NSRect aRect = new NSRect(n1-n3,n2-n3,n3*2,n3*2);
NSBezierPath p1 = NSBezierPath.bezierPathWithOvalInRect(aRect);
NSColor c1 = NSColor.redColor();
c1.set();
p1.stroke();
}
}

}


 2つのメソッドが追加されていますね。1つは、NSRectというのを引数に持ったコンストラクタ、もう1つは「drawRect」というメソッドです。

 コンストラクタは、「オリジナルのクラスを作成する場合には必須」と考えて下さい。なぜなら、このコンストラクタがなかった場合、ビルドに失敗してしまうからです。Cocoaのコントロール類をウィンドウに配置したりすると、この「NSRectを引数に持つコンストラクタ」によってインスタンスが生成され組み込まれます。このため、このコンストラクタがないとインスタンス生成に失敗してしまうのです。

 もう1つのdrawRectは、コントロールの表示を更新する要求があったときに呼び出されるものです。JavaのAWTにあった「paint」メソッドのようなものと考えればよいでしょう。引数に渡されるNSRectは、コントロールの領域を示すものとなります。つまり、このrectを使って描画すれば、コントロールの領域にぴったりとはめ込まれるように描画ができるわけですね。

 ここでは、まずコントロール全体を白く塗りつぶした後、ランダムに30個の円を描くようにしました。基本的には既に説明した事柄ばかりですが、一つ触れておきたいのは「引数で渡されたNSRectの利用の仕方」でしょう。ここでは「rect.width()」「rect.height()」という形でコントロールの縦横の幅を取り出しています。この他に、横位置と縦位置を得る「x()」「y()」といったメソッドも用意されています。NSRectは、このようなメソッドを使うことで縦横幅などを得ることができるのです。覚えておくと重宝しますね。

 一見するとCocoaのコントロールはJavaのAWTなどとずいぶん違うもののように感じられるでしょうが、このように実際にクラスを継承してコントロールを作成したりすると、その基本的な仕組みはかなり似通っていることがわかります。Interface Builderではアウトレットやアクションといったものを接続するなどして「おっ、JavaのAWTとはかなり雰囲気が違うぞ?」という感じがしましたが、クラス定義を覗いてみれば、あらかじめ用意されているさまざまなイベント用のメソッドがあって、それを用意することで「このイベントが発生したらこの処理が呼び出される」ということを実現していたわけです。ということは、イベントで呼び出されるメソッドさえわかれば、ある程度コントロールの自作はできそうですね。


MyViewの実行画面


■マウスのイベントを使う

 今度は、ユーザーの操作を利用する機能を付け足してみましょう。それは「マウスの操作」です。こうした「ユーザーの操作に対応した処理』の場合、Javaでは「イベントリスナー」というものを使いましたね。が、Cocoaはもっとシンプルです。単純に「操作に対応したメソッドを用意する」だけで済みます。

 では、先ほどのMyViewクラスを書き換えて、「クリックした地点の円が表示される」というようなコントロールに改造してみましょう。

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

public class MyView extends NSView {
NSPoint p1;

public MyView(NSRect rect) {
super(rect);
p1 = new NSPoint(-100,-100);
}

public void drawRect(NSRect rect) {
NSColor c0 = NSColor.whiteColor();
c0.set();
NSBezierPath r1 = NSBezierPath.bezierPathWithRect(rect);
r1.fill();
NSColor c1 = NSColor.redColor();
c1.set();
r1.stroke();
NSColor c2 = NSColor.blueColor();
c2.set();
NSRect r2 = new NSRect(p1.x()-25,p1.y()-25,50,50);
NSBezierPath o1 = NSBezierPath.bezierPathWithOvalInRect(r2);
o1.fill();
}

public void mouseDown(NSEvent event) {
p1 = event.locationInWindow();
p1 = convertPointFromView(p1,null);
display();
}

}


 ざっとこんな感じになるでしょう。これをビルドして実行してみて下さい。起動した状態では、四角い枠線に白いコントロールが表示されます。その中をマウスでクリックすると、そこに青い円が表示されるようになります。もちろん、ウィンドウを切り替えたりしても表示が消えることはありません。

クリックした地点に円


 ここでは、NSPointのフィールドを1つ用意し、マウスをクリックしたらその地点をおさめておくようにします。そしてdrawRectでは、そのフィールドの地点に円を描くようにするわけです。マウスをクリックした際の処理は、以下のようなメソッドで行なっています。

public void mouseDown(NSEvent event) { ・・・ }

 これは文字通り「マウスボタンを押し下げた時に呼び出される」メソッドです。同様にマウスボタンを離した際に呼び出される「mouseUp」や、ドラッグ中に繰り返し呼び出される「mouseDragged」といったものもあります。これらは引数に「NSEvent」というものが渡されますが、これが発生したイベントに関する情報をまとめたクラスです。JavaのAWTでもeventクラスはありましたら、それと似たようなものと考えればよいでしょう。

 ここでは、このNSEventからマウスポインタの位置を取り出し、それをフィールドにおさめています。が、これにはちょっとした注意が必要です。

p1 = event.locationInWindow();
p1 = convertPointFromView(p1,null);

 最初に「locationInWindow」というメソッドを呼び出しています。これがイベント発生時のマウスポインタの位置を調べるメソッドで、位置を示すNSPointインスタンスを返してきます。が、注意しておきたいのは、「これで得られるのは、コントロール内の位置ではなく、コントロールが配置されているウィンドウ内の位置である」ということです。従って、この値をそのまま使って描画をすると、コントロールが配置されている位置の差分だけずれてしまいます。

 そこで、「ウィンドウ内の相対位置を、コントロール内の相対位置に変換する」という処理が必要になります。それが「convertPointFromView」です。これはNSPointを変換した値(NSPoint)を返すもので、第1引数が元のNSPoint、第2引数が元の座標の基準となっているコントロールのインスタンスとなります。このコントロールが収められているウィンドウの場合には、nullにしておくことになっています。

 というわけで、ウィンドウからコントロールに座標変換したものをフィールドにおさめ、それをdrawRect時に利用して描画することで、「クリックした位置に図形が描かれる」という処理ができたわけです。マウス関連は、とりあえずマウスポインタの位置だけ利用できるようになれば、ちょっとしたコントロールは作れるようになるでしょう。これを元に、オリジナルのコントロール作りに挑戦してみるとよいでしょう。


GO NEXT


GO HOME