WisdomSoft - for your serial experiences.

9.8 デスクトップペイン

ウィンドウ内に別の子ウィンドウ(内部フレーム)を表示する方法を解説します。アプリケーション内に仮想的なデスクトップがあるかのように、複数の子ウィンドウをコンテンツとして表示できます。

9.8.1 内部フレーム

Windows 環境のビジネスアプリケーションなどで見られる MDI (Multiple Document Interface) は、アプリケーションのクライアント領域に、あたかも子アプリケーションが存在するように、新しいウィンドウを表示して制御することができる特殊なコンポーネントです。Windows では、かつて Microsoft Excel 使われて以来 Microsoft の製品で利用されていました。

MDI は、アプリケーションを複数起動することなく、単一のアプリケーション内で複数のデータを同時に扱うことができ、様々なデータに同時にアクセスできることから、編集目的のソフトウェアにはタブコンポーネントに並ぶ重要な機能でしょう。

MDI は、Swing の世界ではマルチドキュメントインタフェース、または仮想デスクトップと呼ばれています。仮想デスクトップを実現するには、子フレームを表示することができるデスクトップペインを使います。この軽量コンテナは javax.swing.JDesktopPane クラスで実装されています。

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

JDesktopPane クラスのコンストラクタは、パラメータを持たない単純なコンストラクタだけが公開されてます。JDesktopPane 自体は、子フレームとなるコンポーネント管理するためのコンテナに過ぎません。通常は、このコンテナをアプリケーションウィンドウの中央に配置します。

問題は、デスクトップペインに表示されるフレームウィンドウです。JFrame 等のクラスは JComponent を継承していない重量コンポーネントなので、子コンポーネントとして利用することはできません。JDesktopPane は通常のコンテナなので他のコンポーネントを追加することも可能ですが、一般的にこのコンテナには、仮想デスクトップ上に表示する内部ウィンドウ javax.swing.JInternalFrame クラスのオブジェクトが追加されます。  

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

JInternalFrame クラスは、JComponent を拡張する軽量のウィンドウコンテナです。このコンポーネントには一般的なウィンドウのように、サイズ変更境界線やタイトルバー、最小化ボタン、最大化ボタン、終了ボタンなどが付属しています。

最も重要なのは、JInternalFrame クラスが RootPaneContainer インタフェースを実装していることでしょう。そのため、このウィンドウは Swing 重量ウィンドウと同様にルートペインが提供されています。内部ウィンドウにメニューバーを表示させたり、子コンポーネントを追加する方法も、従来のウィンドウと同じメソッドを使って行うことができるのです。

表1 JInternalFrame クラスのコンストラクタとメソッド(抜粋).
コンストラクタ 解説
public JInternalFrame() サイズ変更、クローズ、最大化、アイコン化を行うことができない JInternalFrame を、タイトルなしで作成する。
public JInternalFrame(String title) サイズ変更、クローズ、最大化、アイコン化を行うことができない JInternalFrame を、指定されたタイトルで作成する。
public JInternalFrame(String title, boolean resizable) クローズ、最大化、アイコン化を行うことができない JInternalFrame を、タイトルと、サイズ変更可能性を指定して作成する。
public JInternalFrame(String title, boolean resizable, boolean closable) 最大化、アイコン化を行うことができない JInternalFrame を、タイトル、サイズ変更可能性、およびクローズ可能性を指定して作成する。
public JInternalFrame(String title, boolean resizable, boolean closable, boolean maximizable) アイコン化を行うことができない JInternalFrame を、タイトル、サイズ変更可能性、クローズ可能性、および最大化可能性を指定して作成する。
public JInternalFrame(String title, boolean resizable, boolean closable, boolean maximizable, boolean iconifiable) JInternalFrame を、タイトル、サイズ変更可能性、クローズ可能性、最大化可能性、およびアイコン化可能性を指定して作成する。
メソッド
public void setTitle(String title) フレームのタイトルを設定する。
public String getTitle() フレームのタイトルを返す。
public void setResizable(boolean b) ユーザーが、フレームのサイズ変更ができるかどうかを設定する。
public boolean isResizable() ユーザーが、フレームのサイズ変更できるかどうかを返す。
public void setClosable(boolean b) ユーザーが、フレームを閉じることができるかどうかを設定する。
public boolean isClosable() ユーザーが、フレームを閉じることができるかどうかを返す。
public void setMaximizable(boolean b) ユーザーが、フレームを最大化できるかどうかを設定する。
public boolean isMaximizable() ユーザーが、フレームを最大化できるかどうかを返す。
public void setIconifiable(boolean b) ユーザーが、フレームをアイコン化できるかどうかを設定する。
public boolean isIconifiable() ユーザーが、フレームをアイコン化できるかどうかを返す。

