2012-02-15 18 views
12

Me gustaría agregar una forma de desplazarse por los elementos del menú en un JPopupMenu, de forma muy similar a desplazarse por una lista de elementos en un JComboBox.¿Agregar desplazamiento vertical a un JPopupMenu?

Digamos que tengo 10 elementos de menú. Me gustaría mostrar solo 5 elementos de menú a la vez, y usaría un botón de desplazamiento vertical en la parte inferior o superior de JPopupMenu para mostrar los elementos del menú que no están en la lista y ocultar los que acabo de ver.

¿Es posible? Estoy usando el software JideSplitButton de JIDE Software, que muestra un JPopupMenu al hacer clic. Estoy tratando de mantener la apariencia de la barra de comandos en la que coloqué el JideSplitButton, por lo que no quiero reemplazarlo con un JComboBox a menos que realmente tenga que hacerlo.

Respuesta

3

Básicamente, usted puede agregar cualquier JComponents a la JPopupMenu, puede agregar JScrollPane a la JPopup anidando JPanel/JList con otros JComponents,

Aviso pero no hay regla que pivotar GUI no permite dos ventanas emergentes ligeras al mismo tiempo, el mejor ejemplo es common Bug in Swing about JComboBox in the JPopup

tiene que ver JWindow, crear una vez y volver a usar eso para otra Acción, nada mejor para comprobar cómo funciona realmente JWindow para JCalendar by Kai Toedter

22

Aquí hay una versión que he creado usando una barra de desplazamiento. Es sólo un ejemplo sencillo, por lo que se adaptan como mejor le parezca:

import java.awt.Component; 
import java.awt.Container; 
import java.awt.Dimension; 
import java.awt.Graphics; 
import java.awt.Insets; 
import java.awt.LayoutManager; 
import java.awt.event.AdjustmentEvent; 
import java.awt.event.AdjustmentListener; 
import java.awt.event.MouseWheelEvent; 
import java.awt.event.MouseWheelListener; 
import javax.swing.JPopupMenu; 
import javax.swing.JScrollBar; 

public class JScrollPopupMenu extends JPopupMenu { 
    protected int maximumVisibleRows = 10; 

    public JScrollPopupMenu() { 
     this(null); 
    } 


    public JScrollPopupMenu(String label) { 
     super(label); 
     setLayout(new ScrollPopupMenuLayout()); 

     super.add(getScrollBar()); 
     addMouseWheelListener(new MouseWheelListener() { 
      @Override public void mouseWheelMoved(MouseWheelEvent event) { 
       JScrollBar scrollBar = getScrollBar(); 
       int amount = (event.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) 
          ? event.getUnitsToScroll() * scrollBar.getUnitIncrement() 
          : (event.getWheelRotation() < 0 ? -1 : 1) * scrollBar.getBlockIncrement(); 

       scrollBar.setValue(scrollBar.getValue() + amount); 
       event.consume(); 
      } 
     }); 
    } 

    private JScrollBar popupScrollBar; 
    protected JScrollBar getScrollBar() { 
     if(popupScrollBar == null) { 
      popupScrollBar = new JScrollBar(JScrollBar.VERTICAL); 
      popupScrollBar.addAdjustmentListener(new AdjustmentListener() { 
       @Override public void adjustmentValueChanged(AdjustmentEvent e) { 
        doLayout(); 
        repaint(); 
       } 
      }); 

      popupScrollBar.setVisible(false); 
     } 

     return popupScrollBar; 
    } 

    public int getMaximumVisibleRows() { 
     return maximumVisibleRows; 
    } 

    public void setMaximumVisibleRows(int maximumVisibleRows) { 
     this.maximumVisibleRows = maximumVisibleRows; 
    } 

    public void paintChildren(Graphics g){ 
     Insets insets = getInsets(); 
     g.clipRect(insets.left, insets.top, getWidth(), getHeight() - insets.top - insets.bottom); 
     super.paintChildren(g); 
    } 

    protected void addImpl(Component comp, Object constraints, int index) { 
     super.addImpl(comp, constraints, index); 

     if(maximumVisibleRows < getComponentCount()-1) { 
      getScrollBar().setVisible(true); 
     } 
    } 

