JavaFX UI控件教程(二十三)之Menu

翻译自  Menu

本章介绍如何创建菜单和菜单栏,添加菜单项,将菜单分组,创建子菜单以及设置上下文菜单。

您可以使用以下JavaFX API类在JavaFX应用程序中构建菜单。

  • 菜单栏

  • 菜单项

    • 菜单

    • CheckMenuItem

    • RadioMenuItem

    • 菜单

    • CustomMenuItem

      • SeparatorMenuItem

  • 上下文菜单

图22-1显示了具有典型菜单栏的应用程序的屏幕截图。

图22-1使用菜单栏和三个菜单类别的应用程序

在JavaFX应用程序中构建菜单

菜单是可以根据用户的请求显示的可操作项目列表。当菜单可见时,用户可以选择一个菜单项。用户单击某个项目后,菜单将返回隐藏模式。通过使用菜单,您可以通过在菜单中放置并不总是需要显示的功能来节省应用程序用户界面(UI)中的空间。

菜单栏中的菜单通常按类别分组。编码模式是声明菜单栏,定义类别菜单,并使用菜单项填充类别菜单。在JavaFX应用程序中构建菜单时,请使用以下菜单项类:

  • MenuItem - 创建一个可操作的选项

  • Menu - 创建子菜单

  • RadioButtonItem - 创建互斥选择

  • CheckMenuItem - 创建可在选定和未选定状态之间切换的选项

要分隔一个类别中的菜单项,请使用SeparatorMenuItem该类。

按菜单栏中的类别组织的菜单通常位于窗口的顶部,剩下的场景用于关键的UI元素。如果出于某些原因,您无法为菜单栏分配UI的任何可视部分,则可以使用用户打开的上下文菜单,只需单击鼠标即可。

创建菜单栏

虽然菜单栏可以放在用户界面的其他位置,但通常它位于UI的顶部,并且它包含一个或多个菜单。菜单栏会自动调整大小以适合应用程序窗口的宽度。默认情况下,添加到菜单栏的每个菜单都由带有文本值的按钮表示。

考虑一个呈现有关植物的参考信息的应用程序,例如它们的名称,二项式名称,图片和简要描述。您可以创建三个菜单类别:文件,编辑和视图,并使用菜单项填充它们。例22-1显示了添加了菜单栏的此类应用程序的源代码。

示例22-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.scene.paint.Color;
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<String, Effect>("Sepia Tone", new SepiaTone()),
        new SimpleEntry<String, Effect>("Glow", new Glow()),
        new SimpleEntry<String, Effect>("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);
        scene.setFill(Color.OLDLACE);
 
        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方法添加到菜单栏之前保持不可见。

图22-2菜单栏已添加到应用程序中

您可以使用键盘的箭头键浏览菜单。但是,当您选择菜单时,不执行任何操作,因为尚未定义菜单的行为。

添加菜单项

通过添加以下项来设置“文件”菜单的功能:

  • 随机播放 - 加载有关植物的参考信息

  • 清除 - 删除参考信息并清除场景

  • 分隔符 - 分离菜单项

  • 退出 - 退出应用程序

例22-2中的粗线通过使用MenuItem类创建了一个Shuffle菜单,并将图形组件添加到应用程序场景中。该MenuItem级能够创建带有文本和图形的动作项。用户单击执行的操作由setOnAction方法定义,类似于Button类。

示例22-2使用Graphics添加Shuffle菜单项

import java.util.AbstractMap.SimpleEntry;
import java.util.Map.Entry;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
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<String, Effect>("Sepia Tone", new SepiaTone()),
        new SimpleEntry<String, Effect>("Glow", new Glow()),
        new SimpleEntry<String, Effect>("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(new EventHandler<ActionEvent>() {
            public void handle(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菜单项时,shuffle调用的方法setOnAction通过计算相应数组中元素的索引来指定标题,二项式名称,工厂图片及其描述。

“清除”菜单项用于擦除应用程序场景。您可以通过使VBox具有GUI元素的容器不可见来实现此操作,如例22-3所示。

示例22-3使用加速器创建清除菜单项

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

在实施MenuItem类别可让开发设置菜单加速器,执行相同的动作的菜单项的组合键。使用“清除”菜单,用户可以从“文件”菜单类别中选择操作,也可以同时按下“控制键”和“X”键。

Exit菜单关闭应用程序窗口。设置System.exit(0)为此菜单项的操作,如例22-4所示。

示例22-4创建退出菜单项

MenuItem exit = new MenuItem("Exit");
exit.setOnAction(new EventHandler<ActionEvent>() {
    public void handle(ActionEvent t) {
        System.exit(0);
    }
});

使用示例22-5中getItems显示的方法将新创建的菜单项添加到“文件”菜单。您可以创建一个分隔符菜单项并将其添加到方法中,以便可视地分离“退出”菜单项。getItems

示例22-5添加菜单项

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

示例22-2示例22-3示例22-4示例22-5添加到Menu Sample应用程序,然后编译并运行它。选择Shuffle菜单项以加载有关不同植物的参考信息。然后清除场景(清除),并关闭应用程序(退出)。图22-3显示了Clear菜单项的选择。

图22-3包含三个菜单项的文件菜单

使用“视图”菜单,您可以隐藏和显示参考信息的元素。实现createMenuItem方法并在方法中调用它start以创建四个CheckMenuItem对象。然后将新创建的检查菜单项添加到“视图”菜单,以切换标题,二项式名称,工厂图片及其描述的可见性。例22-6显示了实现这些任务的两个代码片段。

示例22-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(new ChangeListener<Boolean>() {
        public void changed(ObservableValue ov,
        Boolean old_val, Boolean new_val) {
            node.setVisible(new_val);
        }
    });
    return cmi;
}

CheckMenuItem班是的扩展MenuItem类。它可以在选定和取消选择的状态之间切换。选中后,检查菜单项会显示复选标记。

例22-6创建了四个CheckMenuItem对象并处理其selectedProperty属性的更改。例如,当用户取消选择该项时picView,该setVisible方法接收该false值,该工厂的图片变得不可见。将此代码片段添加到应用程序,编译并运行应用程序时,您可以尝试选择和取消选择菜单项。图22-4显示了显示工厂标题和图片时的应用程序,但隐藏了其二项式名称和描述。

图22-4使用检查菜单项

创建子菜单

对于“编辑”菜单,定义两个菜单项:“图片效果”和“无效果”。“图片效果”菜单项被设计为具有三个项目的子菜单,用于设置三种可用视觉效果中的一种。“无效果”菜单项将删除所选效果并恢复图像的初始状态。

使用RadioMenuItem该类创建子菜单的项目。将单选菜单按钮添加到切换组以使选择互斥。例22-7实现了这些任务。

示例22-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(new EventHandler<ActionEvent>() {
     public void handle(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);
            }
        }
 });
