Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add Menus and SubMenus recursively in JavaFX?

I have created CustomMenuBar class that extends javafx.scene.control.MenuBar, and the thing I want to implement is to add new menus only by String value like shown in start method of following code:

package recursivemenu;

import javafx.application.Application;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class RecursivelyAddMenuAndSubMenu extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {

        String menu1 = "File > Open";
        String menu2 = "File > Close";
        String menu3 = "File > Recently closed > File1";
        String menu4 = "File > Recently closed > File2";
        String menu5 = "File > Recently closed > File3";
        String menu6 = "File > Recently closed > File4";
        String menu7 = "File > Recently closed > File5";

        CustomMenuBar customMenuBar = new CustomMenuBar();
        customMenuBar.addMenu(menu1);
        customMenuBar.addMenu(menu2);
        customMenuBar.addMenu(menu3);
        customMenuBar.addMenu(menu4);
        customMenuBar.addMenu(menu5);
        customMenuBar.addMenu(menu6);
        customMenuBar.addMenu(menu7);

        BorderPane borderPane = new BorderPane();
        borderPane.setTop(customMenuBar);
        Scene scene = new Scene(borderPane);

        primaryStage.setScene(scene);
        primaryStage.setMaximized(true);
        primaryStage.show();
    }

    class CustomMenuBar extends MenuBar {

        void addMenu(String menu) {

            String[] menuChain = splitMenus(menu);

            // Add new top menu if not exists.
            if (getMenu(menuChain[0], getMenus()) == null)
                createMenu(menuChain[0]);

            // Adding sub menus
            Menu topMenu = getMenu(menuChain[0], getMenus());

            if (topMenu.getItems().isEmpty()) {
                addSubMenu(topMenu, menuChain[1]);
            } else {
                // Add sub menu if not exists.
                if (getItem(menuChain[1], topMenu.getItems()) == null)
                    createSubMenu(menuChain[1], topMenu);
            }
        }

        private void createSubMenu(String subMenuText, Menu menu) {
            menu.getItems().add(new MenuItem(subMenuText));
        }

        private MenuItem getItem(String subMenuText, ObservableList<MenuItem> items) {

            for (MenuItem item : items) {
                if (item.getText().equals(subMenuText)) {
                    return item;
                }
            }
            return null;
        }

        private void addSubMenu(Menu topMenu, String subMenuText) {
            topMenu.getItems().add(new MenuItem(subMenuText));
        }

        private void createMenu(String menuText) {
            getMenus().add(new Menu(menuText));
        }

        private Menu getMenu(String menuText, ObservableList<Menu> menus) {
            for (Menu menu : menus) {
                if (menu.getText().equals(menuText))
                    return menu;
            }
            return null;
        }

        private String[] splitMenus(String menuText) {

            String[] menuChain = menuText.split("\\s*>\\s*");
            for (int i = 0; i < menuChain.length; i++)
                menuChain[i] = menuChain[i].trim();
            return menuChain;
        }
    }
}

String format should be like Menu > SubMenu > SubMenu > etc. And this is example I am only capable to add menu with one sub menu. And I am stuck in here, and don't know to make addMenu method add all menus and sub menus recursively.

The end result should look like this:

Edit:
Menus and sub menus should not be duplicated and they should have to keep sequence of hierarchy.

Edit2:
CustomMenuBar should be able to add any length of nested sub menus.

e.g.
It should work with Menu > SubMenu1 > SubMenu2 > SubMenu3
It should work with Menu > SubMenu1 > SubMenu2 > SubMenu3 > SubMenu4 too.
and with Menu > SubMenu1 > SubMenu2 > SubMenu3 > SubMenu4 > ... > ...> etc as well.

like image 303
perf coder Avatar asked Oct 31 '17 12:10

perf coder


People also ask

Which root control contains all the menus and Menultems?

control. Menu class provides all the methods to deal with menus. This class needs to be instantiated to create a Menu. The following sample of code shows the implementation of JavaFX menu.

What are the two parameters of stage in Javafx divided as?

A stage has two parameters determining its position namely Width and Height. It is divided as Content Area and Decorations (Title Bar and Borders).

