WisdomSoft - for your serial experiences.

10.1 メニューバー

ウィンドウ上にメニューバーとメニュー項目を表示します。メニュー項目は階層化でき、アイコンなど他の任意のコンポーネントを表示できます。

10.1.1 項目の表示

メニューバーは、そのアプリケーションが提供するサービスや機能をユーザーに列強するコンテナです。機能的にはツリーコンポーネントに似ていて、メニューバーに列挙する項目は、子メニューを保有することができます。そのため、表示する機能を階層化するなどのディレクトリ構造を実現することができます。

メニューバーは javax.swing.JMenuBar クラスで実装されています。このクラスも、システムを参照しない JComponent クラスを継承した軽量コンポーネントなので、システムが提供するメニューバーとは異なる独自のメニューバーとなるでしょう。

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

公開されている JMenuBar クラスのコンストラクタは、引数を受け取らないコンストラクタだけです。JMenuBar クラス自体は特別な処理をするのではなく、表示する子メニュー項目を管理するための機能が提供されています。メニューバーにメニュー項目を追加するには JMenuBar クラスの add() メソッドを使います。

JMenuBar クラス add() メソッド
public JMenu add(JMenu c)

c パラメータには追加するメニューコンポーネントを指定します。戻り値は、追加したメニューコンポーネント、すなわち c が返ります。すべてのメニューはインデックスで管理されるため、追加されたメニューはこのメニューバーの最後尾に配置されます。

メニューバーに表示されるメニュー項目は javax.swing.JMenu クラスを使います。JMenu クラスは、メニューの最終的な項目となるメニュー項目のリストを管理してくれます。

javax.swing.JMenu クラス
java.lang.Object
  |
  +--java.awt.Component
        |
        +--java.awt.Container
              |
              +--javax.swing.JComponent
                    |
                    +--javax.swing.AbstractButton
                          |
                          +--javax.swing.JMenuItem
                                |
                                +--javax.swing.JMenu
public class JMenu extends JMenuItem implements Accessible, MenuElement

JMenu クラスは AbstractButton から派生していることからも、ボタンのような性質であることが想像できます。実際に、メニュー項目はアイコンと表示テキストを表示することができ、メニューを選択するとコンボボックスのようにドロップダウンリストが表示され、そこにメニュー項目が列挙されます。

表1 JMenu クラスのコンストラクタとメソッド(抜粋)
コンストラクタ 解説
public JMenu() テキストのない JMenu を作成する。
public JMenu(String s) 指定されたテキストを持つ JMenu を作成する。
public JMenu(Action a) 指定された Action からプロパティを取得する JMenu を作成する。
メソッド
public JMenuItem add(Action a) Action オブジェクトに属するメニュー項目を作成し、末尾に追加する。
public JMenuItem add(String s) 指定されたテキストを持つ新しいメニュー項目を作成し、末尾に追加する。
public Component add(Component c, int index) 指定されたコンポーネントをこのコンテナの指定された位置に追加する。
public Component add(Component c) このメニューの最後にコンポーネントを追加する。
public JMenuItem add(JMenuItem menuItem) このメニューの最後にメニュー項目を追加する。
public void addSeparator() メニューの最後に新しいセパレータを追加する。
public JMenuItem insert(JMenuItem mi, int pos) 指定された JMenuItem を指定された位置に挿入する。
public JMenuItem insert(Action a, int pos) 指定された Action オブジェクトに属するメニュー項目を作成し、指定された位置に挿入する。
public void insert(String s, int pos) 指定されたテキストを持つメニュー項目を作成し、指定された位置に挿入する。
public void remove(Component c) 指定されたコンポーネント c を削除する。
public void remove(int pos) 指定されたインデックスにあるメニュー項目を削除する。
public void remove(JMenuItem item) 指定されたメニューをこのメニューから削除する。
public void removeAll() メニュー項目をすべて削除する。
public JMenuItem getItem(int pos) 指定された位置の JMenuItem を返す。
public int getItemCount() セパレータを含めたメニュー項目数を返す。

JMenu クラスの add() メソッドからメニュー項目を追加することができます。メニュー項目には Component クラスの実装を追加できるため、重量コンポーネントも含めてすべてのコンポーネントを項目とすることができます。しかし、通常は javax.swing.JMenuItem クラスのインスタンスをコンポーネントとして追加します。

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

ここで重要なのは、JMenu クラスが JMenuItem クラスを継承しているという事実です。これは、JMenu クラスが表示するメニュー項目を階層化することができるということを表しています。