//Adding items to the Edit menu
menuEdit.getItems().addAll(menuEffect, noEffects);

setUserData方法定义特定无线电菜单项的视觉效果。当选择切换组中的一个项目时,相应的效果将应用于图片。选择“无效果”菜单项时,该setEffect方法指定该null值,并且不对图片应用任何效果。

图22-5捕获用户选择Shadow菜单项的时刻。

图22-5带有三个单选菜单项的子菜单

DropShadow效果被施加到图像,它看起来如图图22-6

图22-6应用了DropShadow效果的Quince图片

在“图片效果”子菜单中未选择任何效果时,可以使用类的setDisable方法MenuItem禁用“无效果”菜单。修改例22-7,如例22-8所示。

示例22-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(new EventHandler<ActionEvent>() {
    public void handle(ActionEvent t) {
        pic.setEffect(null);
        groupEffect.getSelectedToggle().setSelected(false);
        noEffects.setDisable(true);
    }
});

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);
                noEffects.setDisable(false);
            } else {
                noEffects.setDisable(true);
        }
    }
});
menuEdit.getItems().addAll(menuEffect, noEffects);

如果未RadioMenuItem选择任何选项,则禁用“无效果”菜单项,如图22-7所示。当用户选择其中一个视觉效果时,将启用“无效果”菜单项。

图22-7效果菜单项被禁用

添加上下文菜单

如果无法为所需功能分配用户界面的任何空间,则可以使用上下文菜单。上下文菜单是一个弹出窗口,响应鼠标单击而显示。上下文菜单可以包含一个或多个菜单项。

在“菜单示例”应用程序中,为工厂的图片设置上下文菜单,以便用户可以复制图像。

使用ContextMenu该类定义上下文菜单,如例22-9所示。

示例22-9定义上下文菜单

final ContextMenu cm = new ContextMenu();
MenuItem cmItem1 = new MenuItem("Copy Image");
cmItem1.setOnAction(new EventHandler<ActionEvent>() {
    public void handle(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,
    new EventHandler<MouseEvent>() {
        @Override public void handle(MouseEvent e) {
            if (e.getButton() == MouseButton.SECONDARY)  
                cm.show(pic, e.getScreenX(), e.getScreenY());
        }
});

当用户右键单击该ImageView对象时,将show调用该方法以使其显示上下文菜单。

setOnAction为上下文菜单的“复制图像”项定义的方法会创建一个Clipboard对象,并将图像添加为其内容。图22-8捕获用户选择“复制图像”上下文菜单项的时刻。

图22-8使用上下文菜单

您可以尝试复制图像并将其粘贴到图形编辑器中。

要进一步增强,可以向上下文菜单添加更多菜单项并指定不同的操作。您还可以使用CustomMenuItem该类创建自定义菜单。使用此类,您可以在菜单中嵌入任意节点,并指定例如按钮或滑块作为菜单项。

相关的API文档 

猜你喜欢

转载自blog.csdn.net/moakun/article/details/83051055