/*
 * Copyright (c) 2010, 2017, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package javafx.scene.control;

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ObjectPropertyBase;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.scene.Node;

import com.sun.javafx.collections.TrackableObservableList;
import com.sun.javafx.scene.control.Logging;
import javafx.beans.DefaultProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.event.EventDispatchChain;

A popup menu of actionable items which is displayed to the user only upon request. When a menu is visible, in most use cases, the user can select one menu item before the menu goes back to its hidden state. This means the menu is a good place to put important functionality that does not necessarily need to be visible at all times to the user.

Menus are typically placed in a MenuBar, or as a submenu of another Menu. If the intention is to offer a context menu when the user right-clicks in a certain area of their user interface, then this is the wrong control to use. This is because when Menu is added to the scenegraph, it has a visual representation that will result in it appearing on screen. Instead, ContextMenu should be used in this circumstance.

Creating a Menu and inserting it into a MenuBar is easy, as shown below:


final Menu menu1 = new Menu("File");
MenuBar menuBar = new MenuBar();
menuBar.getMenus().add(menu1);

A Menu is a subclass of MenuItem which means that it can be inserted into a Menu's items ObservableList, resulting in a submenu being created:


MenuItem menu12 = new MenuItem("Open");
menu1.getItems().add(menu12);

The items ObservableList allows for any MenuItem type to be inserted, including its subclasses Menu, MenuItem, RadioMenuItem, CheckMenuItem, CustomMenuItem and SeparatorMenuItem. In order to insert an arbitrary Node to a Menu, a CustomMenuItem can be used. One exception to this general rule is that SeparatorMenuItem could be used for inserting a separator.

See Also:
Since:JavaFX 2.0
/** * <p> * A popup menu of actionable items which is displayed to the user only upon request. * When a menu is visible, in most use cases, the user can select one menu item * before the menu goes back to its hidden state. This means the menu is a good * place to put important functionality that does not necessarily need to be * visible at all times to the user. * <p> * Menus are typically placed in a {@link MenuBar}, or as a submenu of another Menu. * If the intention is to offer a context menu when the user right-clicks in a * certain area of their user interface, then this is the wrong control to use. * This is because when Menu is added to the scenegraph, it has a visual * representation that will result in it appearing on screen. Instead, * {@link ContextMenu} should be used in this circumstance. * <p> * Creating a Menu and inserting it into a MenuBar is easy, as shown below: * <pre><code> * final Menu menu1 = new Menu("File"); * MenuBar menuBar = new MenuBar(); * menuBar.getMenus().add(menu1); * </code></pre> * <p> * A Menu is a subclass of {@link MenuItem} which means that it can be inserted * into a Menu's {@link #getItems() items} ObservableList, resulting in a submenu being created: * <pre><code> * MenuItem menu12 = new MenuItem("Open"); * menu1.getItems().add(menu12); * </code></pre> * <p> * The items ObservableList allows for any {@link MenuItem} type to be inserted, * including its subclasses {@link Menu}, {@link MenuItem}, {@link RadioMenuItem}, {@link CheckMenuItem}, * {@link CustomMenuItem} and {@link SeparatorMenuItem}. In order to insert an arbitrary {@link Node} to * a Menu, a CustomMenuItem can be used. One exception to this general rule is that * {@link SeparatorMenuItem} could be used for inserting a separator. * * @see MenuBar * @see MenuItem * @since JavaFX 2.0 */
@DefaultProperty("items") public class Menu extends MenuItem {

Called when the contextMenu for this menu will be shown. However if the contextMenu is empty then this will not be called.

/** * <p>Called when the contextMenu for this menu <b>will</b> be shown. However if the * contextMenu is empty then this will not be called. * </p> */
public static final EventType<Event> ON_SHOWING = new EventType<Event>(Event.ANY, "MENU_ON_SHOWING");

Called when the contextMenu for this menu shows. However if the contextMenu is empty then this will not be called.

/** * <p>Called when the contextMenu for this menu shows. However if the * contextMenu is empty then this will not be called. * </p> */
public static final EventType<Event> ON_SHOWN = new EventType<Event>(Event.ANY, "MENU_ON_SHOWN");

Called when the contextMenu for this menu will be hidden. However if the contextMenu is empty then this will not be called.

/** * <p>Called when the contextMenu for this menu <b>will</b> be hidden. However if the * contextMenu is empty then this will not be called. * </p> */
public static final EventType<Event> ON_HIDING = new EventType<Event>(Event.ANY, "MENU_ON_HIDING");

Called when the contextMenu for this menu is hidden. However if the contextMenu is empty then this will not be called.

/** * <p>Called when the contextMenu for this menu is hidden. However if the * contextMenu is empty then this will not be called. * </p> */
public static final EventType<Event> ON_HIDDEN = new EventType<Event>(Event.ANY, "MENU_ON_HIDDEN"); /*************************************************************************** * * * Constructors * * * **************************************************************************/
Constructs a Menu with an empty string for its display text.
Since:JavaFX 2.2
/** * Constructs a Menu with an empty string for its display text. * @since JavaFX 2.2 */
public Menu() { this(""); }
Constructs a Menu and sets the display text with the specified text.
Params:
  • text – the text to display on the menu button
/** * Constructs a Menu and sets the display text with the specified text. * * @param text the text to display on the menu button */
public Menu(String text) { this(text,null); }
Constructs a Menu and sets the display text with the specified text and sets the graphic Node to the given node.
Params:
  • text – the text to display on the menu button
  • graphic – the graphic to display on the menu button
/** * Constructs a Menu and sets the display text with the specified text * and sets the graphic {@link Node} to the given node. * * @param text the text to display on the menu button * @param graphic the graphic to display on the menu button */
public Menu(String text, Node graphic) { this(text, graphic, (MenuItem[])null); }
Constructs a Menu and sets the display text with the specified text, the graphic Node to the given node, and inserts the given items into the items list.
Params:
  • text – the text to display on the menu button
  • graphic – the graphic to display on the menu button
  • items – The items to display in the popup menu.
Since:JavaFX 8u40
/** * Constructs a Menu and sets the display text with the specified text, * the graphic {@link Node} to the given node, and inserts the given items * into the {@link #getItems() items} list. * * @param text the text to display on the menu button * @param graphic the graphic to display on the menu button * @param items The items to display in the popup menu. * @since JavaFX 8u40 */
public Menu(String text, Node graphic, MenuItem... items) { super(text,graphic); getStyleClass().add(DEFAULT_STYLE_CLASS); if (items != null) { getItems().addAll(items); } parentPopupProperty().addListener(observable -> { for (int i = 0; i < getItems().size(); i++) { MenuItem item = getItems().get(i); item.setParentPopup(getParentPopup()); } }); } /*************************************************************************** * * * Properties * * * **************************************************************************/
Indicates whether the ContextMenu is currently visible.
@defaultValuefalse
/** * Indicates whether the {@link ContextMenu} is currently visible. * * @defaultValue false */
private ReadOnlyBooleanWrapper showing; private void setShowing(boolean value) { if (getItems().size() == 0 || (value && isShowing())) return; // these events will not fire if the showing property is bound if (value) { if (getOnMenuValidation() != null) { Event.fireEvent(this, new Event(MENU_VALIDATION_EVENT)); for(MenuItem m : getItems()) { if (!(m instanceof Menu) && m.getOnMenuValidation() != null) { Event.fireEvent(m, new Event(MenuItem.MENU_VALIDATION_EVENT)); } } } Event.fireEvent(this, new Event(Menu.ON_SHOWING)); } else { Event.fireEvent(this, new Event(Menu.ON_HIDING)); } showingPropertyImpl().set(value); Event.fireEvent(this, (value) ? new Event(Menu.ON_SHOWN) : new Event(Menu.ON_HIDDEN)); } public final boolean isShowing() { return showing == null ? false : showing.get(); } public final ReadOnlyBooleanProperty showingProperty() { return showingPropertyImpl().getReadOnlyProperty(); } private ReadOnlyBooleanWrapper showingPropertyImpl() { if (showing == null) { showing = new ReadOnlyBooleanWrapper() { @Override protected void invalidated() { // force validation get(); // update the styleclass if (isShowing()) { getStyleClass().add(STYLE_CLASS_SHOWING); } else { getStyleClass().remove(STYLE_CLASS_SHOWING); } } @Override public Object getBean() { return Menu.this; } @Override public String getName() { return "showing"; } }; } return showing; } // --- On Showing
Called just prior to the ContextMenu being shown, even if the menu has no items to show. Note however that this won't be called if the menu does not have a valid anchor node.
Returns:the on showing property
/** * Called just prior to the {@code ContextMenu} being shown, even if the menu has * no items to show. Note however that this won't be called if the menu does * not have a valid anchor node. * @return the on showing property */
public final ObjectProperty<EventHandler<Event>> onShowingProperty() { return onShowing; } public final void setOnShowing(EventHandler<Event> value) { onShowingProperty().set(value); } public final EventHandler<Event> getOnShowing() { return onShowingProperty().get(); } private ObjectProperty<EventHandler<Event>> onShowing = new ObjectPropertyBase<EventHandler<Event>>() { @Override protected void invalidated() { eventHandlerManager.setEventHandler(ON_SHOWING, get()); } @Override public Object getBean() { return Menu.this; } @Override public String getName() { return "onShowing"; } }; // -- On Shown
Called just after the ContextMenu is shown.
Returns:the on shown property
/** * Called just after the {@link ContextMenu} is shown. * @return the on shown property */
public final ObjectProperty<EventHandler<Event>> onShownProperty() { return onShown; } public final void setOnShown(EventHandler<Event> value) { onShownProperty().set(value); } public final EventHandler<Event> getOnShown() { return onShownProperty().get(); } private ObjectProperty<EventHandler<Event>> onShown = new ObjectPropertyBase<EventHandler<Event>>() { @Override protected void invalidated() { eventHandlerManager.setEventHandler(ON_SHOWN, get()); } @Override public Object getBean() { return Menu.this; } @Override public String getName() { return "onShown"; } }; // --- On Hiding
Called just prior to the ContextMenu being hidden.
Returns:the on hiding property
/** * Called just prior to the {@link ContextMenu} being hidden. * @return the on hiding property */
public final ObjectProperty<EventHandler<Event>> onHidingProperty() { return onHiding; } public final void setOnHiding(EventHandler<Event> value) { onHidingProperty().set(value); } public final EventHandler<Event> getOnHiding() { return onHidingProperty().get(); } private ObjectProperty<EventHandler<Event>> onHiding = new ObjectPropertyBase<EventHandler<Event>>() { @Override protected void invalidated() { eventHandlerManager.setEventHandler(ON_HIDING, get()); } @Override public Object getBean() { return Menu.this; } @Override public String getName() { return "onHiding"; } }; // --- On Hidden
Called just after the ContextMenu has been hidden.
Returns:the on hidden property
/** * Called just after the {@link ContextMenu} has been hidden. * @return the on hidden property */
public final ObjectProperty<EventHandler<Event>> onHiddenProperty() { return onHidden; } public final void setOnHidden(EventHandler<Event> value) { onHiddenProperty().set(value); } public final EventHandler<Event> getOnHidden() { return onHiddenProperty().get(); } private ObjectProperty<EventHandler<Event>> onHidden = new ObjectPropertyBase<EventHandler<Event>>() { @Override protected void invalidated() { eventHandlerManager.setEventHandler(ON_HIDDEN, get()); } @Override public Object getBean() { return Menu.this; } @Override public String getName() { return "onHidden"; } };
* Instance variables * *
/*************************************************************************** * * * Instance variables * * * **************************************************************************/
private final ObservableList<MenuItem> items = new TrackableObservableList<MenuItem>() { @Override protected void onChanged(Change<MenuItem> c) { while (c.next()) { // remove the parent menu from all menu items that have been removed for (MenuItem item : c.getRemoved()) { item.setParentMenu(null); item.setParentPopup(null); } // set the parent menu to be this menu for all added menu items for (MenuItem item : c.getAddedSubList()) { if (item.getParentMenu() != null) { Logging.getControlsLogger().warning("Adding MenuItem " + item.getText() + " that has already been added to " + item.getParentMenu().getText()); item.getParentMenu().getItems().remove(item); } item.setParentMenu(Menu.this); item.setParentPopup(getParentPopup()); } } if (getItems().size() == 0 && isShowing()) { showingPropertyImpl().set(false); } } }; /*************************************************************************** * * * Public API * * * **************************************************************************/
The items to show within this menu. If this ObservableList is modified at runtime, the Menu will update as expected.
Returns:the list of items
/** * The items to show within this menu. If this ObservableList is modified at * runtime, the Menu will update as expected. * @return the list of items */
public final ObservableList<MenuItem> getItems() { return items; }
If the Menu is not disabled and the ContextMenu is not already showing, then this will cause the ContextMenu to be shown.
/** * If the Menu is not disabled and the {@link ContextMenu} is not already showing, * then this will cause the {@link ContextMenu} to be shown. */
public void show() { if (isDisable()) return; setShowing(true); }
Hides the ContextMenu if it was previously showing, and any showing submenus. If this menu is not showing, then invoking this function has no effect.
/** * Hides the {@link ContextMenu} if it was previously showing, and any showing * submenus. If this menu is not showing, then invoking this function * has no effect. */
public void hide() { if (!isShowing()) return; // hide all sub menus for (MenuItem i : getItems()) { if (i instanceof Menu) { final Menu m = (Menu) i; m.hide(); } } setShowing(false); }
{@inheritDoc}
/** {@inheritDoc} */
@Override public <E extends Event> void addEventHandler(EventType<E> eventType, EventHandler<E> eventHandler) { eventHandlerManager.addEventHandler(eventType, eventHandler); }
{@inheritDoc}
/** {@inheritDoc} */
@Override public <E extends Event> void removeEventHandler(EventType<E> eventType, EventHandler<E> eventHandler) { eventHandlerManager.removeEventHandler(eventType, eventHandler); }
{@inheritDoc}
/** {@inheritDoc} */
@Override public EventDispatchChain buildEventDispatchChain(EventDispatchChain tail) { return tail.prepend(eventHandlerManager); }
* Stylesheet Handling * *
/*************************************************************************** * * * Stylesheet Handling * * * **************************************************************************/
private static final String DEFAULT_STYLE_CLASS = "menu"; private static final String STYLE_CLASS_SHOWING = "showing"; }