9.3 スクロールペイン
9.3.1 ビューポートをバーで操作する
これまで、リストやツリー、テキストコンポーネントについて説明してきましたが、これらのコンポーネントにはスクロールバーが不足していました。リストやツリーの場合、表示する項目がコンポーネント内に表示しきれなくなった場合、当然スクロールバーを表示するべきだと考えられます。テキストコンポーネントも同様で、テキストを表示しきれなくなれば、スクロールバーを表示してテキストをスクロールできるようにしなければならないでしょう。
JViewport クラスを使えば、表示するコンポーネントを管理しながらスクロール処理を実現することができましたが、スクロールバーを追加したり、スクロールが行われたときにビューを制御するコードを記述しなければなりません。
そこで、ビューポートを内包するスクロールペインを使えば、ビューポートが表示するビューに同期して適切なスクロールバーを表示してくれます。Swing で開発するアプリケーションのほとんどは、スクロールが必要な処理をこのコンテナに丸投げすることができます。スクロールペインは 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 にスクロールバーを表示しているような関係を想像してください。
コンストラクタ | 解説 |
---|---|
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 のいずれかを指定できます。
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は、大きなイメージを表示するラベルコンポーネントをビューに設定しているスクロールペインを作成しています。スクロールペインはビューとスクロールバーを監視し、常にお互いを同期させています。スクロールバーを移動させればビューの座標がそれに合わせて移動させられます。同様に、ビューポートやビューが変化すれば、スクロールバーを同期させます。
スクロールペインを使う場合、JScrollPane クラスは自由にレイアウトマネージャを設定できない点に注意しなければなりません。スクロールペインは、常に javax.swing.ScrollPaneLayout クラスによるレイアウトが設定されています。ScrollPaneLayout クラスの詳細については省略しますが、このクラスはLayoutManager を実装するスクロールペイン専用のレイアウトマネージャです。JScrollPane クラスは setLayout() メソッドをオーバーライドして ScrollPaneLayout 型以外のオブジェクトを指定すると例外を発生させます。
9.3.2 ヘッダ
スクロールペインで用いられているレイアウトでは、コンポーネントの中央にスクロールに対応するビューを表示し、右側と下部にスクロールバーを表示していました。実は、これに加えてスクロールペインの左側と上部にヘッダと呼ばれるビューポートを追加することができます。ヘッダに表示するビューは開発者が自由に設定できますが、通常は表示するコンポーネントのスクロール状態などを説明する情報提供用のコンポーネントを表示することになるでしょう。代表的なのは、サイズなどを表す定規を表示するコンポーネントをヘッダに表示します。
スクロールペインに追加される、上部の水平なビューポートを列ヘッダと呼び、左側の垂直なビューポートを行ヘッダと呼んでいます。列ヘッダと行ヘッダは、中央のビューポートのように、JViewport を設定することもできますが、通常はビューポートに表示されるビューを設定するだけでよいでしょう。JScrollPane クラスのヘッダに関するメソッドは表2に示します。
メソッド | 解説 |
---|---|
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() | 行ヘッダを返します。 |
列ヘッダや行ヘッダにビューとなるコンポーネントを直接設定する場合、開発者はこのコンポーネントが直接表示されるのではなく、ビューポートのビューとして表示されることを忘れてはなりません。このビューポートもスクロールバー全体の制御に組み込まれ、中央のビューポートやスクロールバーに同期します。つまり、スクロールバーを使ってスクロールされると、行ヘッダや列ヘッダもこれに同期するためビューの座標が変更されるのです。通常、ヘッダに利用されるビューは、スクロールペインが表示している中央のビューと同じサイズかそれ以上のサイズとなるでしょう。
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は、5 ピクセルごとにラインを引く Ruler クラスを独自に定義し、これをスクロールペインのヘッダにビューとして設定しています。Ruler クラスのコンポーネントは、中央の JLabel コンポーネントをビューとするビューポートにサイズを合わせなければならないので、JLabel が表示するイメージを推奨サイズの参考に利用しています。スクロールバーを移動させれば、列ヘッダや行ヘッダも同期して移動することが確認できます。逆に、列ヘッダや行ヘッダのビューポートの座標を制御すれば、ヘッダを使ってスクロールさせるということも可能になるでしょう。
9.3.3 コーナー
ヘッダとスクロールバーを表示すると、スクロールペインの左上隅、右上隅、左下隅、右下隅の 4 ヶ所に矩形の空間が存在します。これらの空間のことをコーナーと呼び、スクロールペインはコーナーに何らかのコンポーネントを配置することを許可しています。デフォルトのコーナーは、スクロールペインの背景色を持つ JPanel インスタンスが設定されています。
コーナーに新しいコンポーネントを設定する場合は JScrollPane クラスの setCorner() メソッドを使います。現在コーナーに設定されているコンポーネントを取得したければ getCorner() メソッドを使うと良いでしょう。
public void setCorner(String key, Component corner)
public Component getCorner(String key)
key には、コンポーネントを表示するコーナーの位置を識別する文字列を指定します。この文字列は ScrollPaneConstants インタフェースのフィールド定数として公開されています。ただし、コーナーは極めて小さい空間なので、この空間で意味のあるコンポーネントを表示することは難しいでしょう。
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は、表示されるスクロールペインの右下端にボタンコンポーネントを配置するというプログラムです。 JScrollPane.LOWER_RIGHT_CORNER をキーとして setCorner() メソッドからコンポーネントをコーナーに表示することができます。
このプログラムのコーナーに表示されるボタンコンポーネントを押すと、actionPerformed() メソッドでスクロールペインが内包しているビューポートを操作し、スクロールバーを右下端に移動させます。ただし、スクロールバーがすでに右下端に配置されている場合は、最初の位置まで戻します。
プログラムの設計上、コーナーに配置するコンポーネントはユーザーを補助する程度のものが好ましく、プログラムの操作に必須なコンポーネントであってはいけません。コーナーのコンポーネントは必ず表示されるという保障はなく、スクロールペインの状態や、ルックアンドフィールに依存するからです。