dynamisch gefilterte JComboBox

takidoso

Erfahrenes Mitglied
Halli hallo,
ich bin gerade dabei einen Renderer zusammenzubauen der mit einer JComboBox arbeitet und dabei soller er, wenn man etwas in die Combo eingibt den eingegebenen String nach einer kleinen Verzögerung als Filter für seine Combobox-Liste verwenden, indem er die Liste einfach reduziert.

Mein Ansatz ist wie folgt:
Code:
public class MappedValuesCellEditor extends DefaultCellEditor
{
  ...
   private final JComboBox m_combo;   
   private DynKeyListener  m_keyListener = null;
   private java.util.List        m_pickList      = null;
  ...
   protected void reducePickList(String filter)
    {
        m_combo.removeAllItems();
        for (int i = 0; i<m_pickList.size(); i++)
        {
            NamedId item = (NamedId)m_pickList.get(i);
            if (item.getName().regionMatches(true,0,filter,0,filter.length()))
            {
                m_combo.addItem(item);
            }
        }
    }

    public void setDynamic(boolean on)
    {
        if (on)
        {
            m_keyListener = new DynKeyListener();
            m_combo.getEditor().getEditorComponent().addKeyListener(m_keyListener);
        }
        else
        {
            m_combo.getEditor().getEditorComponent().removeKeyListener(m_keyListener);
        }
    }

    private class DynKeyListener implements KeyListener
    {
        private javax.swing.Timer timer;
        private int m_delay     = 500;
        
        public DynKeyListener()
        {
            timer = new javax.swing.Timer(m_delay,
            new ActionListener()
            {
                 public void actionPerformed(ActionEvent e)
                 {
                     String str = (String)m_combo.getEditor().getItem();                
                     System.out.println("str="+str);
                     m_combo.showPopup();   //test1
                     reducePickList(str);
                     m_combo.showPopup();  //test2
                 }
            });  
            timer.setRepeats(false);
        }

        public DynKeyListener(int delay)
        {
            this();
            m_delay = delay;
        }
        public void keyTyped(KeyEvent e)
        {
            timer.restart();
        }

        public void keyPressed(KeyEvent e)
        {            
        }
        public void keyReleased(KeyEvent e)
        {            
        }

    }
...
}

Es Funktioniert auch fast jedoch habe ich das Problem mit dem Anzeigen des Popups der Combo. Und zwar ließe ich die Zeile mit dem //test2 weg bekomme ich kein Problem aber das Popup zeigt sich nicht. Wenn ich Zeile mit //test1 entferne bekomme ich die selbe Exception wie wenn ich beide Zeilen habe. lasse ich nur ein showPopup() stehen und rufe nicht reducePickList(str) auf wird das Popup ganz ordnungsgemäß gezeigt.

zum näheren Verständnis, die Exception die ich bekomme ist:
java.awt.IllegalComponentStateException: component must be showing on the screen to determine its location

at java.awt.Component.getLocationOnScreen_NoTreeLock(Component.java:1507)

at java.awt.Component.getLocationOnScreen(Component.java:1481)

at javax.swing.JPopupMenu.show(JPopupMenu.java:921)

at javax.swing.plaf.basic.BasicComboPopup.show(BasicComboPopup.java:177)

at javax.swing.plaf.basic.BasicComboBoxUI.setPopupVisible(BasicComboBoxUI.java:927)

at javax.swing.JComboBox.setPopupVisible(JComboBox.java:790)

at javax.swing.JComboBox.showPopup(JComboBox.java:775)

at de.mc.gui.MappedValuesCellEditor$2.actionPerformed(MappedValuesCellEditor.java:257)

at javax.swing.Timer.fireActionPerformed(Timer.java:271)

at javax.swing.Timer$DoPostEvent.run(Timer.java:201)

at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:178)

at java.awt.EventQueue.dispatchEvent(EventQueue.java:454)

at java.awt.EventDispatchThread.pumpOneEventForHierarchy(EventDispatchThread.java:201)

at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:151)

at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:145)

at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:137)

at java.awt.EventDispatchThread.run(EventDispatchThread.java:100)

hat jemand eine Idee wie ich dem Problem Herr werden kann?
Oder hat jemand so etwas ähnliches auch versucht und war damit erfolgreich?

mit bestem Dank im Voraus

Takidoso
 
So... in den letzten Tagen habe ich es aufgegeben die JComboBox, die ich als Editor in meinem Table verwende dynamisch zu reduzieren. stattdessen habe ich nach langem getüftel es geschaft die Combo, dazu zu veranlassen in ihrer Liste das am besten passende Element zu selektieren.
Schade jedoch ist dabei, dass ich nicht mehr das spezifische Betriebssystemverhalten von Swing nehme sondern lediglich (und zwar mit Trix) das BasicComboBoxUI erweitere, um somit auf die JList der Combo zu gelangen um in ihr selektieren zu können.