    public void remove(int index) { 
     // can't remove the scrollbar 
     ++index; 

     super.remove(index); 

     if(maximumVisibleRows >= getComponentCount()-1) { 
      getScrollBar().setVisible(false); 
     } 
    } 

    public void show(Component invoker, int x, int y){ 
     JScrollBar scrollBar = getScrollBar(); 
     if(scrollBar.isVisible()){ 
      int extent = 0; 
      int max = 0; 
      int i = 0; 
      int unit = -1; 
      int width = 0; 
      for(Component comp : getComponents()) { 
       if(!(comp instanceof JScrollBar)) { 
        Dimension preferredSize = comp.getPreferredSize(); 
        width = Math.max(width, preferredSize.width); 
        if(unit < 0){ 
         unit = preferredSize.height; 
        } 
        if(i++ < maximumVisibleRows) { 
         extent += preferredSize.height; 
        } 
        max += preferredSize.height; 
       } 
      } 

      Insets insets = getInsets(); 
      int widthMargin = insets.left + insets.right; 
      int heightMargin = insets.top + insets.bottom; 
      scrollBar.setUnitIncrement(unit); 
      scrollBar.setBlockIncrement(extent); 
      scrollBar.setValues(0, heightMargin + extent, 0, heightMargin + max); 

      width += scrollBar.getPreferredSize().width + widthMargin; 
      int height = heightMargin + extent; 

      setPopupSize(new Dimension(width, height)); 
     } 

     super.show(invoker, x, y); 
    } 

    protected static class ScrollPopupMenuLayout implements LayoutManager{ 
     @Override public void addLayoutComponent(String name, Component comp) {} 
     @Override public void removeLayoutComponent(Component comp) {} 

     @Override public Dimension preferredLayoutSize(Container parent) { 
      int visibleAmount = Integer.MAX_VALUE; 
      Dimension dim = new Dimension(); 
      for(Component comp :parent.getComponents()){ 
       if(comp.isVisible()) { 
        if(comp instanceof JScrollBar){ 
         JScrollBar scrollBar = (JScrollBar) comp; 
         visibleAmount = scrollBar.getVisibleAmount(); 
        } 
        else { 
         Dimension pref = comp.getPreferredSize(); 
         dim.width = Math.max(dim.width, pref.width); 
         dim.height += pref.height; 
        } 
       } 
      } 

      Insets insets = parent.getInsets(); 
      dim.height = Math.min(dim.height + insets.top + insets.bottom, visibleAmount); 

      return dim; 
     } 

     @Override public Dimension minimumLayoutSize(Container parent) { 
      int visibleAmount = Integer.MAX_VALUE; 
      Dimension dim = new Dimension(); 
      for(Component comp : parent.getComponents()) { 
       if(comp.isVisible()){ 
        if(comp instanceof JScrollBar) { 
         JScrollBar scrollBar = (JScrollBar) comp; 
         visibleAmount = scrollBar.getVisibleAmount(); 
        } 
        else { 
         Dimension min = comp.getMinimumSize(); 
         dim.width = Math.max(dim.width, min.width); 
         dim.height += min.height; 
        } 
       } 
      } 

      Insets insets = parent.getInsets(); 
      dim.height = Math.min(dim.height + insets.top + insets.bottom, visibleAmount); 

      return dim; 
     } 

     @Override public void layoutContainer(Container parent) { 
      Insets insets = parent.getInsets(); 

      int width = parent.getWidth() - insets.left - insets.right; 
      int height = parent.getHeight() - insets.top - insets.bottom; 

      int x = insets.left; 
      int y = insets.top; 
      int position = 0; 

      for(Component comp : parent.getComponents()) { 
       if((comp instanceof JScrollBar) && comp.isVisible()) { 
        JScrollBar scrollBar = (JScrollBar) comp; 
        Dimension dim = scrollBar.getPreferredSize(); 
        scrollBar.setBounds(x + width-dim.width, y, dim.width, height); 
        width -= dim.width; 
        position = scrollBar.getValue(); 
       } 
      } 

      y -= position; 
      for(Component comp : parent.getComponents()) { 
       if(!(comp instanceof JScrollBar) && comp.isVisible()) { 
        Dimension pref = comp.getPreferredSize(); 
        comp.setBounds(x, y, width, pref.height); 
        y += pref.height; 
       } 
      } 
     } 
    } 
} 
+0