表1は、JInternalFrame クラスのコンストラクタトと、コンストラクタのパラメータに関係するプロパティのアクセッサメソッドです。JInternalFrame クラスは、デフォルトでユーザーが自由にフレームのサイズを変更したり、閉じたりできないように設定されていますが、これらのメソッドやコンストラクタから true を設定することで、ユーザーがウィンドウを制御することができます。因みに、アイコン化とは仮想デスクトップの端にアイコンとして配置するウィンドウの最小化を意味しています。

JInternalFrame は軽量コンポーネントなので、コンテナに配置することができますが、このコンポーネントは JDesktopPane 専用のコンポーネントだと考えてください。このコンポーネントのメソッドなど、多くの機能が JDesktopPane の子になることを前提としています。

JDesktopPane クラスはデフォルトでレイアウトマネージャが null に設定されています。そのため、レイアウトマネージャを設定するか、JInternalFrame の位置とサイズをプログラムから明示的に指定しなければ正しく表示されません。また、JInternalFrame クラスはデフォルトで非表示に設定されています。仮想デスクトップに追加した後、表示する場合は setVisible() メソッドから true を指定する必要があるでしょう。

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

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 int wndCount = 0;
	private Point pt = new Point(0 , 0);
	private JDesktopPane desktopPane = new JDesktopPane();
	public Test() {
		JButton frameButton = new JButton("フレームを生成する");
		frameButton.addActionListener(this);

		getContentPane().add(frameButton , BorderLayout.NORTH);
		getContentPane().add(desktopPane , BorderLayout.CENTER);
	}

	public void actionPerformed(ActionEvent e) {
		JInternalFrame frame = new JInternalFrame(
			"JInternalFrame " + wndCount++ , true , true , true ,true);

		if (pt.x > getWidth()) pt.x = 0;
		if (pt.y > getHeight()) pt.y = 0;

		frame.setBounds(pt.x , pt.y , 400 , 200);
		frame.setVisible(true);

		pt.x += 10;
		pt.y += 10;

		desktopPane.add(frame);
	}
}
実行結果
コード1 実行結果

コード1は、ウィンドウの上部に表示されている「フレームを生成する」ボタンを押すと、ウィンドウ全体に表示されている仮想デスクトップに新しい JInternalFrame が追加されるというプログラムです。このプログラムでは、アプリケーションウィンドウの中央に JDesktopPane コンテナを表示しています。このコンテナに JInternalFrame を表示状態で追加すれば、実行結果のようにウィンドウが表示されます。このウィンドウは JDesktopPane コンテナの範囲で、自由に移動させることができます。

因みに、閉じられた内部フレームは自動的に破棄されます。この設定を変更し、閉じられてもインスタンス自体は破棄しないようにプログラムしたい場合は JInternalFrame クラスの setDefaultCloseOperation() メソッドを使います。現在の終了方法を取得するには getDefaultCloseOperation() メソッドを使います。

JInternalFrame クラス setDefaultCloseOperation() メソッド
public void setDefaultCloseOperation(int operation)
JInternalFrame クラス getDefaultCloseOperation() メソッド
public int getDefaultCloseOperation()

これらのメソッドが扱う数値は WindowConstants インタフェースで宣言されているフィールド定数のうち、何も行わないことを表す DO_NOTHING_ON_CLOSE フィールド、不可視状態にする HIDE_ON_CLOSE フィールド、そして、ウィンドウを破棄することを表す DISPOSE_ON_CLOSE フィールドのいずれかを指定します。

9.8.2 内部フレームリスナ

