24 菜单
本章介绍如何创建菜单和菜单栏,添加菜单项,将菜单分组为类别,创建子菜单以及设置上下文菜单。
您可以使用JavaFX API的以下类来构建JavaFX应用程序中的菜单。
-
MenuBar(菜单栏)
-
MenuItem(菜单项)
-
Menu(菜单)
-
CheckMenuItem(复选菜单项)
-
RadioMenuItem(单选菜单项)
-
Menu(菜单)
-
CustomMenuItem(自定义菜单项)
-
SeparatorMenuItem(分隔符菜单项)
-
-
-
ContextMenu(上下文菜单)
图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
方法添加到菜单栏之前保持不可见。
您可以使用键盘的箭头键浏览菜单。然而,当您选择一个菜单时,不会执行任何操作,因为菜单的行为尚未定义。
添加菜单项
通过添加以下项目来设置文件菜单的功能:
-
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-2、示例24-3、示例24-4和示例24-5添加到菜单示例应用程序中,然后编译并运行它。选择“洗牌”菜单项以加载有关不同植物的参考信息。然后清除场景(清除)并关闭应用程序(退出)。图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显示了应用程序在标题和植物图片显示的时刻,但其二名法名称和描述被隐藏。
创建子菜单
对于编辑菜单,定义两个菜单项:图片效果和无效果。图片效果菜单项被设计为一个子菜单,其中包含三个项目,用于设置三种可用的视觉效果之一。无效果菜单项将删除所选效果并恢复图像的初始状态。
使用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捕捉到用户选择阴影菜单项的时刻。
当将DropShadow
效果应用于图片时,效果如图24-6所示。
您可以使用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所示。当用户选择其中一个视觉效果时,无效果
菜单项将被启用。
添加上下文菜单
当您无法为所需功能分配用户界面的任何空间时,可以使用上下文菜单。上下文菜单是在鼠标点击时弹出的窗口。上下文菜单可以包含一个或多个菜单项。
在菜单示例应用程序中,为植物的图片设置一个上下文菜单,以便用户可以复制该图片。
使用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捕捉到用户选择复制图片上下文菜单项的时刻。
您可以尝试复制该图片并粘贴到图形编辑器中。
为了进一步增强功能,您可以向上下文菜单添加更多菜单项并指定不同的操作。您还可以使用CustomMenuItem
类创建自定义菜单。使用该类,您可以在菜单中嵌入任意节点,并指定例如按钮或滑块作为菜单项。
相关 API 文档