Problema com autocomplete na JComboBox usando L&F [RESOLVIDO]

3 respostas
D

Oi pessoal.

Encontrei o seguinte código pra autocomplete numa JComboBox:

import java.awt.AWTKeyStroke;
import java.awt.KeyboardFocusManager;
import java.awt.event.*;
import java.text.Normalizer;
import java.util.*;
import java.util.regex.Pattern;
import javax.swing.*;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.text.*;

public class AutoCompleteComboBox extends JComboBox implements JComboBox.KeySelectionManager {

    private String searchFor;
    private long lap;
    private boolean pressionado = false; //indica se as setas para cima ou para baixo foram pressionadas

    public class CBDocument extends PlainDocument {

        @Override
        public void insertString(int offset, String str, AttributeSet a) throws BadLocationException {
            if (isPopupVisible()) //se o popup estiver visível ao digitar
                setPopupVisible(false); //fecha, caso contrário a pesquisa não funciona            
            
            if (str == null)
                return;
            
            super.insertString(offset, str, a);
            if (!isPopupVisible() && str.length() != 0)
                fireActionEvent();
        }
    }

    public AutoCompleteComboBox2() {

        lap = new java.util.Date().getTime(); //recupera o tempo atual
        setKeySelectionManager(this); //utilizado para pesquisar na comboBox
        final JTextField tf;

        //faz a tecla ENTER se comportar como a tecla TAB:
        Set<AWTKeyStroke> keystrokes = getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS);
        Set<AWTKeyStroke> newKeystrokes = new HashSet<AWTKeyStroke>(keystrokes);
        newKeystrokes.add(AWTKeyStroke.getAWTKeyStroke(KeyEvent.VK_ENTER, 0));
        setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, newKeystrokes);
                
        if (getEditor() != null) {
            tf = (JTextField) getEditor().getEditorComponent();
            if (tf != null) {
                tf.setDocument(new CBDocument());

                tf.addKeyListener(new KeyAdapter() {

                    @Override
                    public void keyPressed(KeyEvent e) {
                        if (e.getKeyCode() == 38 || e.getKeyCode() == 40) { //se pressionar a seta para cima ou a seta para baixo
                            setPopupVisible(true); 
                            pressionado = true;
                        } else //se pressionar qualquer outra tecla
                        {
                            pressionado = false;
                        }
                    }
                });

                tf.addFocusListener(new FocusAdapter() {
                    @Override
                    public void focusGained(FocusEvent e) {
                        tf.selectAll();
                    }                                
                });
                
                addPopupMenuListener(new PopupMenuListener() {

                    @Override
                    public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
                        pressionado=true; //indica que vai cancelar o evento actionPerformed
                    }

                    @Override
                    public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
                    }

                    @Override
                    public void popupMenuCanceled(PopupMenuEvent e) {
                    }
                });
                
                addActionListener(new ActionListener() { //adiciona o evento à combobox

                    @Override
                    public void actionPerformed(ActionEvent evt) {
                        if (pressionado) //se pressionou a seta para cima ou para baixo, ou abriu o popup, cancela o evento actionPerformed e apenas alterna entre os itens
                            return;
                        
                        JTextField tf = (JTextField) getEditor().getEditorComponent(); //textField que é o editor da combobox
                        String text = tf.getText(); //texto digitado na combobox
                        ComboBoxModel aModel = getModel(); //modelo da combobox
                        String current; //item da combobox
                        String temp = Normalizer.normalize(text, Normalizer.Form.NFD); //remove a acentuação do texto digitado
                        Pattern pattern = Pattern.compile("\\p{InCombiningDiacriticalMarks}+"); //remove acentos e 'ç'
                        String textNormalizado = pattern.matcher(temp).replaceAll("").toLowerCase(); //recebe o texto normalizado digitado na combobox                        
                        String temp2; //normaliza o item da combobox
                        String itemNormalizado; //recebe o item da combobox normalizado                        

                        for (int i = 0; i < aModel.getSize(); i++) { //percorre os itens da comboBox
                            current = aModel.getElementAt(i).toString(); //recebe o item da iteração

                            temp2 = Normalizer.normalize(current, Normalizer.Form.NFD); //remove a acentuação do item da iteração
                            itemNormalizado = pattern.matcher(temp2).replaceAll("").toLowerCase(); //recebe o item da iteração normalizado

                            if (itemNormalizado.startsWith(textNormalizado)) {
                                System.out.println("entrou aqui igual");
                                tf.setText(current); //atribui o item à combobox
                                tf.setSelectionStart(text.length());
                                tf.setSelectionEnd(current.length());
                                setSelectedIndex(i); //foca a lista no item encontrado
                                break;
                            }
                        }
                    }
                });
            }
        }
    }
            
    @Override
    public int selectionForKey(char aKey, ComboBoxModel aModel) {
        long now = new java.util.Date().getTime();
        if (searchFor != null && aKey == KeyEvent.VK_BACK_SPACE && searchFor.length() > 0) {
            searchFor = searchFor.substring(0, searchFor.length() - 1);
        } else {
            if (lap + 1000 < now) { //se o próximo caracter inserido foi digitado a menos de 1 segundo atrás
                searchFor = "" + aKey; //acrescenta o caracter na pesquisa
            } else {
                searchFor = searchFor + aKey;
            }
        }
        lap = now;
        String current;
        for (int i = 0; i < aModel.getSize(); i++) {
            current = aModel.getElementAt(i).toString().toLowerCase();
            if (current.toLowerCase().startsWith(searchFor.toLowerCase())) {
                return i;
            }
        }
        return -1;
    }

    @Override
    public void fireActionEvent() {
        super.fireActionEvent();
    }
}