Hier meine Lösung für die, die so etwas vielleicht auch mal benötigen könnten.
MappedValuesCellEditor ....
Code:
package de.cmk.gui;

import javax.swing.*;
import javax.swing.table.*;
import javax.swing.plaf.basic.*;
import java.awt.event.*;
import java.awt.*;
import java.util.*;

import de.cmk.util.*;
import de.cmk.gui.table.*;

public class MappedValuesCellEditor extends DefaultCellEditor
{
    final static public int SET_ID       = 0;
    final static public int SET_VALUE    = 1;
    final static public int SET_NAMED_ID = 2;

    private final JComboBox m_combo;

    protected CellEditableDeciding  m_editableDecider  = null;
    protected CellChoiceDistributor m_auswahlLieferant = null;

    protected int m_valueMode = SET_ID;

    private DynKeyListener m_keyListener = null;
//    private java.util.List m_pickList    = null;

    private ComboBoxUI m_comboUI = new ComboBoxUI();

    public MappedValuesCellEditor()
    {
        super(new JComboBox());
        m_combo = (JComboBox)editorComponent;
//        System.out.println(m_combo.getUI().getClass());
        m_combo.setUI(m_comboUI);
        m_combo.setModel(new EditedMemberSelectingComboModel());

        delegate = new EditorDelegate()
        {
            public void setValue(Object value)
            {
                if (value instanceof NamedId)
                {
                    m_combo.setSelectedItem(value);
                }
                else
                {
                    m_combo.setSelectedItem(new NamedId(value, ""));
                }
            }

            public Object getCellEditorValue()
            {                
                Object item = m_combo.getSelectedItem();
                if (item == null)
                {
                    //falls nix richtiges rauskam, weil vorher im Editorfeld der Combo editiert wurde
                    // und der Anwender lediglich die Cursor und Entertaste betätigte
                    item = m_comboUI.getJList().getSelectedValue();
                }

                if (item instanceof NamedId)
                {
                    switch(m_valueMode)
                    {
                        case SET_ID:
                             item = ((NamedId)item).getId();
                             break;
                        case SET_VALUE:
                             item = ((NamedId)item).getId();
                        case SET_NAMED_ID:
                            break;
                    }
                }
                else
                {/// möglicherweise toter code ?
                    item = m_combo.getEditor().getItem();
                }

                return item;
            }


            public boolean shouldSelectCell(EventObject anEvent)
            {
                if (anEvent instanceof MouseEvent)
                {
                    MouseEvent e = (MouseEvent)anEvent;
                    return e.getID()!=MouseEvent.MOUSE_DRAGGED;
                }
                return true;
            }


            public boolean isCellEditable(EventObject anEvent)
            {
                boolean rc = true;
                if (anEvent instanceof MouseEvent)
                {
                    if (((MouseEvent)anEvent).getClickCount()>=clickCountToStart)
                    {
                        if (m_editableDecider!=null)
                        {
                            if (anEvent.getSource() instanceof JTable)
                            {
                                JTable sTab = (JTable)anEvent.getSource();
                                TableColumnModel colModel = sTab.getColumnModel();
                                int viewColumn = colModel.getColumnIndexAtX(((MouseEvent)anEvent).getX());
                                int column     = sTab.convertColumnIndexToModel(viewColumn);
                                int row        = sTab.rowAtPoint(((MouseEvent)anEvent).getPoint());

                                rc = m_editableDecider.isCellEditable(sTab,column, row);
                            }
                        }
                        else
                        {
                            rc = true;
                        }
                    }
                    else
                    {
                        rc = false;
                    }

                }
                return rc;
            }



        };
    }

    public Component getTableCellEditorComponent(JTable table, Object value,
                                                 boolean isSelected,
                                                 int row, int column)
    {
        delegate.setValue(value);
        if (m_auswahlLieferant!=null)
        {
            setPickList(m_auswahlLieferant.getChoices(table,value,isSelected, row, column));
        }
        return editorComponent;
    }
    public Component getTreeCellEditorComponent(JTree tree, Object value,
                                                    boolean isSelected,
                                                    boolean expanded,
                                                    boolean leaf, int row)
    {
        String stringValue = tree.convertValueToText(value, isSelected, expanded, leaf, row, false);
        delegate.setValue(stringValue);
        if (m_auswahlLieferant!=null)
        {
            setPickList(m_auswahlLieferant.getChoices(tree, value, isSelected, expanded, leaf, row));
        }

        return editorComponent;
    }

