WisdomSoft - for your serial experiences.

5.7 境界線

Swing コンポーネントには任意の境界線オブジェクトを設定できます。境界線は標準で用意されているものを組み合わせるだけではなく、インターフェイスを実装することでカスタムの境界線を作成することも可能です。

5.7.1 コンポーネントの枠

Swing コンポーネントは周囲に枠を描画する方法を JComponent クラスに一元化しています。コンポーネントの枠の描画処理は、コンポーネントの描画処理とは切り離されているため、コンポーネントに描画する枠を再利用することができるのです。

JComponent クラスは、コンポーネントの境界線となる枠を描画する Border オブジェクトを設定する setBorder() メソッドと、現在の境界線を取得する getBorder() メソッドを提供しています。setBorder() メソッドから境界線を定義している Border オブジェクトを指定することで、コンポーネントに境界線を描画させることができます。

JComponent クラス setBorder() メソッド
public void setBorder(Border border)
JComponent クラス getBorder() メソッド
public Border getBorder()

このメソッドで扱うのは javax.swing.border.Border インタフェースです。javax.swing.border には Border インタフェースの他に、標準でサポートされる Border インタフェースの実装がカプセル化されています。Border インタフェースのメソッドは表1に表します。

表1 Border インタフェースのメソッド
メソッド 解説
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) 指定されたコンポーネントのボーダを、指定された位置およびサイズでペイントします。
public Insets getBorderInsets(Component c) 境界線幅を表す空間の値を返します。
public boolean isBorderOpaque() ボーダが不透明かどうかを返します。

独自の境界線を定義する方法は後述しますが、Border インタフェースの中で最も重要なのは paintBorder() メソッドです。Border インタフェースを実装するオブジェクトが JComponent に設定され、そのコンポーネントの paint() メソッドが呼び出されると Border オブジェクトの paintBorder() メソッドが呼び出されるという仕組みになっています。paintBoder() メソッドでは、与えられた Graphics オブジェクトに x、y 座標から width、height サイズで境界線を描画しなければなりません。

この Border インタフェースを実装するクラスを開発すれば、それはすなわち再利用可能な Swing コンポーネントの枠を手に入れるということになります。

javax.swing.border パッケージには、Border インタフェースの実装例が提供されています。まず、基本に従ってインタフェースを実装する抽象クラス javax.swing.border.AbstractBorder クラスが定義されています。 このパッケージの Border インタフェースを実装するクラスは、まずこの抽象クラスを継承しています。

javax.swing.border.AbstractBorder クラス
java.lang.Object
  |
  +--javax.swing.border.AbstractBorder
public abstract class AbstractBorder extends Object implements Border, Serializable

AbstractBorder クラスの paintBorder() メソッドは何も描画処理を実行しないので、このクラスを継承し、paintBorder() メソッドなどをオーバーライドして具現化していく必要があります。

Swing が標準で提供する Border の実装は、必ず AbstractBorder クラスを継承しています。その中でも最も基本的なのは javax.swing.border.LineBorder クラスでしょう。このクラスは、単純な直線の境界線を定義しています。

javax.swing.border.LineBorder クラス
java.lang.Object
  |
  +--javax.swing.border.AbstractBorder
        |
        +--javax.swing.border.LineBorder
public class LineBorder extends AbstractBorder

LineBorder クラスは次のようなコンストラクタを公開しています。

LineBorder クラスのコンストラクタ
public LineBorder(Color color)
public LineBorder(Color color , int thickness)
public LineBorder(Color color , int thickness , boolean roundedCorners)

color パラメータには線の色、thickness パラメータには線の幅、そして roundedCorners パラメータは境界線の過度を丸くするかどうかを表す boolean 値を指定します。LineBorder オブジェクトは、コンストラクタで設定された色や幅で、コンポーネントの周囲を描画します。

コード1
import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;

public class Test extends JComponent {
	public static void main(String args[]) {
		Border border = new LineBorder(Color.RED , 10 , true);
		JComponent comp = new Test();
		comp.setBorder(border);

		JFrame win = new JFrame();
		win.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
		win.setBounds(10 , 10 , 400 , 300);
		win.getContentPane().add(comp);
		win.show();
	}
}
実行結果
コード1 実行結果

コード1は、10 ピクセルの太さで境界線を描画する Border オブジェクトを生成し、それを JComponent クラスを継承した Test クラスのオブジェクトに設定しています。これをウィンドウに表示すると、実行結果のようにコンポーネントの周囲に枠が描画されます。

5.7.2 立体的な境界線

ボタンのような機能を持ったコンポーネントや、ウィンドウから浮き出ていたり、沈んでいたりする立体的なコンポーネントを実現するには javax.swing.border.EtchedBorder クラスが便利でしょう。このクラスは、他のコンポーネントの境界を立体的に表現したい場合などに利用できるでしょう。このクラスを、エッチングボーダーと呼びます。

javax.swing.border.EtchedBorder クラス
java.lang.Object
  |
  +--javax.swing.border.AbstractBorder
        |
        +--javax.swing.border.EtchedBorder
public class EtchedBorder extends AbstractBorder

EtchedBorder クラスは、コンポーネントの左側と上側、右側と下側をペアに、それぞれ2色ずつ表示するという境界線です。何らかのコンポーネントを囲むコンテナの境界線などに適しているでしょう。このクラスのコンストラクタは次のようなものがあります。

EtchedBorder() クラスのコンストラクタ
public EtchedBorder()
public EtchedBorder(int etchType)
public EtchedBorder(Color highlight , Color shadow)
public EtchedBorder(int etchType , Color highlight , Color shadow)

このとき、etchType パラメータに指定する数値は EtchedBorder クラスで公開されている定数フィールドを用います。RAISED フィールドを指定した場合は浮き彫り、LOWERED フィールドを指定した場合は掘り込みの境界線を意味します。highlight パラメータはハイライトに使う色、shadow パラメータには陰影に使う色を指定します。

インスタンスを生成するときに、カラーがコンストラクタに指定されなかった場合は、paintBorder() メソッドに渡されるコンポーネントの背景色から動的に決定されます。動的に決定する手法を用いれば、コンポーネントの背景色に最適な色が常に選択されるため、便利でしょう。

ボタンのようなコンポーネントでは javax.swing.border.BevelBorder クラスを用いることで、コンポーネントがウィンドウから飛び出ているような、あるいは沈んでいるような印象を与えることができます。 これを斜影ボーダーと呼びます。

javax.swing.border.BevelBorder クラス
java.lang.Object
  |
  +--javax.swing.border.AbstractBorder
        |
        +--javax.swing.border.BevelBorder
public class BevelBorder extends AbstractBorder

BevelBorder クラスのコンストラクタは次のようなものが定義されています。

BevelBorder クラスのコンストラクタ
public BevelBorder(int bevelType)
public BevelBorder(int bevelType, Color highlight, Color shadow)
public BevelBorder(
	int bevelType,
	Color highlightOuterColor, Color highlightInnerColor,
	Color shadowOuterColor, Color shadowInnerColor
)

bevelType には BevelBorder クラスで公開されている静的な定数フィールドの RAISED フィールドまたは LOWERED フィールドを指定します。RAISED はくぼみ斜影を、LOWERED は浮き出し斜影を表しています。

コード2
import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;
import java.awt.event.*;

public class Test extends JComponent {
	public static void main(String args[]) {
		JFrame win = new JFrame();
		win.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
		win.setBounds(10 , 10 , 400 , 300);
		win.getContentPane().add(new Test());
		win.show();
	}

	Border normal , push;
	public Test() {
		normal = new BevelBorder(BevelBorder.RAISED);
		push = new BevelBorder(BevelBorder.LOWERED);
		setBorder(normal);

		addMouseListener(new MouseAdapter() {
			public void mousePressed(MouseEvent e) {
				setBorder(push);
			}
			public void mouseReleased(MouseEvent e) {
				setBorder(normal);
			}
		});
	}
}
実行結果
コード2 実行結果

コード2は、斜影ボーダーを用いてボタンコンポーネントのような動作を実現させています。Test クラスはボタンを実現するための JComponent クラスのサブクラスです。Test() コンストラクタではノーマルな状態とボタンを押した状態のそれぞれの Border オブジェクトを生成しています。後は、マウスリスナでマウスが押されたときと、離されたときに応じて適切な Border オブジェクトを設定するだけです。

因みに BevelBorder クラスはさらに拡張され、javax.swing.border.SoftBevelBorder クラスで角の丸い斜影ボーダーもサポートされています。

javax.swing.border.SoftBevelBorder クラス
java.lang.Object
  |
  +--javax.swing.border.AbstractBorder
        |
        +--javax.swing.border.BevelBorder
              |
              +--javax.swing.border.SoftBevelBorder
public class SoftBevelBorder extends BevelBorder

このクラスは、BevelBorder の境界線の角を丸くしたものですが、そのほかの機能は BevelBorder クラスとほとんど同じです。コード2の BevelBorder() コンストラクタの呼び出しをそのまま SoftBevelBorder() コンストラクタの呼び出しに変換して試してみると良いでしょう。

5.7.3 タイトル付きの境界線

タイトル付きの境界線を使えば、枠の一部に文字列を使ったテキストを挿入することができます。これは、ラジオボタンのような排他的な関係を持つボタンのグループを管理するコンテナなど、なんらかの関係を持つ子コンポーネント郡を管理するコンテナの簡単な説明を行うときなどに便利です。一般的には、アプリケーションの設定ダイアログなどで多く使われてます。

タイトル付きの境界線は javax.swing.border.TitledBorder クラスで実装されています。これをタイトルボーダーと呼びます。

javax.swing.border.TitledBorder クラス
java.lang.Object
  |
  +--javax.swing.border.AbstractBorder
        |
        +--javax.swing.border.TitledBorder
public class TitledBorder extends AbstractBorder

TitleBorder クラスの特徴は、他の Border オブジェクトを抱合している点です。このクラスのコンストラクタは Border クラスを受け取り、それを境界線として設定します。これに加えて、設定されている色とフォントの情報を用いてタイトルを表示するのです。このクラスのコンストラクタは次のようなものがあります。

TitleBorder クラスのコンストラクタ
public TitledBorder(String title)
public TitledBorder(Border border)
public TitledBorder(Border border, String title)
public TitledBorder(
	Border border, String title,
	int titleJustification, int titlePosition
)
public TitledBorder(
	Border border, String title,
	int titleJustification, int titlePosition, Font titleFont
)
public TitledBorder(
	Border border, String title,
	int titleJustification, int titlePosition,
	Font titleFont, Color titleColor
)

title パラメータに指定した文字列が枠の特定の部分に表示されます。このクラス自体は何らかの境界線を描画するものではなく、設定されている border パラメータに基づいて枠を描画します。border パラメータを省略した場合はデフォルトの境界線が採用されます。

titleJustification パラメータには TitledBorder クラスの静的定数フィールドを指定できます。この引数を指定することでタイトルを表示する位置をボーダーの左、中央、右のいずれかに指定できます。この引数には表2のフィールドのいずれかです。

表2 titleJustification パラメータに指定する静的定数フィールド
フィールド定数 解説
CENTER タイトルテキストをボーダラインの中央に配置する。
DEFAULT_JUSTIFICATION タイトルテキストにデフォルトの位置揃えを使用する。
LEADING 左から右方向の場合はタイトルテキストをボーダラインの左側に、 右から左方向の場合はボーダラインの右側に配置する。
LEFT タイトルテキストをボーダラインの左側に配置する。
RIGHT タイトルテキストをボーダラインの右側に配置する。
TRAILING 左から右方向の場合はタイトルテキストをボーダラインの右側に、 右から左方向の場合はボーダラインの左側に配置する。

titlePosition パラメータは、同様にタイトルの垂直位置を指定します。この引数に指定できる TitleBorder クラスの静的定数フィールドは表3に表します。

表3 titlePosition パラメータに指定する静的定数フィールド
フィールド定数 解説
ABOVE_BOTTOM タイトルをボーダのボトムラインより上に配置する。
ABOVE_TOP タイトルをボーダのトップラインより上に配置する。
BELOW_BOTTOM タイトルをボーダのボトムラインより下に配置する。
BELOW_TOP タイトルをボーダのトップラインより下に配置する。
BOTTOM タイトルをボーダのボトムラインの中央に配置する。
DEFAULT_POSITION タイトルテキストにデフォルトの垂直方向配置を使用する。
TOP タイトルをボーダのトップラインの中央に配置する。

titleFont パラメータと titleColor パラメータでタイトルに用いられるテキストのフォントとサイズを指定することができます。枠の描画位置も、このフォントサイズによって自動的に調整されます。

コード3
import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;

public class Test extends JComponent {
	public static void main(String args[]) {
		JComponent comp = new Test();
		TitledBorder border = new TitledBorder(
			new LineBorder(Color.BLUE , 5 , true) , "Kitty on your lap" ,
			TitledBorder.CENTER , TitledBorder.BOTTOM ,
			new Font("Serif" , Font.PLAIN , 20) , Color.RED
		);
		comp.setBorder(border);

		JFrame win = new JFrame();
		win.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
		win.setBounds(10 , 10 , 400 , 300);
		win.getContentPane().add(comp);
		win.show();
	}
}
実行結果
コード3 実行結果