Para usar esse código, basta fazer:

JComboBox comboBox=new AutoCompleteComboBox();
O código funciona perfeitamente, mas somente com os L&F MetalLookAndFeel ou Nimbus. Eu preciso usar este:
UIManager.setLookAndFeel(new com.jgoodies.looks.plastic.PlasticXPLookAndFeel());
Só que com ele, quando vou digitar na combobox, aparece logo o item completo, por exemplo, ao somente a letra 'r' e tenho o item "rock", utilizando o L&F Metal:

[img]http://image.bayimg.com/ialboaadp.jpg[/img]

Agora utilizando o L&F PlasticXPLookAndFeel, o item já aparece completo e não me deixa usar o autocomplete:

[img]http://image.bayimg.com/aalckaadp.jpg[/img]

Se alguém tiver uma ideia pra me ajudar, desde já agradeço.

3 Respostas

fernandopaiva

veja isso: http://guj.com.br/java/261349-jcombobox-com-swingx-quase-tudo-funcionando-

ai eu mostro como eu fiz.

t+ e boa sorte.

D

Vlw fernandopaiva!

Eu resolvi meu problema tirando a linha 116. Ela focava no item encontrado, assim, quando eu exibisse a lista da combobox, iria automaticamente para o item que estivesse digitado.

Eu consigo armazenar o índice do item encontrado, agora me resta saber como setar o foco nesse índice quando o evento popupMenuWillBecomeVisible for invocado.

D

Consegui fazer tudo que queria. Tenho a classe de autocomplete:

import java.awt.AWTKeyStroke;
import java.awt.KeyboardFocusManager;
import java.awt.event.*;
import java.text.Normalizer;
import java.util.*;
import java.util.regex.Pattern;
import javax.swing.*;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.text.*;

public class AutoCompleteComboBox extends JComboBox implements JComboBox.KeySelectionManager {

    private String searchFor;
    private long lap;
    private boolean pressionado = false; //indica se as setas para cima ou para baixo foram pressionadas
    private int index; //índice do item digitado, se não existir na lista: -1

    public class CBDocument extends PlainDocument {

        @Override
        public void insertString(int offset, String str, AttributeSet a) throws BadLocationException {
            if (isPopupVisible()) //se o popup estiver visível ao digitar
                setPopupVisible(false); //fecha, caso contrário a pesquisa não funciona            
            
            if (str == null)
                return;
            
            super.insertString(offset, str, a);
            if (!isPopupVisible() && str.length() != 0)
                fireActionEvent();
        }
    }

