ImageButton

要求

PersonalJava(JDK1.1.8)で動く、画像表示が可能なボタンが欲しい。

設計方針

本来、java.awt.Componentを継承して、Toolkit経由で実行環境から自分でボタンを取って来るところから始めるべきであるが、とりあえず、見た目さえうまく動いていれば良いので手抜きをして、java.awt.Buttonクラスを継承する。

従って、オーバーライドすべき項目は、getPrefferedSize()、paint()。

注意点:ImageObserver

Imageオブジェクトの取り回しに注意。

ImageはToolkit.getDefaultToolkit().getImage()を用いて確保するのであるが、この時点では実際にはImageは確保されておらず、画像ファイルへの参照が保持されただけである。

したがって、この時点ではImageに対する操作はすべて失敗してしまう(drawImage()さえ!)

今回は、ボタンのサイズを決定するために画像の大きさを取得する必要があるためにImageに対する操作、getWidth()とgetHeight()を初期化の時点で行わなければならないためこれは死活問題であります。

では、いつ実際にImageオブジェクトが確保されるかと言えば、最初にdrawImage()が呼び出された後である。
drawImage()のメソッドシグネチャを見ていただきたい。
drawImage(Image,int,int,ImageObserver)
なぜ3つ目の引数が必要であるか? 考えたことはあるでしょうか?

前述の通り、drawImage()が呼び出されて初めて対象のImageはオブジェクトとして生成されるわけですが、このときImageオブジェクトを生成する処理は別スレッドで行われます。

このとき、その別スレッドからの情報を受けて、Componentとやりとりを行うのがこのImageObserverになります。

Imageオブジェクトを取得し始めてから、そのオブジェクトの状態が変化するたびにImageObserverを通じてImage参照元に通知されます。

参照元はこの変化が通知されるたびに、Imageの処理を試みます。 ちなみに、ImageObserverを通して知ることができる状態は次の通り。これらはImageObserverのクラスフィールドであり、static intで表現されている(APIドキュメントではjava.awt.image.ImageObserverを参照)
ABORT imageUpdate メソッドの infoflags 引数の 1 要素であるこのフラグは、非同期的に記録されていたイメージの生成がその完了前に中断したことを示します。
ALLBITSimageUpdate メソッドの infoflags 引数の 1 要素であるこのフラグは、それまでに描画されたスタティックイメージが現在は完成し、その最終形式で再び描画できることを示します。
ERROR imageUpdate メソッドの infoflags 引数の 1 要素であるこのフラグは、非同期的に追跡されたイメージでエラーが検出されたことを示します。
FRAMEBITSimageUpdate メソッドの infoflags 引数の 1 要素であるこのフラグは、マルチフレームイメージの、それまでに描画されたもう 1 つの完全なフレームを再描画に利用できることを示します。
HEIGHTimageUpdate メソッドの infoflags 引数の 1 要素であるこのフラグは、ベースイメージの幅が利用可能であることを示し、imageUpdate コールバックメソッドの引数 height から取得できます。
PROPERTIESimageUpdate メソッドの infoflags 引数の 1 要素であるこのフラグは、イメージのプロパティを現在利用できることを示します。
SOMEBITSimageUpdate メソッドの infoflags 引数の 1 要素であるこのフラグは、イメージをスケーリングしたバリエーションを描画するために必要なピクセルが現在利用可能であることを示します。
WIDTH imageUpdate メソッドの infoflags 引数の 1 要素であるこのフラグは、ベースイメージの幅が利用可能であることを示し、imageUpdate コールバックメソッドの引数 width から取得できます。

さて、このフラグを用いてImageオブジェクトの構築段階をチェックするわけですが、Componentクラスにこのチェックに関する便利なメソッドが用意されています。
checkImage(Image image, ImageObserver observer)
このメソッドを呼ぶと、先ほどのフラグに関する論理和が返されますので、受け取って自分で適当にバイトマスクして比較しましょう。
prepareImage(Image image, ImageObserver observer)
このメソッドは、指定したImageが使用できる状態であればtrueを返すので、本当はこのメソッドを使うだけで良いのですが、効率化のためにはcheckImage()も使った方が良いのです。これについては次の注意点で。

注意点:getPrefferedSize()

ボタンは当然ながら何かしらのContainerに配置されますね? そして、Containerに配置される際LayoutManagerはインスタンスに対してgetPrefferedSize()を呼び出して、そのインスタンスの大きさを知ろうとします。

このとき、Imageオブジェクトの構築が終わる前にgetPrefferedSize()が呼び出されると正しいサイズをLayoutManagerに渡すことができません。そこで、getPrefferedSize()はImageオブジェクトの構築が終わるまで実行を待ってもらいます。

また、必要な情報はImageに関して、幅と高さの情報だけなので、この二つのフラグがたった場合にはまだdrawImage()ができなくてもgetPrefferedSize()は実行できるようにします。

このために、フラグのチェックを新しいスレッドを使って行い、getPrefferedSize()はそのスレッドの終了を待ってから実行を行うことにします。

こういう時にうってつけなのがjoin()メソッドですね?

ソースコード

import java.awt.*;
import java.awt.image.ImageObserver;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;


/**
 * @author 睦月
 * @version 1.1 , 2002/11/05
 * 
 */
public class PdaImageButton extends Button implements Runnable{
	private Image image = null;
	private Dimension dimension = null;
	private Thread thread;
	private int  insetX,insetY;
	/**
	 * Method PdaImageButton.
	 * 
	 * @param imageName ボタンに貼り付けたい画像
	 */
	public PdaImageButton(String imageName){
		super();
		image = Toolkit.getDefaultToolkit().getImage( imageName );
		thread = new Thread(this);
		thread.start();
		insetX = insetY = 2;
	}
	public void run(){
		prepareImage(image,this);
		int state,sizeFlag;
		sizeFlag = ImageObserver.WIDTH | ImageObserver.HEIGHT;
		state = checkImage(image,this);
		while(((state & sizeFlag) != sizeFlag) && 
		((state & ImageObserver.ERROR) != ImageObserver.ERROR)){
			try{
				thread.sleep(100);
			}catch(Exception e){
				System.out.println(e);
			}
			state = checkImage(image,this);
		}
		dimension = new Dimension(image.getWidth(this)+4,image.getHeight(this)+4);
	}
	public void update(Graphics g){
		paint(g);
	}
	public void paint(Graphics g){
		super.paint(g);
		if(image != null){
			g.drawImage(image,insetX,insetY,this);
		}

	}

	/**
	 * @see java.awt.Component#getPreferredSize()
	 */
	public Dimension getPreferredSize() {
		if(dimension == null){
			try{
				thread.join();
			}catch(InterruptedException ie){
				System.out.println(ie);
			}
		}
		return dimension;
	}
	public static void main(String[] args) {
		Frame frame = new Frame();
		frame.addWindowListener(new WindowAdapter(){
			public void windowClosing(WindowEvent e){
				System.exit(0);
			}
			public void windowClosed(WindowEvent e){
				System.exit(0);
			}
		});
		Panel panel = new Panel();
		
		if(args.length != 0){
			for(int i=0;i<args.length;i++){
				panel.add(new PdaImageButton(args[i]));
			}
		}
		
		ScrollPane scrollPane = new ScrollPane();
		scrollPane.add(panel);
		scrollPane.setSize(800,500);
		frame.add(scrollPane);
		frame.pack();
		frame.show();
	}
}
戻る