表2 JMenuItem クラスのコンストラクタ
コンストラクタ 解説
public JMenuItem() テキストおよびアイコンを指定しないで JMenuItem を作成します。
public JMenuItem(Icon icon) 指定されたアイコンで JMenuItem を作成します。
public JMenuItem(String text) 指定されたテキストで JMenuItem を作成します。
public JMenuItem(Action a) 指定された Action からプロパティを取得するメニュー項目を作成します。
public JMenuItem(String text, Icon icon) 指定されたテキストおよびアイコンで JMenuItem を作成します。
public JMenuItem(String text, int mnemonic) 指定されたテキストおよびキーボードニーモニックで JMenuItem を作成します。

表2を見て分かるように、JMenuItem クラスは AbstractButton 抽象クラスを継承しているため JButton クラスに良く似ています。JMenu クラスや JMenuItem の重要な機能のほとんどは AbstractButton クラスで宣言されているメソッドを用いるため、ボタンが押されたときのイベント処理方法などは説明不要でしょう。

コード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 JMenuBar menuBar = new JMenuBar();
	public Test() {
		JMenu menu1 = new JMenu("JMenu 1");
		JMenu menu2 = new JMenu("JMenu 2");

		menu1.add("Menu String 1").addActionListener(this);
		menu1.add("Menu String 2").addActionListener(this);

		JMenuItem menuItem1 = new JMenuItem(
			"JMenuItem" , new ImageIcon("icon1.jpg")
		);
		menuItem1.addActionListener(this);
		menu2.add(menuItem1);

		JButton button = new JButton("JButton");
		button.addActionListener(this);
		menu2.add(button);

		menuBar.add(menu1);
		menuBar.add(menu2);
		getRootPane().setJMenuBar(menuBar);
	}

	public void actionPerformed(ActionEvent e) {
		AbstractButton item = (AbstractButton)e.getSource();
		JOptionPane.showMessageDialog(this, item.getText());
	}
}
実行結果
コード1 実行結果
コード1 実行結果

コード1は、単純なテキストだけのメニュー項目と、アイコン付きのメニュー項目と JButton コンポーネントをメニュー項目に持つ JMenu インスタンスを作成し、これをメニューバーに追加しています。メニューバーをウィンドウに表示するには、依然ルートペインを解説したときに説明した setJMenuBar() メソッドを使います。

JMenu クラスの add() メソッドで、文字列からメニュー項目を生成すると、メソッドの内部で JMenuItem が生成され、作成したメニュー項目を返してくれます。このプログラムでは、この add() メソッドの戻り値を利用して、生成と同時に ActionListener を項目に追加しています。このプログラムのメニュー項目を選択すると、そのメニュー項目の getText() メソッドから得られる文字列がダイアログに表示されるでしょう。

Swing のメニューバーは、プラットフォームに依存しない軽量コンポーネントで作られているということに注目してください。Windows では、アプリケーションウィンドウの上部にメニューバーが表示されますが Mac OS X では、デスクトップの上部にアクティブになっているアプリケーションのメニューバーが表示される仕組みを採用しています。しかし、Swing で実行される場合は、メニューバーも軽量コンポーネントとして Java によって描画されるため、アプリケーションウィンドウに表示されるのです。

10.1.2 メニュー項目を階層化する

JMenu クラスは JMenuItem クラスを継承しているため、JMenu クラスの add() メソッドに JMenuItem 型として JMenu インスタンスを設定することができます。JMenu が項目として追加した場合もメニューのテキストとアイコンが参照されてい表示されますが、通常の項目とは異なり、JMenu 項目を選択すると、選択された JMenu が持つメニュー項目を、さらにドロップダウンメニューリストとして表示します。そのため、メニュー項目を木構造のように階層化することができます。

コード2
import javax.swing.*;

public class Test extends JFrame {
	public static void main(String args[]) {
		JMenuBar menuBar = new JMenuBar();
		JMenu menu1 = new JMenu("JMenu 1");
		JMenu menu2 = new JMenu("JMenu 2");

		menu1.add("Menu String 1");
		menu1.addSeparator();
		menu1.add(menu2);

		menu2.add("Menu String 2");
		menu2.add("Menu String 3");

		menuBar.add(menu1);

		JFrame win = new Test();
		win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		win.setBounds(10 , 10 , 400 , 300);
		win.getRootPane().setJMenuBar(menuBar);
		win.show();
	}
}
実行結果
コード2 実行結果

コード2は、JMenu クラスのオブジェクト menu1 に、同じ JMenu クラスのオブジェクト menu2 を追加しています。JMenu クラスは JMenuItem クラスを継承してるため、メニュー項目として利用することができます。JMenu クラスによるメニュー項目を選択すると、実行結果のように、そのメニューが保有しているメニュー項目をさらに表示します。

因みに、このプログラムでは addSeparator() メソッドを使って、menu1 オブジェクトにセパレータを追加しています。セパレータは、メニュー項目とメニュー項目の間を区切る、区切り線として表示されます。

10.1.3 メニューの表示イベント