How do you add a button to a scene in Javafx?

You can create a Button by instantiating the javafx. scene. control. Button class of this package and, you can set text to the button using the setText() method.


1 Answers

I tried to keep it as simple as I could. I will leave the optimization to you. You can find comments inside the code for more info about the entire process and logic.

import javafx.application.Application;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class RecursivelyAddMenuAndSubMenu extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {

        String menu1 = "File > Open";
        String menu2 = "File > Close";
        String menu3 = "File > Recently closed > File1";
        String menu4 = "File > Recently closed > File2";
        String menu5 = "File > Recently closed > File3";
        String menu6 = "File > Recently closed > File4";
        String menu7 = "File > Recently closed > File5";
        String menu8 = "File > Recently closed > File5 > something";

        CustomMenuBar customMenuBar = new CustomMenuBar();
        customMenuBar.addMenu(menu1);
        customMenuBar.addMenu(menu2);
        customMenuBar.addMenu(menu3);
        customMenuBar.addMenu(menu4);
        customMenuBar.addMenu(menu5);
        customMenuBar.addMenu(menu6);
        customMenuBar.addMenu(menu7);
        customMenuBar.addMenu(menu8);

        BorderPane borderPane = new BorderPane();
        borderPane.setTop(customMenuBar);
        Scene scene = new Scene(borderPane);

        primaryStage.setScene(scene);
        primaryStage.show();
    }

    class CustomMenuBar extends MenuBar {

        private Menu currentMenu;
        private Menu head;

        void addMenu(String menu) {
            String tokens[] = splitMenusInHalf(menu);

            // we found something like "x -> y ... "
            if (tokens.length > 1) {

                // search for the current root if it contains
                // the menu we are about to create
                currentMenu = this.getMenu(tokens[0], head);
                boolean isAdded = true;

                // if not create it
                if (currentMenu == null) {
                    currentMenu = new Menu(tokens[0]);
                    isAdded = false;
                }

                // find out if there was a previous Menu created
                // if so the current is a sub-menu of the previous one
                if (head == null) {
                    head = currentMenu;
                    if (!isAdded) {
                        this.getMenus().add(currentMenu);
                    }
                } else {
                    if (!isAdded) {
                        // otherwise add the current Menu as sub-menu
                        head.getItems().add(currentMenu);
                    }
                    // set the Current "head" the sub-menu
                    head = currentMenu;
                }
                // Recursive check for more menus or menuItems
                addMenu(tokens[1]);
            } else {
                // If found only something like "x" which is MenuItem
                currentMenu.getItems().add(new MenuItem(tokens[0]));
                // reset everything for the next addMenu call
                currentMenu = null;
                head = null;
            }
        }

        private Menu getMenu(String menuText, Menu root) {
            if (root == null) {
                ObservableList<Menu> allMenus = this.getMenus();
                for (Menu m : allMenus) {
                    if (m.getText().equals(menuText)) {
                        return m;
                    }
                }
            } else {
                ObservableList<MenuItem> allMenus = root.getItems();
                for (MenuItem m : allMenus) {
                    if (m.getText().equals(menuText)) {
                        // We are about to convert MenuItem to Menu
                        if (!(m instanceof Menu)) {

                            // Get the previous menuItem location
                            int index = allMenus.indexOf(m);
                            // Remove it
                            allMenus.remove(m);
                            // Create a new Menu with the previous MenuItem text
                            m = new Menu(menuText);
                            // Add it to the correct location
                            allMenus.add(index, m);
                        }
                        return (Menu) m;
                    }
                }
            }
            return null;
        }

        private String[] splitMenusInHalf(String menuText) {
            String[] menuChain = menuText.split("\\s*>\\s*", 2);
            for (int i = 0; i < menuChain.length; i++)
                menuChain[i] = menuChain[i].trim();
            return menuChain;
        }
    }
}

As the OP asked the getMenu() will now convert any previous MenuItem to Menu if the user tried to add a sub-menu or a MenuItem on it.

like image 186
JKostikiadis Avatar answered Sep 19 '22 17:09

JKostikiadis