WisdomSoft - for your serial experiences.

9.3 スクロールペイン

スクロールペインを用い、ビューポートの表示位置を調整するスクロールバーを表示します。

9.3.1 ビューポートをバーで操作する

これまで、リストやツリー、テキストコンポーネントについて説明してきましたが、これらのコンポーネントにはスクロールバーが不足していました。リストやツリーの場合、表示する項目がコンポーネント内に表示しきれなくなった場合、当然スクロールバーを表示するべきだと考えられます。テキストコンポーネントも同様で、テキストを表示しきれなくなれば、スクロールバーを表示してテキストをスクロールできるようにしなければならないでしょう。

JViewport クラスを使えば、表示するコンポーネントを管理しながらスクロール処理を実現することができましたが、スクロールバーを追加したり、スクロールが行われたときにビューを制御するコードを記述しなければなりません。

そこで、ビューポートを内包するスクロールペインを使えば、ビューポートが表示するビューに同期して適切なスクロールバーを表示してくれます。Swing で開発するアプリケーションのほとんどは、スクロールが必要な処理をこのコンテナに丸投げすることができます。スクロールペインは javax.swing.JScrollPane クラスで提供されています。

javax.swing.JScrollPane クラス
java.lang.Object
  |
  +--java.awt.Component
        |
        +--java.awt.Container
              |
              +--javax.swing.JComponent
                    |
                    +--javax.swing.JScrollPane
public class JScrollPane extends JComponent implements ScrollPaneConstants, Accessible

JScrollPane クラスは、内部にコンポーネントを表示するビューポートを管理し、ビューポートの右側と下部にスクロールバーを表示します。BorderLayout の CENTER にビューポートを、EAST と SOUTH にスクロールバーを表示しているような関係を想像してください。

表1 JScrollPane クラスのコンストラクタとメソッド(抜粋)
コンストラクタ 解説
public JScrollPane() 空のビューを持つデフォルトの JScrollPane を作成する。
public JScrollPane(int vsbPolicy, int hsbPolicy) スクロールバーポリシーを指定して空の JScrollPane を作成する。
public JScrollPane(Component view) 指定されたビューを表示する JScrollPane を作成する。
public JScrollPane(Component view, int vsbPolicy, int hsbPolicy) ビューとスクロールバーポリシーを指定して、JScrollPane を作成する。
メソッド
public void setViewportView(Component view) 必要に応じてビューポートを作成してから、ビューを設定する。
public void setViewport(JViewport viewport) 新しいビューポートを設定する。
public JViewport getViewport() 現在の JViewport を返す。
public void setHorizontalScrollBarPolicy(int policy) 水平スクロールバーがいつ表示されるかを設定する。
public int getHorizontalScrollBarPolicy() 水平スクロールバーのポリシーの値を返す。
public void setVerticalScrollBarPolicy(int policy) 垂直スクロールバーがいつ表示されるかを設定する。
public int getVerticalScrollBarPolicy() 垂直スクロールバーのポリシーの値を返す。
public void setWheelScrollingEnabled(boolean handleWheel) マウスホイールの移動に応答するかどうかを設定する。
public boolean isWheelScrollingEnabled() マウスホイールに応答してスクロールが行われるかどうかを示す。

スクロールペインが表示するビューは、このクラスのコンストラクタから指定するか、setViewportView() メソッドで指定します。JViewport コンポーネント自体を setViewport() メソッドから設定することができますが、このメソッドを使うメリットはほとんどありません。通常は setViewportView() メソッドを使ってビューを設定するだけで目的の結果を得ることができます。

コンストラクタやメソッドが指定するスクロールバーのポリシーとは、スクロールバーをどのようなタイミングで表示するを数値で指定するものです。スクロールバーのポリシーを表す値は javax.swing.ScrollPaneConstants インタフェースで定義されています。 JScrollPane クラスはこのインタフェースを実装しているため、JScrollPane クラスから値を指定することもできます。ScrollPaneConstants インタフェースは、フィールド定数を提供するためのインタフェースなので、メソッドは宣言していません。

垂直スクロールバーのポリシーに指定できる値は、必要になったときに表示する JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED、常に非表示にする JScrollPane.VERTICAL_SCROLLBAR_NEVER、常に表示する JScrollPane.VERTICAL_SCROLLBAR_ALWAYS のいずれかです。