メニューに含まれているメニュー項目が何らかのデータを参照している場合や、メニューが選択された瞬間に何らかの処理を挟む、またはメニューが非選択状態になった瞬間に何らかの処理を行う場合などは、メニューリスナを利用して通知を受けると良いでしょう。メニューはドロップダウンを表示する直前や、非表示にする直前にリスナを呼び出しています。

メニューリスナは javax.swing.event.MenuListener インタフェースを実装しなければなりません。このインタフェースには、メニューが選択されたときに呼び出される menuSelected() メソッド、メニューの選択が解除されたときに呼び出される menuDeselected() メソッド、そして、メニューがキャンセルされたときに呼び出される menuCanceled() メソッドが宣言されています。

MenuListener インターフェイス menuSelected() メソッド
public void menuSelected(MenuEvent e)
MenuListener インターフェイス menuDeselected() メソッド
public void menuDeselected(MenuEvent e)
MenuListener インターフェイス menuCanceled() メソッド
public void menuCanceled(MenuEvent e)

これらのメソッドに渡される e パラメータは、javax.swing.event.MenuEvent クラスのオブジェクトです。

javax.swing.event.MenuEvent クラス
java.lang.Object
  |
  +--java.util.EventObject
        |
        +--javax.swing.event.MenuEvent
public class MenuEvent extends EventObject

このクラスの役割は、イベントを発生された源泉オブジェクトを提供するだけで、新しく公開されているメソッドはありません。

メニューリスナを用意することができれば、JMenu クラスの addMenuListener() メソッドからリスナを登録することができます。登録したリスナを解除するには removeMenuListener() メソッドを、現在登録しているメニューリスナの配列を得るには getMenuListener() メソッドを使います。

JMenu クラス addMenuListener() メソッド
public void addMenuListener(MenuListener l)
JMenu クラス removeMenuListener() メソッド
public void removeMenuListener(MenuListener l)
JMenu クラス getMenuListeners() メソッド
public MenuListener[] getMenuListeners()

メニューバーや、何らかのメニューの項目として登録されている JMenu が選択されたタイミングで処理を追加する場合は、このリスナを使うと良いでしょう。

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

public class Test extends JFrame implements MenuListener, 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 JMenu menu = new JMenu("Window");
	private JMenuItem menuItem1 = new JMenuItem("Create Frame");
	private JMenuItem menuItem2 = new JMenuItem("Delete Frame");

	public Test() {
		menu.add(menuItem1);
		menu.add(menuItem2);
		menu.addMenuListener(this);

		menuItem1.addActionListener(this);
		menuItem2.addActionListener(this);

		JMenuBar menuBar = new JMenuBar();
		menuBar.add(menu);
		getRootPane().setJMenuBar(menuBar);
		getContentPane().add(desktopPane , BorderLayout.CENTER);
	}

	public void actionPerformed(ActionEvent e) {
		if (e.getSource() == menuItem1) createFrame();
		else deleteFrame();
	}
	private void createFrame() {
		JInternalFrame frame = new JInternalFrame(
			"JInternalFrame " + wndCount++ , true , true , true ,true);

		frame.setBounds(0 , 0 , 200 , 120);
		frame.setVisible(true);
		desktopPane.add(frame);
	}
	private void deleteFrame() {
		JInternalFrame frame = desktopPane.getSelectedFrame();
		if (frame == null) return;
		frame.dispose();
	}

	public void menuSelected(MenuEvent e) {
		if (desktopPane.getComponentCount() == 0) return;

		menu.addSeparator();
		JInternalFrame [] frames = desktopPane.getAllFrames();
		for(int i = 0; i < frames.length ; i++) {
			JMenuItem item = new JMenuItem(frames[i].getTitle());
			menu.add(item);
		}
		System.out.println("menuSelected");
	}
	public void menuDeselected(MenuEvent e) {
		menu.removeAll();
		menu.add(menuItem1);
		menu.add(menuItem2);
		System.out.println("menuDeselected");
	}
	public void menuCanceled(MenuEvent e) {
		System.out.println("menuCanceled");
	}
}
実行結果
コード3 実行結果

コード3は、デスクトップペインに生成されている内部フレームと同期するメニュー項目を実現しています。「Window」メニューの「Create Frame」メニューを選択すると、デスクトップペインに新しく内部フレームが追加されます。「Delete Frame」メニューは、選択されている内部フレームを削除します。

内部フレームはユーザーによって動的に生成・削除されるため、内部フレームに対応するメニュー項目をソースコードで静的に作成することはできません。そこで、メニュー項目が表示される直前にデスクトップペインを調べ、内部フレームに対応するメニュー項目を生成し、メニューが閉じられると同時に生成したメニュー項目を破棄しています。こうすることで、常に最新のデスクトップペインに合わせてメニューを展開することができます。