Esto es absolutamente increíble (y trabajar) ¡componente! Agregué un constructor 'public JScrollPopupMenu (int maximumVisibleRows)' y estaba listo para funcionar. ¡Gracias! – bobndrew

+0

@Riquochet ¿no debería usted también volver a implementar otros métodos que dependen de los índices de los componentes? Me gusta 'insert (Component component, int index)' e 'insert (Action a, int index)'? – andrybak

+0

@andrybak, sí deberías. Solo quería crear un ejemplo simple pero completamente funcional.Para completar, estoy seguro de que hay más que se debe hacer. – Riquochet

10

Además de la JScrollPopupMenu arriba, yo también necesitaba barra de desplazamiento AA en un submenú (también conocido como un "Tire derecha Menú") Esto parece ser un caso más común. Así que me he adaptado un JMenu utilizar el llamado JScrollPopupMenu JScrollMenu:

import javax.swing.Action; 
import javax.swing.JButton; 
import javax.swing.JMenu; 
import javax.swing.JMenuItem; 
import javax.swing.JPopupMenu; 
import javax.swing.MenuElement; 
import javax.swing.UIManager; 
import javax.swing.plaf.MenuItemUI; 
import javax.swing.plaf.PopupMenuUI; 
import java.awt.Component; 
import java.awt.ComponentOrientation; 



public class JScrollMenu extends JMenu { 
    // Covers the one in the JMenu because the method that creates it in JMenu is private 
    /** The popup menu portion of the menu.*/ 
    private JPopupMenu popupMenu; 


    /** 
    * Constructs a new <code>JMenu</code> with no text. 
    */ 
    public JScrollMenu() { 
     this(""); 
    } 

    /** 
    * Constructs a new <code>JMenu</code> with the supplied string as its text. 
    * 
    * @param s the text for the menu label 
    */ 
    public JScrollMenu(String s) { 
     super(s); 
    } 

    /** 
    * Constructs a menu whose properties are taken from the <code>Action</code> supplied. 
    * 
    * @param a an <code>Action</code> 
    */ 
    public JScrollMenu(Action a) { 
     this(); 
     setAction(a); 
    } 


    /** 
    * Lazily creates the popup menu. This method will create the popup using the <code>JScrollPopupMenu</code> class. 
    */ 
    protected void ensurePopupMenuCreated() { 
     if(popupMenu == null) { 
      this.popupMenu = new JScrollPopupMenu(); 
      popupMenu.setInvoker(this); 
      popupListener = createWinListener(popupMenu); 
     } 
    } 

////////////////////////////// 
//// All of these methods are necessary because ensurePopupMenuCreated() is private in JMenu 
////////////////////////////// 
    @Override 
    public void updateUI() { 
     setUI((MenuItemUI) UIManager.getUI(this)); 

     if(popupMenu != null) { 
      popupMenu.setUI((PopupMenuUI) UIManager.getUI(popupMenu)); 
     } 
    } 


    @Override 
    public boolean isPopupMenuVisible() { 
     ensurePopupMenuCreated(); 
     return popupMenu.isVisible(); 
    } 


    @Override 
    public void setMenuLocation(int x, int y) { 
     super.setMenuLocation(x, y); 
     if(popupMenu != null) { 
      popupMenu.setLocation(x, y); 
     } 
    } 

    @Override 
    public JMenuItem add(JMenuItem menuItem) { 
     ensurePopupMenuCreated(); 
     return popupMenu.add(menuItem); 
    } 

    @Override 
    public Component add(Component c) { 
     ensurePopupMenuCreated(); 
     popupMenu.add(c); 
     return c; 
    } 

    @Override 
    public Component add(Component c, int index) { 
     ensurePopupMenuCreated(); 
     popupMenu.add(c, index); 
     return c; 
    } 


    @Override 
    public void addSeparator() { 
     ensurePopupMenuCreated(); 
     popupMenu.addSeparator(); 
    } 

    @Override 
    public void insert(String s, int pos) { 
     if(pos < 0) { 
      throw new IllegalArgumentException("index less than zero."); 
     } 

     ensurePopupMenuCreated(); 
     popupMenu.insert(new JMenuItem(s), pos); 
    } 