仮想デスクトップに表示される内部フレーム JInternalFrame クラスのコンポーネントは、プログラムやユーザーのアクションによってサイズを変更したり、移動、アイコン化、最大化、または閉じるなどの要求を受けることがあります。これらの機能はすべて仮想デスクトップと内部フレームが補ってくれますが、これらの処理タイミングで何らかのコードを実行したり、内部フレームの動作を監視したい場合は JInternalFrame にリスナを追加して通知を受ける必要があります。

JInternalFrame クラスは、内部フレームの起動や終了のタイミングで通知するリスナ javax.swing.event.InternalFrameListener インタフェースを追加することができます。このインタフェースで宣言されているメソッドは、表2を参照してください。

表2 InternalFrameListener インタフェースのメソッド
メソッド 解説
public void internalFrameActivated(InternalFrameEvent e) 内部フレームが起動されたときに呼び出される。
public void internalFrameDeactivated(InternalFrameEvent e) 内部フレームが終了されたときに呼び出される。
public void internalFrameIconified(InternalFrameEvent e) 内部フレームがアイコン化されたときに呼び出される。
public void internalFrameDeiconified(InternalFrameEvent e) 内部フレームがアイコン化解除されたときに呼び出される。
public void internalFrameClosing(InternalFrameEvent e) 内部フレームがクローズ処理中のときに呼び出される。
public void internalFrameClosed(InternalFrameEvent e) 内部フレームがクローズされたときに呼び出される。
public void internalFrameOpened(InternalFrameEvent e) 内部フレームがオープンされたときに呼び出される。

InternalFrameListener で宣言されているメソッドが受けるのは javax.swing.event.InternalFrameEvent クラスのオブジェクトです。このイベントオブジェクトは、イベントを発生された JInternalFrame オブジェクトをリスナに提供します。

Item name...
java.lang.Object
  |
  +--java.util.EventObject
        |
        +--java.awt.AWTEvent
              |
              +--javax.swing.event.InternalFrameEvent
public class InternalFrameEvent extends AWTEvent

InternalFrameEvent クラスで定義されている新しいメソッドは、イベント発生元の内部フレームを提供する getInternalFrame() メソッドだけです。

InternalFrameEvent クラス getInternalFrame() メソッド
public JInternalFrame getInternalFrame()

InternalFrameListener を JInternalFrame クラスに追加するには addInternalFrameListener() メソッドを使います。登録しているリスナを削除するには removeInternalFrameListener() メソッドを、現在登録されているリスナを得るには getInternalFrameListeners() メソッドを使います。

JInternalFrame クラス addInternalFrameListener() メソッド
public void addInternalFrameListener(InternalFrameListener l)
JInternalFrame クラス removeInternalFrameListener() メソッド
public void removeInternalFrameListener(InternalFrameListener l)
JInternalFrame クラス getInternalFrameListeners() メソッド
public InternalFrameListener[] getInternalFrameListeners()

内部フレームを動的に生成するようなシステムで、個々のフレームの終了処理などを総合的に管理したい場合などで利用することができるでしょう。内部フレームを制御するコンポーネントなどと同期させたい場合、フレームの監視を行うなどの利用方法が考えられます。

9.8.3 内部フレームの制御

内部フレームは、JDesktopPane の子コンポーネントとして配置されている場合は、ユーザーがある程度自由に制御することができます。しかし、アイコン化や最大化などのフレームの動作は、プログラムからも制御できなければなりません。内部フレームをプログラムから制御するためのメソッドは、表3に列挙します。

表3 JInternalFrame クラスの制御に関するメソッド(抜粋)
メソッド 解説
public void setIcon(boolean b) throws PropertyVetoException フレームをアイコン化またはアイコン化解除する。
public boolean isIcon() フレームがアイコン化されているかどうかを返す。
public void setMaximum(boolean b) throws PropertyVetoException フレームを最大化または復元する。
public boolean isMaximum() フレームが最大化されているかどうかを返す。
public void setSelected(boolean selected) throws PropertyVetoException フレームを選択または選択解除する。
public boolean isSelected() フレームが選択されているかどうかを返す。
public void setClosed(boolean b) throws PropertyVetoException 引数が true の場合はこの内部フレームを閉じる。
public boolean isClosed() フレームが閉じているかどうかを返す。
public void show() フレームが不可視の場合、前面に移動して可視にし選択する。
public void dispose() フレームを不可視、選択解除にして閉じる。
public void toFront() フレームを前面に移動する。
public void toBack() フレームを背面に移動する。