    public AutoCompleteComboBox() {

        lap = new java.util.Date().getTime(); //recupera o tempo atual
        setKeySelectionManager(this); //utilizado para pesquisar na comboBox
        final JTextField tf;

        //faz a tecla ENTER se comportar como a tecla TAB:
        Set<AWTKeyStroke> keystrokes = getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS);
        Set<AWTKeyStroke> newKeystrokes = new HashSet<AWTKeyStroke>(keystrokes);
        newKeystrokes.add(AWTKeyStroke.getAWTKeyStroke(KeyEvent.VK_ENTER, 0));
        setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, newKeystrokes);
                
        if (getEditor() != null) {
            tf = (JTextField) getEditor().getEditorComponent();
            if (tf != null) {
                tf.setDocument(new CBDocument());

                tf.addKeyListener(new KeyAdapter() {

                    @Override
                    public void keyPressed(KeyEvent e) {
                        if (e.getKeyCode() == 38 || e.getKeyCode() == 40) { //se pressionar a seta para cima ou a seta para baixo
                            setPopupVisible(true); 
                            pressionado = true;
                        } 
                        
                        else //se pressionar qualquer outra tecla
                            pressionado = false;
                    }
                });

                tf.addFocusListener(new FocusAdapter() {
                    @Override
                    public void focusGained(FocusEvent e) {
                        tf.selectAll();
                    }                                
                });
                
                addPopupMenuListener(new PopupMenuListener() {

                    @Override
                    public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
                        pressionado=true; //indica que vai cancelar o evento actionPerformed                                                
                        setSelectedIndex(index); //seleciona o item na lista da combobox, se for -1 não seleciona nada
                    }

                    @Override
                    public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
                    }

                    @Override
                    public void popupMenuCanceled(PopupMenuEvent e) {
                    }
                });
                
                addActionListener(new ActionListener() { //adiciona o evento à combobox

                    @Override
                    public void actionPerformed(ActionEvent evt) {
                        if (pressionado) //se pressionou a seta para cima ou para baixo, ou abriu o popup, cancela o evento actionPerformed e apenas alterna entre os itens
                            return;
                        
                        JTextField tf = (JTextField) getEditor().getEditorComponent(); //textField que é o editor da combobox
                        String text = tf.getText(); //texto digitado na combobox
                        ComboBoxModel aModel = getModel(); //modelo da combobox
                        String current; //item da combobox
                        String temp = Normalizer.normalize(text, Normalizer.Form.NFD); //remove a acentuação do texto digitado
                        Pattern pattern = Pattern.compile("\\p{InCombiningDiacriticalMarks}+"); //remove acentos e 'ç'
                        String textNormalizado = pattern.matcher(temp).replaceAll("").toLowerCase(); //recebe o texto normalizado digitado na combobox                        
                        String temp2; //normaliza o item da combobox
                        String itemNormalizado; //recebe o item da combobox normalizado                        

                        for (int i = 0; i < aModel.getSize(); i++) { //percorre os itens da comboBox
                            current = aModel.getElementAt(i).toString(); //recebe o item da iteração

                            temp2 = Normalizer.normalize(current, Normalizer.Form.NFD); //remove a acentuação do item da iteração
                            itemNormalizado = pattern.matcher(temp2).replaceAll("").toLowerCase(); //recebe o item da iteração normalizado

                            if (itemNormalizado.startsWith(textNormalizado)) {
                                tf.setText(current); //atribui o item à combobox
                                tf.setSelectionStart(text.length());
                                tf.setSelectionEnd(current.length());
                                index=i;                                                                
                                break;
                            }
                            
                            else
                                index=-1;
                        }
                    }
                });
            }
        }
    }
            
    @Override
    public int selectionForKey(char aKey, ComboBoxModel aModel) {
        long now = new java.util.Date().getTime();
        if (searchFor != null && aKey == KeyEvent.VK_BACK_SPACE && searchFor.length() > 0) 
            searchFor = searchFor.substring(0, searchFor.length() - 1);
        
        else {
            if (lap + 1000 < now) //se o próximo caracter inserido foi digitado a menos de 1 segundo atrás
                searchFor = "" + aKey; //acrescenta o caracter na pesquisa

            else
                searchFor = searchFor + aKey;
        }
        lap = now;
        String current;
        for (int i = 0; i < aModel.getSize(); i++) {
            current = aModel.getElementAt(i).toString().toLowerCase();
            if (current.toLowerCase().startsWith(searchFor.toLowerCase()))
                return i;
        }
        return -1;
    }

    @Override
    public void fireActionEvent() {
        super.fireActionEvent();
    }
}

E a classe model da combobox:

import java.util.ArrayList;
import javax.swing.AbstractListModel;
import javax.swing.MutableComboBoxModel;

public class MyComboBoxModel extends AbstractListModel implements MutableComboBoxModel {
    private ArrayList<String> listaCombo; //lista de itens da combobox
    private Object selectedItem; //objeto selecionado na combobox
    
    public MyComboBoxModel(ArrayList<String> lista) {
        listaCombo=lista;
    }
        
    @Override
    public int getSize() {
        return listaCombo.size();
    }

    @Override
    public Object getElementAt(int index) {
        return listaCombo.get(index);
    }

    @Override
    public void addElement(Object obj) {
        listaCombo.add(obj.toString());
    }

    @Override
    public void removeElement(Object obj) {
        listaCombo.remove(obj);
    }

    @Override
    public void insertElementAt(Object obj, int index) {
        listaCombo.add(index, obj.toString());
    }

    @Override
    public void removeElementAt(int index) {
        listaCombo.remove(index);
    }

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

    @Override
    public Object getSelectedItem() {
        return selectedItem;
    }
}
Para ter a combobox com essas classes:
JCombobox jComboBox1=new AutoCompleteComboBox();
ArrayList<String> lista=new ArrayList<String>();
lista.add("item1");
lista.add("item2");
lista.add("item3");
jComboBox1.setModel(new MyComboBoxModel(lista));

Assim, a combobox criada terá autocomplete e quando exibir a lista de itens, se houver o item digitado, ele estará selecionado, caso contrário, nada será selecionado.

Criado 16 de janeiro de 2012
Ultima resposta 16 de jan. de 2012
Respostas 3
Participantes 2