高度な検索
Developer Connection
Member Login ログイン | ご入会 ADC連絡先


Technote 1156

Scribbling Into AWT Components


目次

Pure Java 以外の描画方法について

実現方法

互換性について

参考文献

このテクニカルノートでは、Java AWT の Graphics API 以外の方法で AWT コンポーネントに描画する方法を説明します。特に、コンポーネントの可視領域に対応する QuickDraw の GrafPort、原点、クリップ領域がわかれば、どのような方法 (たいていは QuickDraw でしょうが) を使っても、コンポーネントの内部を描画することができます。



Pure Java 以外の描画方法について

大半の Java アプリケーションでは、ユーザインタフェースの描画を行うのに、普通の「100% Pure Java」API を使えば十分です (ときに仕方なくそうする場合もありますが)。こうしたアプリケーションは、ボタンやチェックボックスなど既存の AWT コンポーネントと、Component クラスの独自サブクラスを組み合わせて使用します。後者の場合、AWT の Graphics クラスが提供するグラフィックスプリミティブを使用してコンポーネント内部を描画します。(Swing や IFC など、より上位のクラスライブラリによって提供されるコンポーネントを使用する場合もありますが、これも結局は Graphics を使用します。)

しかし、プラットフォームのツールボックスが提供する描画サービスを使用しなければならない Java コードもあります。プラットフォームのツールボックスの機能に対応する Java API を提供することを目的とする Java ライブラリなどがそうです。例えば、アップルの QuickTime For Java や OpenGL Java インタフェースはそのようなライブラリの例です。

こうしたコードはネイティブメソッドまたは JDirect を使って、ツールボックスを呼び出す必要があります。いずれの機構も別の文書で解説されていますが、いざ描画を行う段になって問題が起こります。それは、描画のコードがコンポーネントに描画するのに必要なリソースをどのように獲得するかという問題です。例えば、QuickDraw の呼び出しを行う前に、適切な GrafPort を取得し、GrafPort の原点とクリップ領域を設定する必要があります。

これは Mac OS だけではなく、どのプラットフォームにもあてはまる問題です (必要なリソースの細部はもちろんプラットフォーム固有ですが)。このため、サン・マイクロシステムズは、DrawingSurface (以下では「描画面」とも呼びます) と総称される API 群を定義し、Component オブジェクトからネイティブウィンドウのシステムリソースへの対応付けで使用できるようにしました。

先頭ページに戻る


実現方法

まず、描画面として正しい種類の Component クラスを選んでください。描画面にはネイティブピアを伴ったコンポーネントが必要なため、軽量コンポーネントは使えません。また、Button や Choice など、AWT 自身がすでに描画済みのコンポーネントに独自の描画を行うのは止めた方がいいでしょう。そういうわけで、最も動作が良好な Component タイプは、Canvas、Panel および、全 Window タイプ (Window、Frame、Dialog) です。

DrawingSurface の取得方法

sun.awt.DrawingSurface インタフェースは、ネイティブな描画面に関する情報にアクセスするために使用します。このインタフェースは非軽量コンポーネントのピアと、オフスクリーングラフィックス用に作成した Image によって実装されています。インタフェースには唯一、getInfo というメソッドだけがあり、DrawingSurfaceInfo オブジェクトを返します。これが主に必要なオブジェクトです。

以下に示すのは、theComponent という Component オブジェクトに対応する DrawingSurfaceInfo を取得する Java コードの断片です。

import sun.awt.*;
                  
...
                  
DrawingSurface ds = (DrawingSurface)theComponent.getPeer();
DrawingSurfaceInfo dsi = ds.getDrawingSurfaceInfo();

(JNI を使ってネイディブコードからでも同じことができますが、もっと面倒です。)

描画方法

描画を始める前に、DrawingSurfaceInfolock メソッドを呼び出し、描画を終えたら、同じく DrawingSurfaceInfounlock メソッドを呼び出す必要があります。lock メソッドは QuickDraw の状態設定を行い、GrafPort を描画可能な状態に設定します。

描画中に、他のスレッドの下で動作する AWT コードにポート (ツールボックスの他の大域的状態) が壊されるのを防ぐには、DrawingSurfaceInfolock メソッドを呼び出す前に、ツールボックスのロック (Toolbox.LOCK) で同期を取る必要があります。これについては「Technote 1153:MRJ から複数スレッドで安全にツールボックスを利用する方法」で詳細を説明しています。下のコードの断片は、その方法を表したものです。

重要
ツールボックスロックの同期は、AWT が唯一保持する中心的なセマフォの獲得にあたり、synchronized で指定された同期ブロックが終了するまでは、他の Java スレッドやネイティブコードのアプリケーションはツールボックスにアクセスできません。これは次のことを意味します。

  • ロックの時間をできるだけ短くしてください。ブロックに入ったらただちに描画を済ませ、すぐにブロックを出なければなりません。

  • 描画面をロックしている間、他の AWT 呼び出しを行ってはいけません (描画を行うスレッドが、AWT 呼び出しを行う必要のある同じアプリケーションの他のスレッドに対してブロックするかもしれない処理をしてもいけません。そうするとデッドロックが起こる可能性があります)。

  • 処理から抜ける際には、最後に必ず unlock を呼び出してください。それには、finally 節の中で unlock を呼び出すのがよいでしょう。


