ドキュメント



JavaFX: JavaFX UIコンポーネントの操作

24 メニュー

この章では、メニューおよびメニュー・バーの作成、メニュー項目の追加、カテゴリへのメニューのグループ化、サブメニューの作成、およびコンテキスト・メニューの設定の方法について説明します。

JavaFX APIの次のクラスを使用して、JavaFXアプリケーション内でメニューをビルドできます。

  • MenuBar

  • MenuItem

    • Menu

    • CheckMenuItem

    • RadioMenuItem

    • Menu

    • CustomMenuItem

      • SeparatorMenuItem

  • ContextMenu

図24-1に、一般的なメニュー・バーがあるアプリケーションのスクリーン・キャプチャを示します。

図24-1 メニュー・バーと3つのメニュー・カテゴリがあるアプリケーション

図24-1の説明が続きます
「図24-1 メニュー・バーと3つのメニュー・カテゴリがあるアプリケーション」の説明

JavaFXアプリケーションでのメニューのビルド

メニューは、ユーザーのリクエストに応じて表示できるアクション可能な項目のリストです。メニューが表示されている場合、一度に1つのメニュー項目を選択できます。項目をクリックした後、メニューは非表示モードに戻ります。メニューを使用することにより、常時表示する必要がない機能をメニュー内に配置して、アプリケーション・ユーザー・インタフェース(UI)内のスペースを節約できます。

通常、メニュー・バー内のメニューはカテゴリにグループ化されています。コーディング・パターンとしては、メニュー・バーを宣言し、カテゴリ・メニューを定義し、カテゴリ・メニューにメニュー項目を移入します。JavaFXアプリケーション内でメニューをビルドする場合、次のメニュー項目クラスを使用します。

  • MenuItem - 1つのアクション可能なオプションを作成します

  • Menu - サブメニューを作成します

  • RadioButtonItem - 相互に排他的な選択肢を作成します

  • CheckMenuItem - 選択状態と選択解除状態を切り替えることができるオプションを作成します

1つのカテゴリ内のメニュー項目を区切るには、SeparatorMenuItemクラスを使用します。

通常、メニュー・バーでカテゴリによって構成されたメニューはウィンドウの上部に配置され、シーンの残りの部分は重要なUI要素のために残されます。なんらかの理由によってUIの可視部分をメニュー・バーに割り当てることができない場合は、ユーザーがマウス・クリックで開くコンテキスト・メニューを使用できます。

メニュー・バーの作成

メニュー・バーはユーザー・インタフェース内の任意の場所に配置できますが、通常は、ウィンドウの上部に配置され、1つ以上のメニューが含まれます。メニュー・バーはアプリケーション・ウィンドウの幅に合せて自動的にサイズが変更されます。デフォルトでは、メニュー・バーに追加される各メニューは、テキスト値を持つボタンによって表されます。

植物に関する参照情報(名前、分類学上の名前、画像および簡単な説明など)をレンダリングするアプリケーションについて考えてみます。「File」、「Edit」および「View」という3つのメニュー・カテゴリを作成し、これらにメニュー項目を移入できます。例24-1に、メニュー・バーが追加されたこのようなアプリケーションのソース・コードを示します。

例24-1 メニューのサンプル・アプリケーション

import java.util.AbstractMap.SimpleEntry;
import java.util.Map.Entry;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.effect.DropShadow;
import javafx.scene.effect.Effect;
import javafx.scene.effect.Glow;
import javafx.scene.effect.SepiaTone;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
 
public class MenuSample extends Application {
 
