JComboBox, wlasny model i wyszukiwanie wg klawiszy

0

Na poczatek kod:

package test;

import java.awt.Component;
import java.awt.FlowLayout;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Locale;

import javax.swing.AbstractListModel;
import javax.swing.ComboBoxModel;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JList;

public class LocaleTest {

    public static void main(String[] args) {

        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLayout(new FlowLayout());
        JComboBox combo = new JComboBox(new LocaleModel());
        combo.setRenderer(new LocaleRenderer());
        frame.add(combo);
        frame.setLocationRelativeTo(null);
        frame.pack();
        frame.setVisible(true);
    }
}

class LocaleModel extends AbstractListModel implements ComboBoxModel {

    private Locale[] locales;

    private Locale selected;

    public LocaleModel() {
        locales = Locale.getAvailableLocales();
        Arrays.sort(locales, new Comparator<Locale>() {

            @Override
            public int compare(Locale o1, Locale o2) {
                return o1.getDisplayName().compareTo(o2.getDisplayName());
            }
        });
        selected = Locale.getDefault();
    }

    @Override
    public Locale getElementAt(int index) {
        return locales[index];
    }

    @Override
    public int getSize() {
        return locales.length;
    }

    @Override
    public Locale getSelectedItem() {
        return selected;
    }

    @Override
    public void setSelectedItem(Object anItem) {
        selected = (Locale) anItem;
    }
}

class LocaleRenderer extends DefaultListCellRenderer {
 
   @Override
    public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
        Locale l = (Locale) value;
        return super.getListCellRendererComponent(list, l.getDisplayName(), index, isSelected, cellHasFocus);
    }

}

Kod wyswietla kombo z alfabetycznie posortowanymi localami, posortowanymi wg getDisplayName(). Mam wlasny model i wlasnego renderera - model przechowuje obiekty Locale, a renderer pokazuje ich getDisplayName(). Ladnie dziala, jest tylko 1 problem. Rozwincie liste, i wcisnijcie kilka razy literke H - wlaczy sie wyszukiwanie w liscie wg literek - wyszuka po kolei Hindi, Hungarian, Hungarian (Hungary) (poki co ladnie) i teraz zonk - wyszukuje rowniez Croatian i Croatian (Croatia) (uzywam angielskiego locale). Wynika to z tego ze kod Locale dla Chorwacji to hr.
Przyjrzalem sie sprawie nieco blizej i oto co znalazlem: klasa BasicComboBoxUI ma klase wewnetrzna DefaultKeySelectionManager, ktora ma metode selectionForKey() uruchamiana gdy wciskamy klawisz przy rozwinietej liscie kombo. Ona wewnetrznie korzysta z JList (linijka 1947 w kodzie bibliotek javy 1.6u15), konkretnie metode getNextMatch(), ktora to z kolei wykorzystuje dostarczony przeze mnie model. Logika tam jest taka ze w petli (poczatek to aktualnie zaznaczony indeks +1) pobierane sa elementy modelu i zamieniane na String, i nastepnie sprawdzane czy wcisnieta przez usera literka jest pierwsza literka tego stringa.
Troche lipa bo np moj renderer nie wyswietla Locale.toString() tylko Locale.getDisplayName(), i stad takie dziwne rzeczy wychodza. Co wiecej, mozna sobie wyobrazic modele ktorych elementy zupelnie nie beda sie nadawac zeby zwracac sensowna wartosc z toString(), a wyszukiwanie po literkach nadal bedzie dzialac.

W koncu pytanie - czy ktos wie jak to zmienic na takie dzialanie jak chce, bez wielkiego hakowanai Swinga? Nie wpadlem poki co na nic oczywistego, moze ktos juz wczesniej tym sie zajmowal.
Pozdrawiam.

0

JComboBox ma metodę setKeySelectionManager(...)

public class LocaleTest {

    public static void main(String[] args) {

        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLayout(new FlowLayout());
        JComboBox combo = new JComboBox(new LocaleModel());
        combo.setRenderer(new LocaleRenderer());
        combo.setKeySelectionManager(new Testowa());
        frame.add(combo);
        frame.setLocationRelativeTo(null);
        frame.pack();
        frame.setVisible(true);
    }
}
....
class Testowa implements JComboBox.KeySelectionManager
{
    @Override
    public int selectionForKey(char key,ComboBoxModel model)
    {
        return 0;
    }
}

Jak nalezy oczekiwać, reakcją na dowolny klawisz jest skok do pierwszego wiersza (albński).

0

W ramach samokształcenia napisałem bardziej realistyczny przykład:

public class LocaleTest {

    public static void main(String[] args) {

        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLayout(new FlowLayout());
        LocaleModel model=new LocaleModel();
        JComboBox combo = new JComboBox(model);
        combo.setRenderer(new LocaleRenderer());
        combo.setKeySelectionManager(new Testowa(combo,model));
        frame.add(combo);
        frame.setLocationRelativeTo(null);
        frame.pack();
        frame.setVisible(true);
    }
}
....
class Testowa implements JComboBox.KeySelectionManager
{
    Locale[] locales=null;
    JComboBox combo=null;
    //------------------------
    public Testowa(JComboBox combo,LocaleModel model)
    {
        this.combo=combo;
        locales=model.getLocales();
    }
    //------------------------
    @Override
    public int selectionForKey(char key,ComboBoxModel model)
    {
        int lower=0;
        int index=combo.getSelectedIndex();
        if(locales[index].getDisplayName().charAt(0)==key)
            lower=index+1;
        for(int i=lower;i<locales.length;i++)
            if(locales[i].getDisplayName().charAt(0)==key)
                return i;
        return -1;
    }
}

pozdrawiam

0

Dzieki! Z jakiegos powodu nie wpadlem na to zeby poszukac tej metody... [wstyd] Swojego selection managera to umiem napisac, nie wiedzial tylko jak to podmienic - zacmienie.
Dzieki, pozdro.

0

Taka ciekawostka jeszcze - domyslnie ten manager pochodzi z implementacji ComboBoxUI, jesli jednak wywolam JComboBox.setKeySelectionManager(null) to przy nastepnym wywolaniu metody JComboBox.selectWithKey() (wywolywana przez listenera na nacisniecie guzika), tworzony jest domyslny obiekt tego typu ktorego implementacja istnieje w JBomboBox jako klasa zagniezdzona. Czyli wywolanie setKeySelectionManager(null) wcale nie wylacza tej funkcjonalnosci. Czy ktos (poza mna) widzi to jako bug?

0

W kolejności chronologicznej:
Byłem pewien, że potrafisz napisać selectionManagera, ale nie byłem pewien czy ja potrafię ;-) więc spróbowałem.
Też widzę w tym bug. Nieco sztucznie można wyłączyć szukanie podpinając klasę, której metoda selectionForKey(..) zwraca zawsze -1.

0

Zgadza sie, mozna z -1, ale to wymaga nowej klasy (nawet zagniezdzonej) zeby wylaczyc to urzadzenie. Jakos mi to nie pasuje, pierwszy odruch to ustawic na nulla i juz.
Jeszcze raz dzieki i pozdrawiam.

0

Odruch żeby ustawić na null jest zdrowy, ale (fragment kodu klasy JComboBox)

    public boolean selectWithKeyChar(char keyChar) {
        int index;

        if ( keySelectionManager == null )
            keySelectionManager = createDefaultKeySelectionManager();

1 użytkowników online, w tym zalogowanych: 0, gości: 1