Java Scrollable Popup Menu

Thursday, August 26, 2010

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

0 comments: