Java Swing - A generic builder to create JMenuBar or JPopupMenu invoking on JTable/JTree [Updated: Apr 7, 2018, Created: Apr 6, 2018] |
|
||
Following example creates a generic builder (MenuBuilder) for building menus which can be used for JMenuBar or for JPopupMenu invoking on JTable or JTree. ExampleAn interface for menu actionsFor using our MenuBuilder, we need to implement this interface to define what actions should be taken when a menu is clicked, and also to enable/disable menu items dynamically before they show up. package com.logicbig.uicommon.menu; import java.util.List; public interface MenuAction { void perform(String command, List<?> selection); boolean shouldEnable(String command, List<?> selection); } The main classLet's see our main class to see how MenuBuilder is used. public class MenuBuilderExampleMain { public static void main(String[] args) { JTree tree = createJTree(); JTable table = createJTable(); MenuBuilder menuBuilder = buildMenu(); menuBuilder.buildPopupMenu(tree); //in real scenario we will create different menu for each component menuBuilder.buildPopupMenu(table); JMenu menu = menuBuilder.buildMenu("Main menu"); JMenuBar jMenuBar = new JMenuBar(); jMenuBar.add(menu); JFrame frame = createFrame(); frame.setJMenuBar(jMenuBar); frame.add(new JScrollPane(tree), BorderLayout.WEST); frame.add(new JScrollPane(table), BorderLayout.CENTER); frame.setLocationRelativeTo(null); frame.setVisible(true); } private static MenuBuilder buildMenu() { //in real scenario separate actions should be created for each menu MenuAction nodeAction = createNodeAction(); return MenuBuilder.init() .menu("1", nodeAction) .menu("2", nodeAction) .parent("3", nodeAction) .menu("4", nodeAction) .menu("5", nodeAction) //nested parent .parent("5a", nodeAction) .menu("5a1", nodeAction) .menu("5a2", nodeAction) .parentEnd() .menu("5b", nodeAction) .parentEnd() .menu("6", nodeAction); } private static MenuAction createNodeAction() { return new MenuAction() { @Override public void perform(String command, List<?> selection) { System.out.println("menu action invoked: " + command); System.out.println("selections: "); for (Object o : selection) { if (o instanceof Object[]) {//table row System.out.println(Arrays.toString((Object[]) o)); } else { System.out.println(o); } } } @Override public boolean shouldEnable(String command, List<?> selection) { System.out.println("shouldEnable invoked: " + command); //just disable some randomly, // in real scenario we will disable based on selection, command and some other external dynamic params return Math.random() < 0.6; } }; } private static JTable createJTable() { return new JTable(new Object[][]{ new Object[]{1, 2, 3}, new Object[]{4, 5, 6}, new Object[]{7, 8, 9}}, new String[]{"one", "two", "three"}); } private static JTree createJTree() { JTree tree = new JTree();//using default tree JTreeUtil.setTreeExpandedState(tree, true); return tree; } private static JFrame createFrame() { JFrame frame = new JFrame("Menu Builder Example"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(new Dimension(600, 400)); return frame; } } The Menu Builderpackage com.logicbig.uicommon.menu; import com.logicbig.uicommon.table.JTableUtil; import com.logicbig.uicommon.tree.JTreeUtil; import javax.swing.*; import javax.swing.event.MenuEvent; import javax.swing.event.MenuListener; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.List; public class MenuBuilder { private static final String ClientMenuActionProp = "ClientMenuActionProp"; private List<MenuNode> menuItemList = new ArrayList<>(); private List<MenuNode> currentParents = new ArrayList<>(); public static MenuBuilder init() { return new MenuBuilder(); } public MenuBuilder menu(String displayName, MenuAction action) { MenuNode menuElement = new MenuNode(displayName, action); if (currentParents.size() == 0) { menuItemList.add(menuElement); } else { currentParents.get(currentParents.size() - 1).addChild(menuElement); } return this; } public MenuBuilder parent(String displayName, MenuAction menuAction) { MenuNode menuElement = new MenuNode(displayName, menuAction); menuElement.children = new ArrayList<>(); if (currentParents.size() == 0) { menuItemList.add(menuElement); } else { currentParents.get(currentParents.size() - 1).addChild(menuElement); } currentParents.add(menuElement); return this; } public MenuBuilder parentEnd() { if (currentParents.size() == 0) { throw new IllegalArgumentException("MenuBuilder#endParent() call should " + "only be after MenuBuilder()#parent()"); } currentParents.remove(currentParents.size() - 1); return this; } //todo add more build methods for JRadioButtonMenuItem (radioMenu(.....)), // JCheckBoxMenuItem (checkMenu(....)) and for custom menu component (customMenu(...)) //creates JMenus for JMenuBar public JMenu buildMenu(String name) { JMenu menu = new JMenu(name); initMenuListener(menu); addElementsToRootMenu(menu, this.menuItemList, null); return menu; } //supports JTree and JTable, it can be extended for others public JPopupMenu buildPopupMenu(JComponent source) { if (menuItemList.size() == 0) { throw new IllegalArgumentException("No menu child added"); } JPopupMenu popupMenu = new JPopupMenu(); addElementsToRootMenu(popupMenu, this.menuItemList, source); initPopupListener(popupMenu, source); return popupMenu; } @SuppressWarnings("unchecked") private void addElementsToRootMenu(JComponent rootMenu, List<MenuNode> menuItemList, JComponent source) { for (MenuNode menuNode : menuItemList) { if (menuNode.hasChildren()) { JMenu parentMenu = new JMenu(menuNode.displayName); rootMenu.add(parentMenu); parentMenu.putClientProperty(ClientMenuActionProp, menuNode.action); addElementsToRootMenu(parentMenu, menuNode.children, source);//recursive } else { JMenuItem mi = new JMenuItem(menuNode.displayName); rootMenu.add(mi); mi.putClientProperty(ClientMenuActionProp, menuNode.action); mi.addActionListener((e) -> { MenuAction action = menuNode.action; if (source == null) { List list = new ArrayList<>(); list.add(mi.getText()); action.perform(mi.getActionCommand(), list); } else { List<?> t = getSelection(source); action.perform(mi.getActionCommand(), t); } }); } } } private List<?> getSelection(JComponent source) { if (source instanceof JTree) { JTree tree = ((JTree) source); return JTreeUtil.getSelectedUserObjects(tree); } else if (source instanceof JTable) { JTable table = (JTable) source; return JTableUtil.getSelectedRows(table); } return new ArrayList<>(); } private void initMenuListener(JMenu menu) { menu.addMenuListener(new MenuListener() { @Override public void menuSelected(MenuEvent e) { //no user selected objects in case of JMenu Bar enableDisableItems(menu, new ArrayList<>()); } @Override public void menuDeselected(MenuEvent e) { } @Override public void menuCanceled(MenuEvent e) { } }); } private void initPopupListener(JPopupMenu popup, JComponent component) { component.addMouseListener(new MouseAdapter() { @Override public void mouseReleased(MouseEvent e) { if (e.isPopupTrigger()) { if (!selectionValid(component)) { return; } popup.show(component, e.getX(), e.getY()); } } private boolean selectionValid(JComponent component) { if (component instanceof JTree) { return ((JTree) component).getSelectionCount() != 0; } else if (component instanceof JTable) { return ((JTable) component).getSelectedRowCount() != 0; } return true; } }); popup.addPopupMenuListener(new PopupMenuListener() { @Override public void popupMenuWillBecomeVisible(PopupMenuEvent e) { JPopupMenu popupMenu = (JPopupMenu) e.getSource(); List<?> selectedUserObjects = getSelection(component); enableDisableItems(popupMenu, selectedUserObjects); } @Override public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { } @Override public void popupMenuCanceled(PopupMenuEvent e) { } }); } private void enableDisableItems(MenuElement parentMenuElement, List<?> selectedUserObjects) { for (MenuElement menuElement : parentMenuElement.getSubElements()) { JComponent c = (JComponent) menuElement; MenuAction menuAction = (MenuAction) c.getClientProperty(ClientMenuActionProp); if (menuAction != null) { String command = c instanceof AbstractButton ? ((AbstractButton) c).getActionCommand() : null; c.setEnabled(menuAction.shouldEnable(command, selectedUserObjects)); } if (c.isEnabled()) { enableDisableItems(menuElement, selectedUserObjects);//recursive } } } private static class MenuNode { private final String displayName; private MenuAction action; private ArrayList<MenuNode> children; public MenuNode(String displayName, MenuAction action) { this.displayName = displayName; this.action = action; } public boolean hasChildren() { return children != null && children.size() > 0; } public void addChild(MenuNode child) { if (children == null) { children = new ArrayList<>(); } children.add(child); } } } Output![]() Console output: shouldEnabled invoked: 1 shouldEnabled invoked: 2 shouldEnabled invoked: 4 shouldEnabled invoked: 5 shouldEnabled invoked: 5a1 shouldEnabled invoked: 5a2 shouldEnabled invoked: 5b shouldEnabled invoked: 6 menu action invoked: 5a1 selections: red ![]() Console output: shouldEnabled invoked: 1 shouldEnabled invoked: 2 shouldEnabled invoked: 4 shouldEnabled invoked: 5 shouldEnabled invoked: 5a1 shouldEnabled invoked: 5a2 shouldEnabled invoked: 5b shouldEnabled invoked: 6 menu action invoked: 5a1 selections: [7, 8, 9] ![]() Console output: shouldEnabled invoked: 1 shouldEnabled invoked: 2 shouldEnabled invoked: 4 shouldEnabled invoked: 5 shouldEnabled invoked: 5a1 shouldEnabled invoked: 5a2 shouldEnabled invoked: 5b shouldEnabled invoked: 6 menu action invoked: 5a2 selections: 5a2 Example ProjectDependencies and Technologies Used:
|
|
||
|
|||
|