Java Scrollable Popup Menu

Beginner java tutorial site is rehosted with updated content and useful information under the domain name javabeginner.com

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

Mar 08, 2007 by Hemanth

 

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

}
 
 
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