Java Scrollable Popup Menu

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:

1. JFramePopupMenu.java

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;
	}
}

2. XCheckedButton.java

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 Icon  

	public 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;
	}
}

3. XJPopupMenu.java


	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);
				}
			}
		}
	}
}

4. XConstant.java

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

Like us on Facebook