# JComboBox: Popupmenü sichtbar lassen



## Ralf Ueberfuhr (25. Feb 2006)

Hallo,

Ich schreibe grad an einer JComboBox, die - wenn das Model eine bestimmte Anzahl an Einträgen übersteigt - einen Eintrag "Erweitern" hinzufügt, den man dann auswählen kann, um letztendlich alle Einträge anzuzeigen.

Wenn ich diesen Eintrag auswähle, dann wird folgendes gemacht:
    - Ereignis itemStateChanged() wird ausgelöst, wo ich reagieren kann
    - PopupMenü wird geschlossen

Genau diese Reihenfolge ist ungünstig, denn ich möchte, dass das PopupMenü nach dem Erweitern offen bleibt. Ein showPopup() in der Ereignisbehandlung bringt also nichts, und PopupListener.popupMenuWillBecomeInvisible(e) bietet auch keine cancel()-Methode.

Wie kann ich das Menü offen lassen?

*Hinweis:* JComboBox erweitern und die Methode hidePopup() überschreiben ist ein Weg, aber gibt es auch eine andere Möglichkeit, sodass ich eine normale JComboBox nur mit Model (und Renderer) bereichern muss?

*P.S.* Ich würde bei Interesse auch den Quelltext hier reinstellen, wenn das Problem gelöst wird. ;-)


----------



## André Uhres (25. Feb 2006)

Eine JComboBox zeigt immer alle Zeilen an. 
Wo ist das Problem ?


----------



## Ralf Ueberfuhr (25. Feb 2006)

Ja eben, sie soll es aber zunächst nicht. So sieht sie aus:







Wenn ich nun unten auf "Alle anzeigen" klicke, erweitert sich die Auswahl, aber das Menü geht eben zu. Und genau das will ich verhindern.


----------



## Illuvatar (25. Feb 2006)

Wieso nimmst du nicht das hidePopup überschreiben? Wäre doch auch ganz im Sinne der OOP


----------



## Ralf Ueberfuhr (25. Feb 2006)

masseur81 hat gesagt.:
			
		

> *Hinweis:* JComboBox erweitern und die Methode hidePopup() überschreiben ist ein Weg, aber gibt es auch eine andere Möglichkeit, sodass ich eine normale JComboBox nur mit Model (und Renderer) bereichern muss?



Wer lesen kann, ist klar im Vorteil.  :bae: 

Eben das möchte ich nicht. Ich möchte diese Funktionalität in Form von 2 Klassen bereit stellen:

- *ExtendableComboBoxModel* ist nach außen hin erstmal ein Model wie alle anderen, in das man seine Einträge einfügt. Es managed die tatsächlich angezeigten Einträge maximaler Anzahl.
- *ExtendableComboBoxRenderer* ist nur dazu da, das Item unten korrekt anzuzeigen. Dieses Item hat eine eigene Klasse, um es von den anderen Einträgen zu unterscheiden. Als Grundlage für die Darstellung der anderen Einträge kann man einen eigenen Renderer im Konstruktor angeben. Wenn man der ComboBox den ExtendableComboBoxRenderer nicht hinzufügt, wird der "Alle anzeigen"-Eintrag als String dargestellt, nur die kleine obere Linie fehlt dann.

Der zugehörige Quelltext sieht nun so aus:


```
final JComboBox cb = new JComboBox();
// Es sollen max. 5 Zeilen angezeigt werden.
cb.setModel(new ExtendableComboBoxModel(5));
// Das ist nur optische Kosmetik
cbProjects.setRenderer(new ExtendableComboBoxRenderer());
// Wenn ein eigener Renderer existiert:
final ComboBoxRenderer ownRenderer = ....;
cbProjects.setRenderer(new ExtendableComboBoxRenderer(ownRenderer));
```

Was ich eben noch nicht schaffe, ist das hidePopup() zu beeinflussen.


----------



## André Uhres (26. Feb 2006)

masseur81 hat gesagt.:
			
		

> ...Ein showPopup() in der Ereignisbehandlung bringt also nichts...