    final PageData[] pages = new PageData[] {
        new PageData("Apple",
            "The apple is the pomaceous fruit of the apple tree, species Malus "
            + "domestica in the rose family (Rosaceae). It is one of the most "
            + "widely cultivated tree fruits, and the most widely known of "
            + "the many members of genus Malus that are used by humans. "
            + "The tree originated in Western Asia, where its wild ancestor, "
            + "the Alma, is still found today.",
            "Malus domestica"),
        new PageData("Hawthorn",
            "The hawthorn is a large genus of shrubs and trees in the rose "
            + "family, Rosaceae, native to temperate regions of the Northern "
            + "Hemisphere in Europe, Asia and North America. "
            + "The name hawthorn was "
            + "originally applied to the species native to northern Europe, "
            + "especially the Common Hawthorn C. monogyna, and the unmodified "
            + "name is often so used in Britain and Ireland.",
            "Crataegus monogyna"),
        new PageData("Ivy",
            "The ivy is a flowering plant in the grape family (Vitaceae) native to"
            + " eastern Asia in Japan, Korea, and northern and eastern China. "
            + "It is a deciduous woody vine growing to 30 m tall or more given "
            + "suitable support,  attaching itself by means of numerous small "
            + "branched tendrils tipped with sticky disks.",
            "Parthenocissus tricuspidata"),
        new PageData("Quince",
            "The quince is the sole member of the genus Cydonia and is native to "
            + "warm-temperate southwest Asia in the Caucasus region. The "
            + "immature fruit is green with dense grey-white pubescence, most "
            + "of which rubs off before maturity in late autumn when the fruit "
            + "changes color to yellow with hard, strongly perfumed flesh.",
            "Cydonia oblonga")
    };
 
    final String[] viewOptions = new String[] {
        "Title", 
        "Binomial name", 
        "Picture", 
        "Description"
    };
 
    final Entry<String, Effect>[] effects = new Entry[] {
        new SimpleEntry<>("Sepia Tone", new SepiaTone()),
        new SimpleEntry<>("Glow", new Glow()),
        new SimpleEntry<>("Shadow", new DropShadow())
    };
 
    final ImageView pic = new ImageView();
    final Label name = new Label();
    final Label binName = new Label();
    final Label description = new Label();
 
    public static void main(String[] args) {
        launch(args);
    }
 
    @Override
    public void start(Stage stage) {
        stage.setTitle("Menu Sample");
        Scene scene = new Scene(new VBox(), 400, 350);
         
        MenuBar menuBar = new MenuBar();
 
        // --- Menu File
        Menu menuFile = new Menu("File");
 
        // --- Menu Edit
        Menu menuEdit = new Menu("Edit");
 
        // --- Menu View
        Menu menuView = new Menu("View");
 
        menuBar.getMenus().addAll(menuFile, menuEdit, menuView);
 
 
        ((VBox) scene.getRoot()).getChildren().addAll(menuBar);
 
        stage.setScene(scene);
        stage.show();
    }
 
 
    private class PageData {
        public String name;
        public String description;
        public String binNames;
        public Image image;
        public PageData(String name, String description, String binNames) {
            this.name = name;
            this.description = description;
            this.binNames = binNames;
            image = new Image(getClass().getResourceAsStream(name + ".jpg"));
        }
    }
}

他のUIコントロールとは異なり、MenuクラスおよびMenuItemクラスの他の拡張機能によってNodeクラスは拡張されません。これらは、アプリケーション・シーンに直接追加することはできず、getMenusメソッドを介してメニュー・バーに追加されるまでは表示されません。

図24-2 アプリケーションに追加されたメニュー・バー

図24-2の説明が続きます
「図24-2 アプリケーションに追加されたメニュー・バー」の説明

メニュー内を移動するには、キーボードの矢印キーを使用します。ただし、メニューを選択しても、メニューの動作がまだ定義されていないため、アクションは実行されません。

メニュー項目の追加

次の項目を追加することにより、「File」メニューの機能を設定します。

  • Shuffle - 植物に関する参照情報をロードします

  • Clear - 参照情報を削除し、シーンをクリアします

  • Separator - メニュー項目をデタッチします

  • Exit - アプリケーションを終了します

例24-2の太字の行では、MenuItemクラスを使用して「Shuffle」メニューを作成し、アプリケーション・シーンにグラフィカル・コンポーネントを追加しています。MenuItemクラスを使用すると、テキストとグラフィックを使用してアクション可能な項目を作成できます。ユーザーがクリックしたときに実行されるアクションは、Buttonクラスと同じように、setOnActionメソッドによって定義されます。

例24-2 グラフィックを使用した「Shuffle」メニュー項目の追加