水平スクロールバーのポリシーには、必要になったときに表示する JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED、常に非表示にする JScrollPane.HORIZONTAL_SCROLLBAR_NEVER、常に表示する JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS のいずれかを指定できます。

コード1
import javax.swing.*;

public class Test {
	public static void main(String args[]) {
		JLabel label = new JLabel(new ImageIcon("test.jpg"));
		JScrollPane scroll = new JScrollPane(label);

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

コード1は、大きなイメージを表示するラベルコンポーネントをビューに設定しているスクロールペインを作成しています。スクロールペインはビューとスクロールバーを監視し、常にお互いを同期させています。スクロールバーを移動させればビューの座標がそれに合わせて移動させられます。同様に、ビューポートやビューが変化すれば、スクロールバーを同期させます。

スクロールペインを使う場合、JScrollPane クラスは自由にレイアウトマネージャを設定できない点に注意しなければなりません。スクロールペインは、常に javax.swing.ScrollPaneLayout クラスによるレイアウトが設定されています。ScrollPaneLayout クラスの詳細については省略しますが、このクラスはLayoutManager を実装するスクロールペイン専用のレイアウトマネージャです。JScrollPane クラスは setLayout() メソッドをオーバーライドして ScrollPaneLayout 型以外のオブジェクトを指定すると例外を発生させます。

9.3.2 ヘッダ

スクロールペインで用いられているレイアウトでは、コンポーネントの中央にスクロールに対応するビューを表示し、右側と下部にスクロールバーを表示していました。実は、これに加えてスクロールペインの左側と上部にヘッダと呼ばれるビューポートを追加することができます。ヘッダに表示するビューは開発者が自由に設定できますが、通常は表示するコンポーネントのスクロール状態などを説明する情報提供用のコンポーネントを表示することになるでしょう。代表的なのは、サイズなどを表す定規を表示するコンポーネントをヘッダに表示します。

スクロールペインに追加される、上部の水平なビューポートを列ヘッダと呼び、左側の垂直なビューポートを行ヘッダと呼んでいます。列ヘッダと行ヘッダは、中央のビューポートのように、JViewport を設定することもできますが、通常はビューポートに表示されるビューを設定するだけでよいでしょう。JScrollPane クラスのヘッダに関するメソッドは表2に示します。

表2 JScrollPane クラスのヘッダに関するメソッド
メソッド 解説
public void setColumnHeaderView(Component view) ビューを設定してから、列ヘッダのビューポートを追加する。
public void setColumnHeader(JViewport columnHeader) 列ヘッダのビューポートを追加する。
public JViewport getColumnHeader() 列ヘッダを返します。
public void setRowHeaderView(Component view) ビューを設定してから、行ヘッダのビューポートを追加する。
public void setRowHeader(JViewport rowHeader) 行ヘッダのビューポートを追加する。
public JViewport getRowHeader() 行ヘッダを返します。

列ヘッダや行ヘッダにビューとなるコンポーネントを直接設定する場合、開発者はこのコンポーネントが直接表示されるのではなく、ビューポートのビューとして表示されることを忘れてはなりません。このビューポートもスクロールバー全体の制御に組み込まれ、中央のビューポートやスクロールバーに同期します。つまり、スクロールバーを使ってスクロールされると、行ヘッダや列ヘッダもこれに同期するためビューの座標が変更されるのです。通常、ヘッダに利用されるビューは、スクロールペインが表示している中央のビューと同じサイズかそれ以上のサイズとなるでしょう。

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

public class Test {
	public static void main(String args[]) {
		Icon icon = new ImageIcon("test.jpg");
		JLabel label = new JLabel(icon);

		Ruler rulerH = new Ruler();
		Ruler rulerV = new Ruler(SwingConstants.VERTICAL);
		rulerH.setPreferredSize(new Dimension(icon.getIconWidth(), 10));
		rulerV.setPreferredSize(new Dimension(10 , icon.getIconHeight()));

		JScrollPane scroll = new JScrollPane(label);
		scroll.setColumnHeaderView(rulerH);
		scroll.setRowHeaderView(rulerV);

		JFrame win = new JFrame();
		win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		win.setBounds(10 , 10 , 400 , 300);
		win.getContentPane().add(scroll);
		win.show();
	}
}

class Ruler extends JComponent implements SwingConstants  {
	private int orientation = HORIZONTAL;

	public Ruler() {}
	public Ruler(int orientation) {
		this.orientation = orientation;
	}
	public void paintComponent(Graphics g) {
		if (orientation == VERTICAL) {
			for(int i = 5 ; i < getSize().height ; i += 5) {
				if (i % 100 == 0) g.drawLine(0 , i , 10 , i);
				g.drawLine(0 , i , 5 , i);
			}
		}
		else {
			for(int i = 5 ; i < getSize().width ; i += 5) {
				if (i % 100 == 0) g.drawLine(i , 0 , i , 10);
				g.drawLine(i , 0 , i , 5);
			}
		}
	}
}
実行結果
コード2 実行結果

コード2は、5 ピクセルごとにラインを引く Ruler クラスを独自に定義し、これをスクロールペインのヘッダにビューとして設定しています。Ruler クラスのコンポーネントは、中央の JLabel コンポーネントをビューとするビューポートにサイズを合わせなければならないので、JLabel が表示するイメージを推奨サイズの参考に利用しています。スクロールバーを移動させれば、列ヘッダや行ヘッダも同期して移動することが確認できます。逆に、列ヘッダや行ヘッダのビューポートの座標を制御すれば、ヘッダを使ってスクロールさせるということも可能になるでしょう。

9.3.3 コーナー

ヘッダとスクロールバーを表示すると、スクロールペインの左上隅、右上隅、左下隅、右下隅の 4 ヶ所に矩形の空間が存在します。これらの空間のことをコーナーと呼び、スクロールペインはコーナーに何らかのコンポーネントを配置することを許可しています。デフォルトのコーナーは、スクロールペインの背景色を持つ JPanel インスタンスが設定されています。

コーナーに新しいコンポーネントを設定する場合は JScrollPane クラスの setCorner() メソッドを使います。現在コーナーに設定されているコンポーネントを取得したければ getCorner() メソッドを使うと良いでしょう。

JScrollPane クラス setCorner() メソッド
public void setCorner(String key, Component corner)
JScrollPane クラス getCorner() メソッド
public Component getCorner(String key)

key には、コンポーネントを表示するコーナーの位置を識別する文字列を指定します。この文字列は ScrollPaneConstants インタフェースのフィールド定数として公開されています。ただし、コーナーは極めて小さい空間なので、この空間で意味のあるコンポーネントを表示することは難しいでしょう。

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

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

	private JScrollPane scrollPane = new JScrollPane();
	private JButton cornerButton = new JButton();
	public Test() {
		JLabel label = new JLabel(new ImageIcon("test.jpg"));
		cornerButton.addActionListener(this);
		scrollPane.setViewportView(label);
		scrollPane.setVerticalScrollBarPolicy(
			JScrollPane.VERTICAL_SCROLLBAR_ALWAYS
		);
		scrollPane.setHorizontalScrollBarPolicy(
			JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS 
		);
		scrollPane.setCorner(
			JScrollPane.LOWER_RIGHT_CORNER, cornerButton
		);
		getContentPane().add(scrollPane);
	}

	public void actionPerformed(ActionEvent e) {
		JViewport view = scrollPane.getViewport();
		Dimension viSize = view.getViewSize();
		Dimension exSize = view.getExtentSize();
		Point pt = new Point(
			viSize.width - exSize.width , viSize.height - exSize.height
		);

		if (pt.equals(view.getViewPosition())) {
			view.setViewPosition(new Point(0 , 0));
		}
		else view.setViewPosition(pt);
	}
}
実行結果
コード3 実行結果

コード3は、表示されるスクロールペインの右下端にボタンコンポーネントを配置するというプログラムです。 JScrollPane.LOWER_RIGHT_CORNER をキーとして setCorner() メソッドからコンポーネントをコーナーに表示することができます。

このプログラムのコーナーに表示されるボタンコンポーネントを押すと、actionPerformed() メソッドでスクロールペインが内包しているビューポートを操作し、スクロールバーを右下端に移動させます。ただし、スクロールバーがすでに右下端に配置されている場合は、最初の位置まで戻します。

プログラムの設計上、コーナーに配置するコンポーネントはユーザーを補助する程度のものが好ましく、プログラムの操作に必須なコンポーネントであってはいけません。コーナーのコンポーネントは必ず表示されるという保障はなく、スクロールペインの状態や、ルックアンドフィールに依存するからです。