    public void setCellChoiceDistributor(CellChoiceDistributor auswahlLieferant)
    {
        m_auswahlLieferant = auswahlLieferant;
    }
    public CellChoiceDistributor getCellChoiceDistributor()
    {
        return m_auswahlLieferant;
    }

    public MappedValuesCellEditor(java.util.List namedIds)
    {
        this();
        setPickList(namedIds);
    }

    public void setEditableDecider(CellEditableDeciding decider)
    {
        m_editableDecider = decider;
    }

    public CellEditableDeciding getEditableDecider()
    {
        return m_editableDecider;
    }

    public void setPickList(java.util.List namedIds)
    {
        Object selItem = m_combo.getSelectedItem();
        m_combo.removeAllItems();
        for (int i=0; i<namedIds.size(); i++)
        {
            m_combo.addItem(namedIds.get(i));
        }
        m_combo.setSelectedItem(selItem);
//        m_pickList = namedIds;
    }

    protected void selectFromPickList(String filter) // früher reducePickList
    {
//        ((DefaultComboBoxModel) m_combo.getModel()).removeAllElements();
        JList     list   = m_comboUI.getJList();
        ListModel lModel = list.getModel();

        boolean gefunden = false;


        for (int filterLen = filter.length(); !gefunden && filterLen>0; filterLen--)
        {
            for (int i = 0; i<lModel.getSize()&&!gefunden; i++)
            {
                NamedId item = (NamedId)lModel.getElementAt(i);
                if (item.getName().regionMatches(true, 0, filter, 0, filterLen))
                {
                    list.clearSelection();
                    list.ensureIndexIsVisible(i);
                    list.setSelectedIndex(i);
                    gefunden = true;
                }
            }
        }

/*
        int x=m_pickList.size();
//        lModel.clear();
        for (int i = 0; i<x; i++)
        {
            NamedId item = (NamedId)m_pickList.get(i);
            if (item.getName().regionMatches(true,0,filter,0,filter.length()))
            {
                m_combo.addItem(item);
        //        lModel.addElement(item);
            }
        }

        for (int i=x-1; i>=0; i--)
        {
            m_combo.removeItemAt(i);
        }

*/
    }

    public void setEditable(boolean editable)
    {
        m_combo.setEditable(editable);
    }


    public void setValueMode(int modus)
    {
        if (modus<SET_ID&&modus>SET_NAMED_ID)
        {
            throw new IllegalArgumentException("modus ist falsch gesetzt.\n"+
                                               "Erlaubt sind: SET_ID, SET_VALUE, SET_NAMED_ID");
        }
        else
        {
            m_valueMode = modus;
        }
    }


    public void setDynamic(boolean on)
    {
        if (on)
        {
            m_keyListener = new DynKeyListener();
            m_combo.getEditor().getEditorComponent().addKeyListener(m_keyListener);
        }
        else
        {
            m_combo.getEditor().getEditorComponent().removeKeyListener(m_keyListener);
        }
    }

    private class DynKeyListener implements KeyListener
    {
        private javax.swing.Timer timer;
        private int m_delay     = 500;

        public DynKeyListener()
        {
            timer = new javax.swing.Timer(m_delay,
                                          new ActionListener()
                                          {
                                              public void actionPerformed(ActionEvent e)
                                              {
                                                  System.out.println(m_combo.getEditor().getItem().getClass());
                                                  String str = (String)m_combo.getEditor().getItem();
                                                  System.out.println("str="+str);
                                                  m_combo.showPopup();
                                                  selectFromPickList(str);
                                              }
                                          });
            timer.setRepeats(false);
        }


        public DynKeyListener(int delay)
        {
            this();
            m_delay = delay;
        }
        /**
         * Invoked when a key has been typed.
         * See the class description for {@link KeyEvent} for a definition of
         * a key typed event.
         */
        public void keyTyped(KeyEvent e)
        {
            timer.restart();
        }

        /**
         * Invoked when a key has been pressed.
         * See the class description for {@link KeyEvent} for a definition of
         * a key pressed event.
         */
        public void keyPressed(KeyEvent e)
        {
        }

        /**
         * Invoked when a key has been released.
         * See the class description for {@link KeyEvent} for a definition of
         * a key released event.
         */
        public void keyReleased(KeyEvent e)
        {
        }

    }// end of class DynKeyListener

    private class ComboBoxUI extends BasicComboBoxUI
    {
//        com.sun.java.swing.plaf.windows.WindowsComboBoxUI x;
        public JList getJList()
        {
            return  popup.getList();
        }
    }// end of class ComboBoxUI

}
to be continued ....
 
Hier noch die weiteren Klassen die benötigt werden
NamedId...
Code:
package de.cmk.util;

import java.text.*;