import java.util.AbstractMap.SimpleEntry;
import java.util.Map.Entry;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.effect.DropShadow;
import javafx.scene.effect.Effect;
import javafx.scene.effect.Glow;
import javafx.scene.effect.SepiaTone;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.TextAlignment;
import javafx.stage.Stage;
 
public class MenuSample extends Application {
 
    final PageData[] pages = new PageData[] {
        new PageData("Apple",
            "The apple is the pomaceous fruit of the apple tree, species Malus "
            +"domestica in the rose family (Rosaceae). It is one of the most "
            +"widely cultivated tree fruits, and the most widely known of "
            +"the many members of genus Malus that are used by humans. "
            +"The tree originated in Western Asia, where its wild ancestor, "
            +"the Alma, is still found today.",
            "Malus domestica"),
        new PageData("Hawthorn",
            "The hawthorn is a large genus of shrubs and trees in the rose "
            + "family, Rosaceae, native to temperate regions of the Northern "
            + "Hemisphere in Europe, Asia and North America. "
            + "The name hawthorn was "
            + "originally applied to the species native to northern Europe, "
            + "especially the Common Hawthorn C. monogyna, and the unmodified "
            + "name is often so used in Britain and Ireland.",
            "Crataegus monogyna"),
        new PageData("Ivy",
            "The ivy is a flowering plant in the grape family (Vitaceae) native"
            +" to eastern Asia in Japan, Korea, and northern and eastern China."
            +" It is a deciduous woody vine growing to 30 m tall or more given "
            +"suitable support,  attaching itself by means of numerous small "
            +"branched tendrils tipped with sticky disks.",
            "Parthenocissus tricuspidata"),
        new PageData("Quince",
            "The quince is the sole member of the genus Cydonia and is native"
            +" to warm-temperate southwest Asia in the Caucasus region. The "
            +"immature fruit is green with dense grey-white pubescence, most "
            +"of which rubs off before maturity in late autumn when the fruit "
            +"changes color to yellow with hard, strongly perfumed flesh.",
            "Cydonia oblonga")
    };
 
    final String[] viewOptions = new String[] {
        "Title", 
        "Binomial name", 
        "Picture", 
        "Description"
    };
 
    final Entry<String, Effect>[] effects = new Entry[] {
        new SimpleEntry<>("Sepia Tone", new SepiaTone()),
        new SimpleEntry<>("Glow", new Glow()),
        new SimpleEntry<>("Shadow", new DropShadow())
    };
 
    final ImageView pic = new ImageView();
    final Label name = new Label();
    final Label binName = new Label();
    final Label description = new Label();
    private int currentIndex = -1;
 
    public static void main(String[] args) {
        launch(args);
    }
 
    @Override
    public void start(Stage stage) {
        stage.setTitle("Menu Sample");
        Scene scene = new Scene(new VBox(), 400, 350);
        scene.setFill(Color.OLDLACE);
 
        name.setFont(new Font("Verdana Bold", 22));
        binName.setFont(new Font("Arial Italic", 10));
        pic.setFitHeight(150);
        pic.setPreserveRatio(true);
        description.setWrapText(true);
        description.setTextAlignment(TextAlignment.JUSTIFY);
 
        shuffle();
 
        MenuBar menuBar = new MenuBar();
 
        final VBox vbox = new VBox();
        vbox.setAlignment(Pos.CENTER);
        vbox.setSpacing(10);
        vbox.setPadding(new Insets(0, 10, 0, 10));
        vbox.getChildren().addAll(name, binName, pic, description);
 
        // --- Menu File
        Menu menuFile = new Menu("File");
        MenuItem add = new MenuItem("Shuffle",
            new ImageView(new Image("menusample/new.png")));
        add.setOnAction((ActionEvent t) -> {
            shuffle();
            vbox.setVisible(true);
        });        
 
        menuFile.getItems().addAll(add);
 
        // --- Menu Edit
        Menu menuEdit = new Menu("Edit");
        
        // --- Menu View
        Menu menuView = new Menu("View");
        
        menuBar.getMenus().addAll(menuFile, menuEdit, menuView);
        ((VBox) scene.getRoot()).getChildren().addAll(menuBar, vbox);
        stage.setScene(scene);
        stage.show();
    }
 