    @Override 
    public JMenuItem insert(JMenuItem mi, int pos) { 
     if(pos < 0) { 
      throw new IllegalArgumentException("index less than zero."); 
     } 
     ensurePopupMenuCreated(); 
     popupMenu.insert(mi, pos); 
     return mi; 
    } 

    @Override 
    public JMenuItem insert(Action a, int pos) { 
     if(pos < 0) { 
      throw new IllegalArgumentException("index less than zero."); 
     } 

     ensurePopupMenuCreated(); 
     JMenuItem mi = new JMenuItem(a); 
     mi.setHorizontalTextPosition(JButton.TRAILING); 
     mi.setVerticalTextPosition(JButton.CENTER); 
     popupMenu.insert(mi, pos); 
     return mi; 
    } 

    @Override 
    public void insertSeparator(int index) { 
     if(index < 0) { 
      throw new IllegalArgumentException("index less than zero."); 
     } 

     ensurePopupMenuCreated(); 
     popupMenu.insert(new JPopupMenu.Separator(), index); 
    } 


    @Override 
    public void remove(JMenuItem item) { 
     if(popupMenu != null){ 
      popupMenu.remove(item); 
     } 
    } 

    @Override 
    public void remove(int pos) { 
     if(pos < 0) { 
      throw new IllegalArgumentException("index less than zero."); 
     } 
     if(pos > getItemCount()) { 
      throw new IllegalArgumentException("index greater than the number of items."); 
     } 
     if(popupMenu != null){ 
      popupMenu.remove(pos); 
     } 
    } 

    @Override 
    public void remove(Component c) { 
     if(popupMenu != null){ 
      popupMenu.remove(c); 
     } 
    } 

    @Override 
    public void removeAll() { 
     if(popupMenu != null){ 
      popupMenu.removeAll(); 
     } 
    } 

    @Override 
    public int getMenuComponentCount() { 
     return (popupMenu == null) ? 0 : popupMenu.getComponentCount(); 
    } 

    @Override 
    public Component getMenuComponent(int n) { 
     return (popupMenu == null) ? null : popupMenu.getComponent(n); 
    } 

    @Override 
    public Component[] getMenuComponents() { 
     return (popupMenu == null) ? new Component[0] : popupMenu.getComponents(); 
    } 

    @Override 
    public JPopupMenu getPopupMenu() { 
     ensurePopupMenuCreated(); 
     return popupMenu; 
    } 

    @Override 
    public MenuElement[] getSubElements() { 
     return popupMenu == null ? new MenuElement[0] : new MenuElement[]{popupMenu}; 
    } 


    @Override 
    public void applyComponentOrientation(ComponentOrientation o) { 
     super.applyComponentOrientation(o); 

     if(popupMenu != null) { 
      int ncomponents = getMenuComponentCount(); 
      for(int i = 0; i < ncomponents; ++i) { 
       getMenuComponent(i).applyComponentOrientation(o); 
      } 
      popupMenu.setComponentOrientation(o); 
     } 
    } 

    @Override 
    public void setComponentOrientation(ComponentOrientation o) { 
     super.setComponentOrientation(o); 
     if(popupMenu != null) { 
      popupMenu.setComponentOrientation(o); 
     } 
    } 
} 
+0

Esta es una buena solución :) ... Estoy intentando averiguar cómo forzar el desplazamiento a un elemento específico en el menú secundario utilizando esta solución, como usar ensureIndexIsVisible (index) en una JList? – Brad

+1

'public void ensureIndexIsVisible (int index) { Rectángulo rect = getMenuComponent (index) .getBounds(); ((JScrollPopupMenu) popupMenu) .getScrollBar(). ScrollRectToVisible (rect); } ' – Riquochet

0

Como necesitaba menú emergente con menú emergente Barra de desplazamiento, acabo de volver a utilizar JComboBox. El truco fue poner JComboBox en un JViewport, de modo que solo el botón de flecha fuera visible. Puede hacer que solo un píxel sea pequeño o incluso menor y usar eventos de JideSplitButton para abrir ventanas emergentes.

Puede encontrar el código en github.

Cuestiones relacionadas