/**
 * <p>Title: NamedId </p>
 * <p>Description: wird für Picklisten (z.B. in Comboboxen) verwendet, um Ids</p>
 * <p>             nach draußen mit einer fachlichen Bezeinung darzustellen.</p>
 */

public class NamedId implements MultiStringRendering,
                                Comparable
{
    final static public int SHOW_NAME_MODE = 0;
    final static public int SHOW_ID_MODE   = 1;

    private Object  m_id;
    private String  m_name;

    protected Collator m_collator = Collator.getInstance();

    public NamedId(Object id, String name)
    {
        m_id   = id;
        m_name = name;
    }

    final public String getName()
    {
        return m_name;
    }

    final public void setName(String name)
    {
        m_name = name;
    }

    final public Object getId()
    {
        return m_id;
    }

    public String toString()
    {
        return m_name;
    }

    public String toString(int mode)
    {
        String showStr;

        switch (mode)
        {
            case SHOW_NAME_MODE:
                showStr = m_name;
                break;
            case SHOW_ID_MODE:
                showStr = m_id.toString();
                break;
            default:
                showStr = m_name;
        }

        return showStr;
    }

    public boolean equals(Object o)
    {
        boolean rc = false;
        if (o!=null)
        {
            if (o==this)
            {
                rc = true;
            }
            else if (o instanceof NamedId)
            {
                if (this.m_id!=null)
                {
                    if (this.m_id.equals(((NamedId)o).m_id))
                    {
                        rc = true;
                    }
                }
                else if (((NamedId)o).m_id==null)
                {
                    rc = true;
                }
            }
        }// end of if (o!=null)

        return rc;
    }

    public int compareTo(Object o)
    {
        int rc = -1;
        if (o!= null)
        {
            rc = m_collator.compare(toString(),o.toString());
        }
        return rc;
    }

    public int hashCode()
    {
        return m_id.hashCode();
    }

}// end of NamedId

MultiStringRendering ...
Code:
package de.cmk.util;

public interface MultiStringRendering
{
    String toString(int mode);
}

Falls die Pickliste von irgenetwas - z.B. einem Inhalt in einer anderen Zelle der selben Zeile abhängig - bestimmt werden soll.

CellChoiceDistributor...
Code:
package de.cmk.gui;

import java.util.*;
import javax.swing.*;

public interface CellChoiceDistributor
{
    List getChoices(JTable table, Object value, boolean isSelected,
                    int row, int column);
    List getChoices(JTree tree,   Object value, boolean isSelected,
                    boolean expanded, boolean leaf, int row);
}

CellEditableDeciding...
Code:
package de.cmk.gui.table;

/**
 * <p>Title: CellEditableDeciding</p>
 * <p>Description: wird von bestimmten TableCellEditoren verwendet, um ein </p>
 * <p>             Erlauben die Zelle zu editieren genauer zu differenzieren.</p>
 */

public interface CellEditableDeciding
{
    /**
     *
     * @param source ist normalerweise ein JTable oder davon abgeleitet
     * @param column Spalte im TableModel
     * @param row    Zeile  im TableModel
     * @return ob Zelle editierbar sein soll (true) oder nicht (false)
     */
    boolean isCellEditable(Object source, int column, int row);
}

EditedMemberSelectingComboModel ...
Code:
package de.cmk.gui.table;

import javax.swing.DefaultComboBoxModel;

import de.cmk.util.*;

/**
 * <p>Title: EditedMemberSelectingComboModel
 * <p>Description: ComboboxModel, welches selektierte Objecte in seiner Liste sucht </p>
 * <p>             und anstelle der Vorgabe einsetzt. Dies hat den Grund um sicherzugehen, </p>
 * <p>             dass editierte Objecte nur angenommen werden, wenn sie in der Liste gefunden werden. </p>
 */

public class EditedMemberSelectingComboModel extends DefaultComboBoxModel
{
    public EditedMemberSelectingComboModel()
    {
        super();
    }

    public EditedMemberSelectingComboModel(final Object[] items)
    {
        super(items);
    }

    /**
     * Setzt
     * <p>
     * @param anObject The combo box value or null for no selection.
     */
    public void setSelectedItem(Object anObject)
    {
        Object o =  getElementAt(0);
        int x;
        if (o instanceof NamedId && !(anObject instanceof NamedId))
        {
            o = new NamedId(anObject,"");
            x = getIndexOf(o);
        }
        else
        {
            x = getIndexOf(anObject);
        }

        o = getElementAt(x);

        super.setSelectedItem(o);
    }

}

So ich hoffe dem ein oder anderem die mühselige Arbeit zu ersparen für einen solchen Editor.
Auch wenn ich es noch nie getestet hatte müsste er, sich auch für JTree's eignen.

In Hoffnung keine Klasse vergessen zu haben, falls doch bitte melden ;)

Takidoso
 
Zurück