文档



JavaFX:使用JavaFX UI组件

24 菜单

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

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

  • MenuBar(菜单栏)

  • MenuItem(菜单项)

    • Menu(菜单)

    • CheckMenuItem(复选菜单项)

    • RadioMenuItem(单选菜单项)

    • Menu(菜单)

    • CustomMenuItem(自定义菜单项)

      • SeparatorMenuItem(分隔符菜单项)

  • ContextMenu(上下文菜单)

图24-1显示了一个带有典型菜单栏的应用程序的屏幕截图。

图24-1 带有菜单栏和三个菜单类别的应用程序

图24-1的描述
"图24-1 带有菜单栏和三个菜单类别的应用程序"的描述

在JavaFX应用程序中构建菜单

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

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

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

  • Menu - 创建一个子菜单

  • RadioButtonItem - 创建互斥选择

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

使用SeparatorMenuItem类在一个类别中分隔菜单项。

菜单栏中按类别组织的菜单通常位于窗口顶部,将其余的场景留给关键的UI元素。如果由于某种原因,您不能为菜单栏分配任何可视部分,您可以使用用户通过鼠标点击打开的上下文菜单。

创建菜单栏

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

考虑一个渲染有关植物的参考信息的应用程序,例如它们的名称、二名法名称、图片和简要描述。您可以创建三个菜单类别:文件、编辑和查看,并用菜单项填充它们。 示例 24-1 显示了添加了菜单栏的这种应用程序的源代码。

示例 24-1 菜单示例应用程序

MenuBar menuBar = new MenuBar();
Menu menuFile = new Menu("文件");
Menu menuEdit = new Menu("编辑");
Menu menuView = new Menu("视图");
menuBar.getMenus().addAll(menuFile, menuEdit, menuView);

与其他UI控件不同,Menu类和其他MenuItem类的扩展不继承Node类。它们不能直接添加到应用场景中,并且在通过getMenus方法添加到菜单栏之前保持不可见。

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

图24-2的描述如下
"图24-2 菜单栏添加到应用程序中"的描述

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

添加菜单项

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

  • Shuffle - 加载有关植物的参考信息

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

  • Separator - 分离菜单项

  • Exit - 退出应用程序

示例 24-2中,粗体行使用MenuItem类创建了一个Shuffle菜单,并向应用程序场景添加了图形组件。MenuItem类可以创建一个带有文本和图形的可操作项。用户点击时执行的操作由setOnAction方法定义,类似于Button类。

示例24-2 添加带有图形的洗牌菜单项

private int currentIndex = -1;
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();
Menu menuFile = new Menu("文件");
MenuItem add = new MenuItem("洗牌",
new ImageView(new Image("menusample/new.png")));
add.setOnAction((ActionEvent t) -> {
shuffle();
vbox.setVisible(true);
});
menuFile.getItems().addAll(add);
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;
}

当用户选择“洗牌”菜单项时,在setOnAction中调用的shuffle方法通过计算相应数组中元素的索引来指定植物的标题、二名法名称、图片和描述。

“清除”菜单项用于擦除应用场景。您可以通过将包含GUI元素的VBox容器设置为不可见来实现,如示例24-3所示。

示例24-3 创建带有加速键的“清除”菜单项

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

通过实现MenuItem类,开发人员可以设置菜单加速键,即执行与菜单项相同操作的键组合。对于“清除”菜单,用户可以从“文件”菜单类别中选择该操作,或同时按下Control键和X键。

“退出”菜单关闭应用程序窗口。将System.exit(0)设置为该菜单项的操作,如示例24-4所示。

示例24-4 创建“退出”菜单项

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

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

示例24-5 添加菜单项

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

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

图24-3 带有三个菜单项的文件菜单

图24-3的描述
"图24-3 带有三个菜单项的文件菜单"的描述

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

示例24-6:应用CheckMenuItem类创建切换选项

// --- 在start方法中创建四个复选菜单项
CheckMenuItem titleView = createMenuItem ("标题", name);                                                       
CheckMenuItem binNameView = createMenuItem ("二名法名称", binName);        
CheckMenuItem picView = createMenuItem ("图片", pic);        
CheckMenuItem descriptionView = createMenuItem ("描述", description);     
menuView.getItems().addAll(titleView, binNameView, picView, descriptionView);

...

// createMenuItem方法
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创建了四个CheckMenuItem对象,并处理它们的selectedProperty属性的变化。例如,当用户取消选择picView项时,setVisible方法接收到false值,植物的图片变得不可见。当您将此代码片段添加到应用程序中,编译并运行应用程序时,您可以尝试选择和取消选择菜单项。图24-4显示了应用程序在标题和植物图片显示的时刻,但其二名法名称和描述被隐藏。

图24-4:使用复选菜单项

图24-4的描述如下
图24-4的描述

创建子菜单

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

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

示例 24-7 创建带有单选菜单项的子菜单

//图片效果菜单
Menu menuEffect = new Menu("图片效果");
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("无效果");

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

//处理菜单项选择
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);
        }
});

//将项目添加到编辑菜单
menuEdit.getItems().addAll(menuEffect, noEffects);

setUserData方法为特定的单选菜单项定义了一个视觉效果。当选择切换组中的项目之一时,相应的效果将应用于图片。当选择无效果菜单项时,setEffect方法指定null值,图片不会应用任何效果。

图 24-5捕捉到用户选择阴影菜单项的时刻。

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

图 24-5 的描述如下
"图 24-5 带有三个单选菜单项" 的描述

当将DropShadow效果应用于图片时,效果如图24-6所示。

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

图24-6的描述
"图24-6 应用了DropShadow效果的Quince图片"的描述

您可以使用MenuItem类的setDisable方法来禁用当在图片效果子菜单中没有选择任何效果时的“无效果”菜单。将示例24-7修改如示例24-8所示。

示例24-8 禁用菜单项

Menu menuEffect = new Menu("图片效果");
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("无效果");
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所示。当用户选择其中一个视觉效果时,无效果菜单项将被启用。

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

图24-7的描述
"图24-7 效果菜单项被禁用"的描述

添加上下文菜单

当您无法为所需功能分配用户界面的任何空间时,可以使用上下文菜单。上下文菜单是在鼠标点击时弹出的窗口。上下文菜单可以包含一个或多个菜单项。

在菜单示例应用程序中,为植物的图片设置一个上下文菜单,以便用户可以复制该图片。

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

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

final ContextMenu cm = new ContextMenu();
MenuItem cmItem1 = new MenuItem("复制图片");
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方法以显示上下文菜单。

为上下文菜单的复制图片项定义的setOnAction方法创建了一个Clipboard对象,并将图片添加为其内容。 图 24-8捕捉到用户选择复制图片上下文菜单项的时刻。

图 24-8 使用上下文菜单

图 24-8 的描述
"图 24-8 使用上下文菜单"的描述

您可以尝试复制该图片并粘贴到图形编辑器中。

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

相关 API 文档 

关闭窗口

目录

JavaFX:使用JavaFX UI组件

展开 折叠