    private void shuffle() {
        int i = currentIndex;
        while (i == currentIndex) {
            i = (int) (Math.random() * pages.length);
        }
        pic.setImage(pages[i].image);
        name.setText(pages[i].name);
        binName.setText("(" + pages[i].binNames + ")");
        description.setText(pages[i].description);
        currentIndex = i;
    }
 
    
    private class PageData {
        public String name;
        public String description;
        public String binNames;
        public Image image;
        public PageData(String name, String description, String binNames) {
            this.name = name;
            this.description = description;
            this.binNames = binNames;
            image = new Image(getClass().getResourceAsStream(name + ".jpg"));
        }
    }
}

ユーザーが「Shuffle」メニュー項目を選択すると、setOnAction内でコールされたshuffleメソッドにより、対応する配列内の要素の索引が計算され、植物のタイトル、分類学上の名前、画像および説明が指定されます。

「Clear」メニュー項目は、アプリケーション・シーンを削除するために使用されます。これを実装するには、例24-3に示すように、GUI要素があるVBoxを非表示にします。

例24-3 アクセラレータを使用した「Clear」メニュー項目の作成

MenuItem clear = new MenuItem("Clear");
clear.setAccelerator(KeyCombination.keyCombination("Ctrl+X"));
clear.setOnAction((ActionEvent t) -> {
    vbox.setVisible(false);
});

MenuItemクラスを実装することにより、開発者は、メニュー・アクセラレータや、メニュー項目と同じアクションを実行するキーの組合せを設定できるようになります。「Clear」メニューを使用すると、ユーザーは、「File」メニュー・カテゴリからアクションを選択したり、コントロール・キーと[X]キーを同時に押すことができるようになります。

「Exit」メニューを使用すると、アプリケーション・ウィンドウが閉じます。例24-4に示すように、このメニュー項目のアクションとしてSystem.exit(0)を設定します。

例24-4 「Exit」メニュー項目の作成

MenuItem exit = new MenuItem("Exit");
exit.setOnAction((ActionEvent t) -> {
    System.exit(0);
});

例24-5に示すgetItemsメソッドを使用して、新しく作成したメニュー項目を「File」メニューに追加します。セパレータ・メニュー項目を作成し、getItemsメソッド内に追加することにより、「Exit」メニュー項目を視覚的にデタッチできます。

例24-5 メニュー項目の追加

menuFile.getItems().addAll(add, clear, new SeparatorMenuItem(), exit);

例24-2例24-3例24-4および例24-5を「Menu Sample」アプリケーションに追加してから、コンパイルして実行します。「Shuffle」メニュー項目を選択し、様々な植物に関する参照情報をロードします。次に、シーンをクリアし(Clear)、アプリケーションを閉じます(Exit)。図24-3に、「Clear」メニュー項目の選択を示します。

図24-3 3つのメニュー項目がある「File」メニュー

図24-3の説明が続きます
「図24-3 3つのメニュー項目がある「File」メニュー」の説明

「View」メニューを使用すると、参照情報の要素の表示と非表示を切り替えることができます。createMenuItemメソッドを実装し、startメソッド内でコールし、4つのCheckMenuItemオブジェクトを作成します。次に、新しく作成したチェック・マーク項目を「View」メニューに追加し、植物のタイトル、分類学上の名前、画像および説明の可視性を切り替えます。例24-6に、これらのタスクを実装するコード・フラグメントを示します。

例24-6 CheckMenuItemクラスの適用による切替えオプションの作成

// --- Creating four check menu items within the start method
CheckMenuItem titleView = createMenuItem ("Title", name);                                                       
CheckMenuItem binNameView = createMenuItem ("Binomial name", binName);        
CheckMenuItem picView = createMenuItem ("Picture", pic);        
CheckMenuItem descriptionView = createMenuItem ("Description", description);     
menuView.getItems().addAll(titleView, binNameView, picView, descriptionView);

...

// The createMenuItem method
private static CheckMenuItem createMenuItem (String title, final Node node){
    CheckMenuItem cmi = new CheckMenuItem(title);
    cmi.setSelected(true);
    cmi.selectedProperty().addListener(
        (ObservableValue<? extends Boolean> ov, Boolean old_val, 
        Boolean new_val) -> {
            node.setVisible(new_val);
    });              
    return cmi;
}

