Following example creates a generic builder (MenuBuilder) for building menus which can be used for JMenuBar or for JPopupMenu invoking on JTable or JTree.
Example
An interface for menu actions
For 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 class
Let'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 Builder
package 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:
|
|