Wieso nicht? Das Popup muss doch sowieso neu gezeichnet werden!

```
String selectedItem = comboBox.getSelectedItem().toString();
        if(selectedItem.equals("Alle...")){
            comboBox.removeItem(selectedItem);
            //TODO:Fehlendes hinzufügen:
            for (int i = 0; i < 10; i++)comboBox.addItem("item"+i);
            SwingUtilities.invokeLater(new Runnable(){
                public void run() {comboBox.showPopup();}
            });
        }
```


----------



## Ralf Ueberfuhr (26. Feb 2006)

Mensch danke, invokeLater(...) ist des Rätsels Lösung. Damit wär das Problem erledigt. Für Interessenten werd ich den Code hier später noch posten, wenn er bissl ausdokumentiert ist.

Vielen Dank Andre!


----------



## Ralf Ueberfuhr (28. Feb 2006)

Also, wie versprochen noch der Quelltext für Interessenten:


```
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Vector;

import javax.swing.ComboBoxModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.event.ListDataListener;

/**
 * Data model that reduces the popup of a combobox to a number of items.
 * To get access to the unvisible items, there is a "Show all"-item at the end of the list that will expand the popup.
 * After selecting an item, it will be inserted at the first position.
 * 
 * BE CAREFUL: To select an item, only use the setSelectedItem()-Method of the model.
 *
 * @author [email="master@ru-soft.de"]Ralf Ueberfuhr[/email]
 */
public class ExtendableComboBoxModel extends DefaultComboBoxModel {

	private static final long serialVersionUID = 253882048493621052L;
	private String extendCommandString = "Alle anzeigen";
	
	/**
	 * This is only a class representing the "Show All"-Item.
	 * It is neccessary for the renderer.
	 *
	 * @author [email="master@ru-soft.de"]Ralf Ueberfuhr[/email]
	 */
	public class ExtendCommand {
		public String toString() {
			return extendCommandString;
		}
	};
	private int maxCountOfItems = Integer.MAX_VALUE;
	private ArrayList<Object> objects = new ArrayList<Object>();
	private boolean isExtendEntryVisible = false;
	private boolean extended = false;
	private final ExtendCommand EXTEND_COMMAND = new ExtendCommand();
	private LinkedList<JComboBox> parents = new LinkedList<JComboBox>(); 
	private boolean changeSelectedItemPosition = true;
	
	/*
	 * Expands or reduces the popup.
	 */
	private void setExtended(final boolean extended) {
		this.extended = extended;
		if(extended) {
			// if the "Show All"-Item is shown...
			if(isExtendEntryVisible) {
				super.removeElementAt(super.getIndexOf(EXTEND_COMMAND));
				isExtendEntryVisible = false;
				for(int i=maxCountOfItems; i<objects.size(); i++) super.addElement(objects.get(i));
			}
		} else {
			while(super.getSize()>maxCountOfItems) {
				super.removeElementAt(maxCountOfItems);
			}
			if(objects.size()>maxCountOfItems) {
				super.addElement(EXTEND_COMMAND);
				isExtendEntryVisible = true;
			}
		}
	}
	
	private boolean isExtended() {
		return extended;
	}
	
	/**
	 * Sets the title of the "Show All"-Item. 
	 * @param title the title of the "Show All"-Item
	 */
	public void setExtendCommandString(final String title) {
		extendCommandString = title;
	}
	
	// -----------------------------------------------------------------------------
	
	/**
	 * @see ComboBoxModel#setSelectedItem(Object)
	 */
	public void setSelectedItem(Object anObject) {
		if(anObject == null) selectedObject = null;
		if(changeSelectedItemPosition && (anObject != null) && !(anObject instanceof ExtendCommand) && (getIndexOf(anObject)!=0)) {
			removeElement(anObject);
			if(objects.size() < 1) addElement(anObject); else insertElementAt(anObject, 0);
		};
		super.setSelectedItem(anObject);
    }
	
	/**
	 * @see DefaultComboBoxModel#addElement(Object)
	 */
	public void addElement(Object anObject) {
		changeSelectedItemPosition = false;
		objects.add(anObject);
		if(isExtended() || (objects.size()<=maxCountOfItems)) {
			super.addElement(anObject);
		} else if(!isExtendEntryVisible) {
			super.addElement(EXTEND_COMMAND);
			isExtendEntryVisible = true;
		}
		changeSelectedItemPosition = true;
    }
	
	/**
	 * @see DefaultComboBoxModel#insertElementAt(Object, int)
	 */
    public void insertElementAt(Object anObject,int index) {
		changeSelectedItemPosition = false;
    	objects.add(index, anObject);
    	if(isExtended() || (objects.size()<= maxCountOfItems)) super.insertElementAt(anObject, index); else {
    		if(!isExtendEntryVisible) {
    			super.addElement(EXTEND_COMMAND);
    			isExtendEntryVisible = true;
    		};
    		if(index<maxCountOfItems) {
    			super.insertElementAt(anObject, index);
    			super.removeElementAt(maxCountOfItems);
    		}
    	};
		changeSelectedItemPosition = true;
    }
    
	/**
	 * @see DefaultComboBoxModel#removeElementAt(int)
	 */
    public void removeElementAt(int index) {
		changeSelectedItemPosition = false;
    	if(index<0) return;
    	if(objects.size()>index) objects.remove(index);
    	if(index<maxCountOfItems || isExtended()) {
    		if(super.getSize()>index) super.removeElementAt(index);
    		if(isExtendEntryVisible && (objects.size()>=maxCountOfItems)) super.insertElementAt(objects.get(maxCountOfItems-1), maxCountOfItems-1);
    	};
    	if(isExtendEntryVisible && (objects.size()<=maxCountOfItems)) super.removeElementAt(super.getIndexOf(EXTEND_COMMAND));
		changeSelectedItemPosition = true;
    }
    
	/**
	 * @see DefaultComboBoxModel#removeAllElements()
	 */
    public void removeAllElements() {
		changeSelectedItemPosition = false;
    	objects.clear();
    	super.removeAllElements();
    	isExtendEntryVisible = false;
    	setSelectedItem(null);
		changeSelectedItemPosition = true;
    }
    
	private boolean isItemSelectionChangeEventHandlingEnabled = true;
	private Object selectedObject = null;
    private final ItemListener ITEM_LISTENER = new ItemListener() {
		public void itemStateChanged(ItemEvent e) {
			if(!isItemSelectionChangeEventHandlingEnabled) return;
			final JComboBox cb = (JComboBox)e.getSource(); 
			if(cb.getModel() == null) return;
			if(!(cb.getModel() instanceof ExtendableComboBoxModel)) return;
			if(e.getStateChange() == ItemEvent.SELECTED) {
				isItemSelectionChangeEventHandlingEnabled = false;
				if(e.getItem() instanceof ExtendableComboBoxModel.ExtendCommand) {
					cb.setSelectedItem(selectedObject);
					((ExtendableComboBoxModel)cb.getModel()).setExtended(true);
					cb.hidePopup();
		            SwingUtilities.invokeLater(new Runnable(){
		                public void run() {cb.showPopup();}
		            }); 				} else {
					selectedObject = e.getItem();
					if(((ExtendableComboBoxModel)cb.getModel()).isExtended()) ((ExtendableComboBoxModel)cb.getModel()).setExtended(false);
				}
				isItemSelectionChangeEventHandlingEnabled = true;
			}
		}
	};
	private final KeyListener KEY_LISTENER = new KeyListener() {
		public void keyTyped(KeyEvent e) {}
		public void keyPressed(KeyEvent e) {}
		public void keyReleased(KeyEvent e) {
			if(e.getKeyCode() == KeyEvent.VK_ESCAPE)
			if(e.getSource() instanceof JComboBox) ((JComboBox)e.getSource()).hidePopup();
		}
	};
    
	/**
	 * @see DefaultComboBoxModel#addListDataListener(ListDataListener)
	 */
    public void addListDataListener(final ListDataListener l) {
    	super.addListDataListener(l);
    	if(l instanceof JComboBox) {
    		parents.add((JComboBox)l);
    		((JComboBox)l).addItemListener(ITEM_LISTENER);
    		((JComboBox)l).addKeyListener(KEY_LISTENER);
    	}
    }

	/**
	 * @see DefaultComboBoxModel#removeListDataListener(ListDataListener)
	 */
    public void removeListDataListener(final ListDataListener l) {
    	super.removeListDataListener(l);
    	if(l instanceof JComboBox) {
    		parents.remove((JComboBox)l);
    		((JComboBox)l).removeItemListener(ITEM_LISTENER);
    		((JComboBox)l).removeKeyListener(KEY_LISTENER);
    	}
    }

    // ------------------------------------------------------------------------------------------

    /**
     * @param maxCountOfItems the count of items that should be shown
     */
	public ExtendableComboBoxModel(final int maxCountOfItems) {
		super();
		this.maxCountOfItems = maxCountOfItems;
	}

    /**
     * @param maxCountOfItems the count of items that should be shown
     */
	public ExtendableComboBoxModel(final int maxCountOfItems, Object[] items) {
		super(items);
		this.maxCountOfItems = maxCountOfItems;
	}

    /**
     * @param maxCountOfItems the count of items that should be shown
     */
	public ExtendableComboBoxModel(final int maxCountOfItems, Vector<?> v) {
		super(v);
		this.maxCountOfItems = maxCountOfItems;
	}
	
	public static void main(String[] args) {
		final ExtendableComboBoxModel model = new ExtendableComboBoxModel(5);
		final JComboBox cb = new JComboBox(model);
		cb.setRenderer(new ExtendableComboBoxRenderer());
		for(int i=0; i<20; i++) model.addElement("Item "+i);
		final JFrame f = new JFrame();
		f.setContentPane(cb);
		f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		f.pack();
		f.setVisible(true);
	}

}
```