CheckMenuItemクラスは、MenuItemの拡張機です。これは、選択済状態と選択解除済状態を切り替えることができます。選択済の場合、チェック・マーク項目によってチェック・マークが表示されます。

例24-6では、4つのCheckMenuItemオブジェクトを作成し、それらのselectedPropertyプロパティの変更を処理しています。たとえば、ユーザーがpicView項目の選択を解除すると、setVisibleメソッドはfalse値を受け取り、植物の画像は非表示になります。このコードをアプリケーションのフラグメントに追加し、アプリケーションをコンパイルして実行すると、メニュー項目の選択と選択解除を試すことができます。図24-4に、植物のタイトルと画像は表示されているが、分類学上の名前と説明は表示されていない状態のアプリケーションを示します。

図24-4 チェック・メニュー項目の使用

図24-4の説明が続きます
「図24-4 チェック・メニュー項目の使用」の説明

サブメニューの作成

「Edit」メニューの場合、「Picture Effect」および「No Effects」の2つのメニュー項目を定義します。「Picture Effect」メニュー項目は、使用可能な3つの視覚効果の1つを設定するための項目が3つあるサブメニューとして設計されます。「No Effects」メニュー項目により、選択した効果を削除し、イメージの初期状態をリストアします。

RadioMenuItemクラスを使用して、サブメニューの項目を作成します。ラジオ・メニュー・ボタンをトグル・グループに追加し、選択を相互に排他的にします。例24-7では、これらのタスクを実装しています。

例24-7 ラジオ・メニュー項目を使用したサブメニューの作成

//Picture Effect menu
Menu menuEffect = new Menu("Picture Effect");
final ToggleGroup groupEffect = new ToggleGroup();
for (Entry<String, Effect> effect : effects) {
    RadioMenuItem itemEffect = new RadioMenuItem(effect.getKey());
    itemEffect.setUserData(effect.getValue());
    itemEffect.setToggleGroup(groupEffect);
    menuEffect.getItems().add(itemEffect);
}
//No Effects menu
final MenuItem noEffects = new MenuItem("No Effects");

noEffects.setOnAction((ActionEvent t) -> {
    pic.setEffect(null);
    groupEffect.getSelectedToggle().setSelected(false);
});

//Processing menu item selection
groupEffect.selectedToggleProperty().addListener(new ChangeListener<Toggle>() {
    public void changed(ObservableValue<? extends Toggle> ov,
        Toggle old_toggle, Toggle new_toggle) {
            if (groupEffect.getSelectedToggle() != null) {
                Effect effect = 
                    (Effect) groupEffect.getSelectedToggle().getUserData();
                pic.setEffect(effect);
            }
        }
 });
groupEffect.selectedToggleProperty().addListener(
    (ObservableValue<? extends Toggle> ov, Toggle old_toggle, 
    Toggle new_toggle) -> {
        if (groupEffect.getSelectedToggle() != null) {
             Effect effect = 
                 (Effect) groupEffect.getSelectedToggle().getUserData();
             pic.setEffect(effect);
        }
});

//Adding items to the Edit menu
menuEdit.getItems().addAll(menuEffect, noEffects);

setUserDataメソッドにより、特定のラジオ・メニュー項目の視覚効果を定義しています。トグル・グループ内の項目の1つを選択すると、対応する効果が画像に適用されます。「No Effects」メニュー項目を選択すると、setEffectにより、null値が指定され、画像に効果が適用されません。

図24-5は、ユーザーが「Shadow」メニュー項目を選択する瞬間を捉えています。

図24-5 3つのラジオ・メニュー項目があるサブメニュー

図24-5の説明が続きます
「図24-5 3つのラジオ・メニュー項目があるサブメニュー」の説明

DropShadow効果を画像に適用すると、図24-6に示すようになります。

図24-6 DropShadow効果が適用されたQuinceの画像

図24-6の説明が続きます
「図24-6 DropShadow効果が適用されたQuinceの画像」の説明