コード3は LineBorder オブジェクトを境界線とする TitleBorder オブジェクトをコンポーネントに設定しています。すばらしいことに、タイトルボーダーは指定した位置に、指定した色とフォントでテキストを表示しながら、一方では LineBorder オブジェクトを正しく描画しています。ここに、他の Border オブジェクトを使っても問題はありません。

ここで驚かされるのは、Swing の徹底した抽象化による設計の恩恵です。境界線を描画する実体を Border インタフェースによって完全に隠蔽しているため、極めて柔軟に扱うことができ、コンポーネントの境界線を再利用することができるという生産性を実現しています。Border オブジェクトは TitleBorder クラスのように入れ子にすることができるため、特殊なテクノロジを使わなくともデザインをハイブリッドできるのです。

5.7.4 Border の合成

複数の Border オブジェクトを重ね合わせる機能があるクラスは TitleBorder だけではありません。アプリケーションプログラマが自由に境界線をハイブリッドすることができる機能を提供する javax.swing.border.CompoundBorder クラスが存在します。これを複合ボーダーと呼びます。

javax.swing.border.CompoundBorder クラス
java.lang.Object
  |
  +--javax.swing.border.AbstractBorder
        |
        +--javax.swing.border.CompoundBorder
public class CompoundBorder extends AbstractBorder

このクラスを使えば、2 つの Border オブジェクトを外側ラインと内側ラインのワンセットとして描画することができます。CompoundBorder クラスはコンストラクタから 2 つの Border オブジェクトを受け取ります。paintBorder() メソッドが呼び出されると、保有する Border オブジェクトを外側と内側に分けて適切な位置で描画します。

CompoundBorder クラスのコンストラクタ
public CompoundBorder()
public CompoundBorder(Border outsideBorder, Border insideBorder)

outsideBorder パラメータには外側の境界線を、insideBorder パラメータには内側の境界線を指定します。パラメータを受け取らないコンストラクタや、パラメータに null を指定した場合は null の境界線と判断され、その部分は描画されません。一方だけを描画する複合ボーダーを作ることも可能なのです。

コード4
import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;

public class Test extends JComponent {
	public static void main(String args[]) {
		JComponent comp = new Test();
		CompoundBorder border = new CompoundBorder(
			new LineBorder(Color.RED , 3) ,
			new EtchedBorder(EtchedBorder.RAISED)
		);
		comp.setBorder(border);

		JFrame win = new JFrame();
		win.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
		win.setBounds(10 , 10 , 400 , 300);
		win.getContentPane().add(comp);
		win.show();
	}
}
実行結果
コード4 実行結果

コード4は、複合ボーダーの外側に LineBorder を、内側に EtchedBorder を指定しています。これをコンポーネントに設定して描画すると、実行結果のように、それぞれの Border オブジェクトが外側と内側に分かれて表示されることを確認できます。

5.7.5 空の境界線

境界線を描画するための範囲は確保するが、実際には何も描画しないという奇妙なクラス javax.swing.border.EmptyBorder クラスが存在します。このクラスは、領域の全てが透明な境界線なのです。

javax.swing.border.EmptyBorder クラス
java.lang.Object
  |
  +--javax.swing.border.AbstractBorder
        |
        +--javax.swing.border.EmptyBorder
public class EmptyBorder extends AbstractBorder implements Serializable

通常、このクラスは単独で用いて意味のあるものではありません。TitleBorder クラスや CompoundBorder クラスなど、他の Border オブジェクトの機能を共有して 1 つの境界線を描画しようとするクラスと一緒に使うべきでしょう。コンストラクタは次のように定義されています。

EmptyBorder クラスのコンストラクタ
public EmptyBorder(int top, int left, int bottom, int right)
public EmptyBorder(Insets borderInsets)

これらのコンストラクタで指定して上下左右の境界線の幅がマージンとして確保されます。例えば CompoundBorder クラスで境界線の間に空間を空けたい場合に利用できるでしょう。CompoundBorder クラス自体が Border インタフェースを実装しているので、CompoundBorder に CompoundBorder オブジェクトを設定することは問題ありません。3 つの Border オブジェクトを設定し、その間のオブジェクトに EmptyBorder を設定すれば 2 つの境界線の間を空けることができます。