こうして描画面をロックすると、QuickDraw で Component オブジェクトに描画する準備ができます。GrafPort は現在のポートに、ローカル座標の (0,0) はコンポーネントの左上角に、clipRgn はコンポーネントの可視領域に設定されます (これには他の非軽量の子コンポーネントが占める領域は含まれません)。

DrawingSurfaceInfo.getBounds メソッドを呼び出すと、Component のバウンディングボックスをローカル座標で取得できます。この呼び出しは、DrawingSurface がロックされている間に呼び出しても安全です。

描画が済んだら、以前の GrafPort を復元する必要はありません。しかし、Component オブジェクトの GrafPort の状態のうち、変更したもの (クリッピング、色など) は元に戻さなければなりません。そして、もちろん描画面のアンロックを行う必要があります。

その方法を、上記のコードの断片に続けて、示します。

import QuickdrawFunctions;   // SDK の JDirect サンプルコードより
import com.apple.mrj.macos.toolbox.Toolbox;
                  
...
synchronized( Toolbox.LOCK ) {
    dsi.lock();
    try{
        Rectangle bounds = dsi.getBounds();
        QuickdrawFunctions.MoveTo(bounds.left, bounds.top);
        QuickdrawFunctions.LineTo(bounds.width-1, bounds.height-1);
    }finally{
        dsi.unlock();
    }
}

WindowPtr の利用

状況によっては (例えば、Palette Manager を直接操作する場合など)、Component オブジェクトが収容されているウィンドウの Mac OS WindowPtr を調べる必要があります。これはコンポーネントの GrafPort と同じではありません。MRJ 2.1 は描画専用の GrafPort を作成するため、WindowPtr に直接描画することはありません。WindowPtr にアクセスする場合でも、描画目的に使ってはいけません。描画は必ず DrawingSurfaceGrafPort に行ってください。

重要
GetWindow メソッドは MRJ 2.1 で新しく追加されたものです。これは MRJ 2.0 にはありません。MRJ 2.0 でこのメソッドを呼び出すとエラーになります。

WindowPtr は次のコードで取得します。GrafPortGDevice も同じようにして取得することができます。

MacDrawingSurface mds = (MacDrawingSurface) dsi.getSurface();
int windowPtr = mds.getWindow();  // WindowPtr を int にキャスト
int grafPtr = mds.getPort();      // CGrafPtr を int にキャスト
int gdevice = mds.getDevice();    // GDHandle を int にキャスト

これらの値をあまり長い間保持してはいけません。Component オブジェクトのピアが破棄されたらもう有効ではないからです。ピアが破棄される場合とは、コンポーネントまたはその親が非表示にされたり、ウィンドウが閉じられた場合です。安全のためには、DrawingSurface がロックされている間だけしか、取得とアクセスを行わないことです。

重要
MRJ 2.1 には、同一の Component や Image に対してネイティブメソッドによる描画と Java からの描画 (java.awt.Graphics を使用) を混在させる場合に発生する、あるバグがあります。このバグを回避するには、描画を行うすべての DrawingSurface に対して最低 1 回は getWindowgetPortgetDevice のいずれかを呼び出す必要があります。これらのメソッドのどれかが呼び出されるまで、MRJ 2.1 はネイティブメソッドによる描画が行われることがわからないため、Java による描画とネイティブメソッドによる描画とを完全に同期することができないのです。

先頭ページに戻る


互換性

DrawingSurface API は MRJ 2.0 以降でも実装されていますが、MRJ 2.1 の実装の方がより安定しています。

MacDrawingSurface.getWindow メソッドは MRJ 2.1 で追加されました。MRJ 2.0 にはこれが実装されていないので、このメソッドを呼び出すクラスをロードしようとすると、クラスローダがエラーを返します。

すべてのプラットフォームの Java 実装が DrawingSurface をサポートしているわけではありません。サンは、sun.*クラスは標準サポートの Java API 群に含まれないことを明言しています。サンの JDK 1.1 以降は DrawingSurface をサポートしています。当然ながら、描画のために必要な具体的な OS 呼び出しはプラットフォーム固有です。MacDrawingSurface クラスは存在しない場合があります。他の Java 実装における DrawingSurface の使用方法については、製品ベンダからドキュメントを入手して調べる必要があります。

先頭ページに戻る


参考文献

  • 「Technote 1153:MRJ から複数スレッドで安全にツールボックスを利用する方法」
    ツールボックスのロックとその使用方法が説明されています。Mac のツールボックスを用いて描画を行う読者は必読です。


先頭ページに戻る