表3に示したメソッドを使えばプログラムからフレームを制御することができるようになります。他のコンポーネントとフレームの動作が同期するようなプログラムには、これらのメソッドが必要になるでしょう。

フレームの状態を表すプロパティを変更するアクセッサメソッドは、PropertyVetoException という例外を発生させる可能性があります。これらの例外は、プロパティ変更の要求に対して、リスナがプロパティ変更を拒否した場合に発生します。

JDesktopPane に表示されているフレームのうち、選択されているフレームは常に 1 つだけです。通常は、選択されているフレームと選択されていないフレームは、タイトルバーの色が異なるなど、外見的にも差別化されます。内部フレームのオブジェクトを保有していれば、任意のフレームを捜査することができますが、ボタンなどからフレームを操作するプログラムのほとんどは、現在選択されてるフレームに対して行われるはずです。

JDesktopPane が選択している内部フレームを取得するには JDesktopPane クラスの getSelectedFrame() メソッドを使います。逆に、プログラムから特定の内部フレームを選択する場合は setSelectedFrame() メソッドから選択することができます。

JDesktopPane クラス setSelectedFrame() メソッド
public void setSelectedFrame(JInternalFrame f)
JDesktopPane クラス getSelectedFrame() メソッド
public JInternalFrame getSelectedFrame()

JInternalFrame の配列を管理して、繰り返し処理で現在選択されているフレームを探すような不毛な処理を記述しなくても、JDesktopPnae オブジェクトがあれば、操作対象としているフレームを得ることができます。ただし、フレームが存在しない場合や、どのフレームも選択されていなければ getSelectedFrame() は null を返します。

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

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 int wndCount = 0;
	private JDesktopPane desktopPane = new JDesktopPane();
	private JButton createFrameButton = new JButton("フレーム生成");
	private JButton iconButton = new JButton("最小化");
	private JButton maximumButton = new JButton("最大化");

	public Test() {
		JPanel panel = new JPanel();
		panel.setLayout(new FlowLayout());
		panel.add(createFrameButton);
		panel.add(iconButton);
		panel.add(maximumButton);

		createFrameButton.addActionListener(this);
		iconButton.addActionListener(this);
		maximumButton.addActionListener(this);

		getContentPane().add(panel , BorderLayout.NORTH);
		getContentPane().add(desktopPane , BorderLayout.CENTER);
	}
	public void actionPerformed(ActionEvent e) {
		if (e.getSource() == createFrameButton) createFrame();
		else if (e.getSource() == iconButton) iconFrame();
		else if (e.getSource() == maximumButton) maximumFrame();
	}

	public void createFrame() {
		JInternalFrame frame = new JInternalFrame(
			"JInternalFrame " + wndCount++ , true , true , true ,true);
		frame.setBounds(0 , 0 , 400 , 200);
		frame.show();
		desktopPane.add(frame);
	}
	public void iconFrame() {
		JInternalFrame frame = desktopPane.getSelectedFrame();
		if (frame == null) return;
		try { frame.setIcon(!frame.isIcon()); }
		catch(Exception err) { /*変更が拒否された処理*/ }
	}
	public void maximumFrame() {
		JInternalFrame frame = desktopPane.getSelectedFrame();
		if (frame == null) return;
		try { frame.setMaximum(!frame.isMaximum()); }
		catch(Exception err) { /*変更が拒否された処理*/ }
	}
}
実行結果
コード2 実行結果

コード2は、ウィンドウ上部に表示されている「最小化」ボタンや「最大化」ボタンを押すと、現在選択されている内部フレームがアイコン化されたり、最大化されるというプログラムです。すでに最大化されているフレームに対してボタンを押した場合はフレームを元に戻します。

このプログラムは、「フレーム生成」ボタンを押すことで動的に内部フレームが生成されるため、配列で個々のフレームを管理するよりも、JDesktopPane が選択しているフレームだけを対象に操作するような仕組みがスマートなのです。これは、多くのマルチドキュメントインタフェースのアプリケーションで採用されている方法です。