```
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;

import javax.swing.DefaultListCellRenderer;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.ListCellRenderer;
import javax.swing.SpringLayout;

/**
 * A renderer for an extendable ComboBox.
 *
 * @author [email="master@ru-soft.de"]Ralf Ueberfuhr[/email]
 */
public class ExtendableComboBoxRenderer extends DefaultListCellRenderer {

	private static final long serialVersionUID = 1884093223687719488L;
	private ListCellRenderer renderer = new DefaultListCellRenderer();
	private class ExtendCommandPanel extends JPanel {
		private static final long serialVersionUID = -4212849860310767245L;
		public ExtendCommandPanel(final String extendTitle) {
			final SpringLayout layout = new SpringLayout();
			setLayout(layout);
			final JHyperlink lbl = new JHyperlink(extendTitle);
			add(lbl);
			layout.putConstraint(SpringLayout.WEST, lbl, 5, SpringLayout.WEST, this);
			layout.putConstraint(SpringLayout.NORTH, lbl, 5, SpringLayout.NORTH, this);
			layout.putConstraint(SpringLayout.EAST, lbl, -5, SpringLayout.EAST, this);
			layout.putConstraint(SpringLayout.SOUTH, this, 5, SpringLayout.SOUTH, lbl);
		}
		public void paint(Graphics g) {
			super.paint(g);
			g.setColor(Color.LIGHT_GRAY);
			g.drawLine(0, 3, (int)getSize().getWidth(), 3);
		}
	};
	
	/**
	 * @see ListCellRenderer#getListCellRendererComponent(JList, Object, int, boolean, boolean)
	 */
	public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
		if(value instanceof ExtendableComboBoxModel.ExtendCommand) {
			return new ExtendCommandPanel(((ExtendableComboBoxModel.ExtendCommand)value).toString());
		} else return renderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
	}

	public ExtendableComboBoxRenderer() {
		this(new DefaultListCellRenderer());
	}
	public ExtendableComboBoxRenderer(final ListCellRenderer renderer) {
		super();
		this.renderer = (renderer != null ? renderer : new DefaultListCellRenderer()); 
	}

}
```


----------

