Scrollable JPopupMenu
Java Swing Tutorial Explaining the Scrollable Popup Menu Component. Scrollable JPopupMenu can be used in any of the Java Applications. I developed this as the popup menu can have so many menuitems that, they exceed the screen visible area and would not be visible. I needed a way to scroll through the menu items of the pop up menu to avoid this visibility problem.
Scrollable PopupMenu Source Code
Custom JButtons are placed on a JPanel. This JPanel is placed on JScrollPane which has a scrollbar. These custom JButtons are nothing but menuitems. These menuitems can be checked and unchecked similar to JCheckBoxMenuItems.
My scrollable jpopupmenu source code contains 5 files.
1. JFramePopupMenu.java (Mainframe containing the button to invoke Scrollable popup menu)
2. XCheckedButton.java
(Custom JButton which acts like a JCheckBoxMenuItem for the pop up
menu. This class provides a JCheckBoxMenuItrem Functionality, optionally working like a JMenuItem, primarily used with
XJPopupMenu. Rationale for development of this component was the inability of a JMenuItem to work in a Scrollable Popup menu as in XJPopupMenu)
3. XJPopupMenu.java (This is the heart of Scrollable JPopupMenu code)
4. XConstant.java (Interface containing commonly used constants)
5. check.gif
6. menu_spacer.gif
Here is a source code showing, how to create a java swing JPopupMenu with a vertical scrollbar:
import java.awt.Component; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.SwingUtilities; public class JFramePopupMenu extends JFrame { private JPanel jContentPane = null; private JButton jbnPopup = null; private JTextField jtfNumOfMenus = null; private JLabel lblNumElem = null; private XJPopupMenu scrollablePopupMenu = new XJPopupMenu(this); private JButton getBtnPopup() { if (jbnPopup == null) { jbnPopup = new JButton(); jbnPopup.setText("View Scrollable popup menu "); int n = Integer.parseInt(getTxtNumElem().getText()); for (int i=0;i<n;i++){ XCheckedButton xx = new XCheckedButton(" JMenuItem " + (i+1)); xx.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent e) { System.out.println( e ); scrollablePopupMenu.hidemenu(); } }); // Add Custom JSeperator after 2nd and 7th MenuItem. if(i == 2 || i == 7){ scrollablePopupMenu.addSeparator(); } scrollablePopupMenu.add(xx); } jbnPopup.addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent e) { Component source = (Component) e.getSource(); scrollablePopupMenu.show(source, e.getX(), e.getY()); } }); } return jbnPopup; } private JTextField getTxtNumElem() { if (jtfNumOfMenus == null) { jtfNumOfMenus = new JTextField(); jtfNumOfMenus.setColumns(3); jtfNumOfMenus.setText("60"); } return jtfNumOfMenus; } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { JFramePopupMenu thisClass = new JFramePopupMenu(); thisClass.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); thisClass.setVisible(true); } }); } public JFramePopupMenu() { super(); initialize(); } private void initialize() { this.setSize(274, 109); this.setContentPane(getJContentPane()); this.setTitle(" Scrollable JPopupMenu "); } private JPanel getJContentPane() { if (jContentPane == null) { lblNumElem = new JLabel(); FlowLayout flowLayout = new FlowLayout(); flowLayout.setHgap(8); flowLayout.setVgap(8); jContentPane = new JPanel(); jContentPane.setLayout(flowLayout); jContentPane.add(getBtnPopup(), null); jContentPane.add(lblNumElem, null); jContentPane.add(getTxtNumElem(), null); } return jContentPane; } }
import java.awt.Color; import java.awt.event.ItemEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.ButtonGroup; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JToggleButton; import javax.swing.SwingConstants; import javax.swing.UIManager; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicButtonUI; /** * @author balajihe * */ public class XCheckedButton extends JButton { // Icon to be used to for the Checked Icon of the Button private static ImageIcon checkedIcon; /** * These colors are required in order to simulate the JMenuItem's L&F */ public static final Color MENU_HIGHLIGHT_BG_COLOR = UIManager.getColor("MenuItem.selectionBackground"); public static final Color MENU_HIGHLIGHT_FG_COLOR = UIManager.getColor("MenuItem.selectionForeground"); public static final Color MENUITEM_BG_COLOR = UIManager.getColor("MenuItem.background"); public static final Color MENUITEM_FG_COLOR = UIManager.getColor("MenuItem.foreground"); // This property if set to false, will result in the checked Icon not being displayed// when the button is selected private boolean displayCheck = true; public XCheckedButton() { super(); init(); } public XCheckedButton(Action a) { super(a); init(); } public XCheckedButton(Icon icon) { super(icon); init(); } public XCheckedButton(String text, Icon icon) { super(text, icon); init(); } public XCheckedButton(String text) { super(text); init(); } /** * Initialize component LAF and add Listeners */ private void init() { MouseAdapter mouseAdapter = getMouseAdapter(); // Basically JGoodies LAF UI for JButton does not allow Background color to be set. // So we need to set the default UI, ComponentUI ui = BasicButtonUI.createUI(this); this.setUI(ui); setBorder(BorderFactory.createEmptyBorder(3, 0, 3, 2)); setMenuItemDefaultColors(); // setContentAreaFilled(false); setHorizontalTextPosition(SwingConstants.RIGHT); setHorizontalAlignment(SwingConstants.LEFT); // setModel(new JToggleButton.ToggleButtonModel()); setModel(new XCheckedButtonModel()); setSelected(false); this.addMouseListener(mouseAdapter); } private void setMenuItemDefaultColors() { XCheckedButton.this.setBackground(MENUITEM_BG_COLOR); XCheckedButton.this.setForeground(MENUITEM_FG_COLOR); } /** * @return */ private MouseAdapter getMouseAdapter() { return new MouseAdapter() { // For static menuitems, the background color remains the highlighted color, if this is not overridden public void mousePressed(MouseEvent e) { setMenuItemDefaultColors(); } public void mouseEntered(MouseEvent e) { XCheckedButton.this.setBackground(MENU_HIGHLIGHT_BG_COLOR); XCheckedButton.this.setForeground(MENU_HIGHLIGHT_FG_COLOR); } public void mouseExited(MouseEvent e) { setMenuItemDefaultColors(); } }; } /** * @param checkedFlag */ public void displayIcon(boolean checkedFlag) { if (checkedFlag && isDisplayCheck()) { if (checkedIcon == null) { checkedIcon = new ImageIcon("check.gif"); } this.setIcon(checkedIcon); } else { this.setIcon(XConstant.EMPTY_IMAGE_ICON); } this.repaint(); } private class XCheckedButtonModel extends JToggleButton.ToggleButtonModel { /* * Need to Override keeping the super code, else the check mark won't come */ public void setSelected(boolean b) { ButtonGroup group = getGroup(); if (group != null) { // use the group model instead group.setSelected(this, b); b = group.isSelected(this); } if (isSelected() == b) { return; } if (b) { stateMask |= SELECTED; } else { stateMask &= ~SELECTED; } // Send ChangeEvent fireStateChanged(); // Send ItemEvent fireItemStateChanged(new ItemEvent(this, ItemEvent.ITEM_STATE_CHANGED, this, this.isSelected() ? ItemEvent.SELECTED : ItemEvent.DESELECTED)); XCheckedButton.this.displayIcon(b); } } // Returns true if Button will display Checked Icon on Click. Default Behaviour is to display a Checked Iconpublic boolean isDisplayCheck() { return displayCheck; } /** * Sets the property which determines whether a checked Icon should be displayed or not * Setting to false, makes this button display like a normal button * @param displayCheck */ public void setDisplayCheck(boolean displayCheck) { this.displayCheck = displayCheck; } }
import java.awt.BorderLayout; import java.awt.Component; import java.awt.Dimension; import java.awt.Graphics; import java.awt.GridLayout; import java.awt.Point; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.AbstractButton; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JSeparator; import javax.swing.UIManager; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicSeparatorUI; /** * This class implements a scrollable Popup Menu * @author balajihe * */ public class XJPopupMenu extends JPopupMenu implements ActionListener { private static final long serialVersionUID = 1; private JPanel panelMenus = new JPanel(); private JScrollPane scroll = null; private JFrame jframe = null; public static final Icon EMPTY_IMAGE_ICON = new ImageIcon("menu_spacer.gif"); public XJPopupMenu(JFrame jframe) { super(); this.jframe = jframe; this.setLayout(new BorderLayout()); panelMenus.setLayout(new GridLayout(0, 1)); panelMenus.setBackground(UIManager.getColor("MenuItem.background")); // panelMenus.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5)); init(jframe); } private void init(JFrame jframe) { super.removeAll(); scroll = new JScrollPane(); scroll.setViewportView(panelMenus); scroll.setBorder(null); scroll.setMinimumSize(new Dimension(240, 40)); scroll.setMaximumSize(new Dimension(scroll.getMaximumSize().width,this.getToolkit().getScreenSize().height - this.getToolkit().getScreenInsets(jframe.getGraphicsConfiguration()).top - this.getToolkit().getScreenInsets(jframe.getGraphicsConfiguration()).bottom - 4)); super.add(scroll, BorderLayout.CENTER); // super.add(scroll); } public void show(Component invoker, int x, int y) { init(jframe); // this.pack(); panelMenus.validate(); int maxsize = scroll.getMaximumSize().height; int realsize = panelMenus.getPreferredSize().height; int sizescroll = 0; if (maxsize < realsize) { sizescroll = scroll.getVerticalScrollBar().getPreferredSize().width; } scroll.setPreferredSize(new Dimension(scroll.getPreferredSize().width + sizescroll + 20,scroll.getPreferredSize().height)); this.pack(); this.setInvoker(invoker); if (sizescroll != 0) { //Set popup size only if scrollbar is visible this.setPopupSize(new Dimension(scroll.getPreferredSize().width + 20,scroll.getMaximumSize().height - 20)); } // this.setMaximumSize(scroll.getMaximumSize()); Point invokerOrigin = invoker.getLocationOnScreen(); this.setLocation((int) invokerOrigin.getX() + x, (int) invokerOrigin.getY() + y); this.setVisible(true); } public void hidemenu() { if (this.isVisible()) { this.setVisible(false); } } public void add(AbstractButton menuItem) { // menuItem.setMargin(new Insets(0, 20, 0 , 0)); if (menuItem == null) { return; } panelMenus.add(menuItem); menuItem.removeActionListener(this); menuItem.addActionListener(this); if (menuItem.getIcon() == null) { menuItem.setIcon(EMPTY_IMAGE_ICON); } if (!(menuItem instanceof XCheckedButton)) { System.out.println(menuItem.getName()); } } public void addSeparator() { panelMenus.add(new XSeperator()); } public void actionPerformed(ActionEvent e) { this.hidemenu(); } public Component[] getComponents() { return panelMenus.getComponents(); } private static class XSeperator extends JSeparator { XSeperator() { ComponentUI ui = XBasicSeparatorUI.createUI(this); XSeperator.this.setUI(ui); } private static class XBasicSeparatorUI extends BasicSeparatorUI { public static ComponentUI createUI(JComponent c) { return new XBasicSeparatorUI(); } public void paint(Graphics g, JComponent c) { Dimension s = c.getSize(); if (((JSeparator) c).getOrientation() == JSeparator.VERTICAL) { g.setColor(c.getForeground()); g.drawLine(0, 0, 0, s.height); g.setColor(c.getBackground()); g.drawLine(1, 0, 1, s.height); } else // HORIZONTAL { g.setColor(c.getForeground()); g.drawLine(0, 7, s.width, 7); g.setColor(c.getBackground()); g.drawLine(0, 8, s.width, 8); } } } } }
import javax.swing.Icon; import javax.swing.ImageIcon; public interface XConstant { public static final Icon EMPTY_IMAGE_ICON = new ImageIcon("menu_spacer.gif"); }
Note: Please use the 2 jgoodies jars present in the zip file for the jgoodies look and feel
Download Scrollable JPopupMenu Source Code