MenuItemクラスのsetDisableメソッドを使用すると、「Picture Effect」サブメニューで効果が選択されていないときに「No Effects」メニューを無効にできます。例24-8に示すように、例24-7を変更します。

例24-8 メニュー項目の無効化

Menu menuEffect = new Menu("Picture Effect");
final ToggleGroup groupEffect = new ToggleGroup();
for (Entry<String, Effect> effect : effects) {
     RadioMenuItem itemEffect = new RadioMenuItem(effect.getKey());
     itemEffect.setUserData(effect.getValue());
     itemEffect.setToggleGroup(groupEffect);
     menuEffect.getItems().add(itemEffect);
}
final MenuItem noEffects = new MenuItem("No Effects");
noEffects.setDisable(true);
noEffects.setOnAction((ActionEvent t) -> {
    pic.setEffect(null);
    groupEffect.getSelectedToggle().setSelected(false);
    noEffects.setDisable(true);
});

groupEffect.selectedToggleProperty().addListener(
    (ObservableValue<? extends Toggle> ov, Toggle old_toggle, 
    Toggle new_toggle) -> {
        if (groupEffect.getSelectedToggle() != null) {
            Effect effect = 
                (Effect) groupEffect.getSelectedToggle().getUserData();
            pic.setEffect(effect);
            noEffects.setDisable(false);
        } else {
                noEffects.setDisable(true);
            }
});
        
menuEdit.getItems().addAll(menuEffect, noEffects);

RadioMenuItemオプションが選択されていない場合、図24-7に示すように、「No Effect」メニュー項目が無効になります。ユーザーが視覚効果の1つを選択すると、「No Effects」メニュー項目が有効になります。

図24-7 無効になった「Effect」メニュー項目

図24-7の説明が続きます
「図24-7 無効になった「Effect」メニュー項目」の説明

コンテキスト・メニューの追加

必要な機能にユーザー・インタフェースのスペースを割り当てることができない場合、コンテキスト・メニューを使用できます。コンテキスト・メニューは、マウスのクリックに応じて表示されるポップアップ・ウィンドウです。1つのコンテキスト・メニューには、1つ以上のメニュー項目を含めることができます。

「Menu Sample」アプリケーションで、植物の画像にコンテキスト・メニューを設定し、ユーザーが画像をコピーできるようにします。

ContextMenuクラスを使用して、例24-9に示すように、コンテキスト・メニューを定義します。

例24-9 コンテキスト・メニューの定義

final ContextMenu cm = new ContextMenu();
MenuItem cmItem1 = new MenuItem("Copy Image");
cmItem1.setOnAction((ActionEvent e) -> {
    Clipboard clipboard = Clipboard.getSystemClipboard();
    ClipboardContent content = new ClipboardContent();
    content.putImage(pic.getImage());
    clipboard.setContent(content);
});

cm.getItems().add(cmItem1);
pic.addEventHandler(MouseEvent.MOUSE_CLICKED, (MouseEvent e) -> {
    if (e.getButton() == MouseButton.SECONDARY)
       cm.show(pic, e.getScreenX(), e.getScreenY());
});

ユーザーがImageViewオブジェクトを右クリックすると、コンテキスト・メニューに対してshowメソッドがコールされ、その表示が可能になります。

コンテキスト・メニューの「Copy Image」項目に定義されているsetOnActionメソッドにより、Clipboardオブジェクトが作成され、そのコンテンツとしてイメージが追加されます。図24-8は、ユーザーが「Copy Image」コンテキスト・メニュー項目を選択する瞬間を捉えています。

図24-8 コンテキスト・メニューの使用

図24-8の説明が続きます
「図24-8 コンテキスト・メニューの使用」の説明

イメージをコピーしてグラフィカル・エディタに貼り付けることができます。

さらに拡張する場合は、コンテキスト・メニューにメニュー項目を追加し、別のアクションを指定できます。また、CustomMenuItemクラスを使用してカスタム・メニューを作成することもできます。このクラスを使用すると、メニュー内に任意のノードを埋め込み、たとえば、ボタンやスライダをメニュー項目として指定できます。

関連APIドキュメント

ウィンドウを閉じる

目次

JavaFX: JavaFX UIコンポーネントの操作

展開 | 縮小