4

我正在编写一个扩展 JComboBox 的自定义组件。我的问题是,如果我添加或删除一个项目,PopupMenu 不会实现它的大小。因此,例如列表中有 2 个项目,但如果之前有 4 个项目,我在 PopupMenu 中也有 2 个“空”项目。

我发现的唯一解决方法是(在 JIntelligentComboBox.java 第 213 行)


this.setPopupVisible(false);
this.setPopupVisible(true);

但结果将是一个闪烁的 PopupMenu :-(

那么我还能做些什么来刷新/重新绘制 PopupMenu 而不会闪烁?

测试:组件和一个小测试程序
要生成我的问题,您可以例如:

  • 键入“e”
  • 按“返回”
  • 键入“m”

提前致谢

编辑:我的目标是一个组合框,它的作用类似于 Firefox 或 Chrome 中的地址栏,我想显示包含键入字符的 PopupMenu 的所有项目。

cboxtester.java:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;

import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.plaf.basic.BasicComboBoxRenderer;


public class cboxtester extends JFrame {

    private DefaultComboBoxModel dcm = new DefaultComboBoxModel(new Object[][] {new Object[] {"Mittagessen", "", 0}, 
                                                                                new Object[] {"Essen", "", 0}, 
                                                                                new Object[] {"Frühstück", "", 0}, 
                                                                                new Object[] {"Abendessen", "", 0}});

    private JIntelligentComboBox icb = new JIntelligentComboBox(dcm);

    private cboxtester(){
        this.add(icb, BorderLayout.CENTER);

        this.add(new JButton("bla"), BorderLayout.EAST);

        this.pack();
        this.setVisible(true);
        this.setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        cboxtester cbt = new cboxtester();
    }

}

JIntelligentComboBox.java:

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.Vector;

import javax.swing.ComboBoxEditor;
import javax.swing.ComboBoxModel;
import javax.swing.DefaultListCellRenderer;
import javax.swing.DefaultRowSorter;
import javax.swing.JComboBox;
import javax.swing.JList;
import javax.swing.JTextField;
import javax.swing.ListCellRenderer;
import javax.swing.MutableComboBoxModel;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.plaf.basic.BasicComboBoxEditor;
import javax.swing.plaf.basic.BasicComboBoxRenderer;
import javax.swing.plaf.metal.MetalComboBoxEditor;


public class JIntelligentComboBox extends JComboBox {

    private ArrayList<Object> itemBackup = new ArrayList<Object>();

    /**  Initisiert die JIntelligentComboBox */
    private void init(){

        class searchComboBoxEditor extends BasicComboBoxEditor {
            public searchComboBoxEditor(){
                super();
            }

            @Override
            public void setItem(Object anObject){
                if (anObject == null) {
                    super.setItem(anObject);
                } else {
                    Object[] o = (Object[]) anObject;
                    super.setItem(o[0]);
                }
            }

            @Override
            public Object getItem(){
                return new Object[]{super.getItem(), super.getItem(), 0};
            }
        }

        this.setEditor(new searchComboBoxEditor());

        this.setEditable(true);

        class searchRenderer extends BasicComboBoxRenderer {

            public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus){
                if (index == 0) {
                    setText("");
                    this.setPreferredSize(new Dimension(1, 1));
                    return this;
                }

                this.setPreferredSize(new Dimension(160, 17));

                if (index == list.getModel().getSize() - 1) {
                    this.setBorder(new EmptyBorder(0, 3, 1, 3));
                } else {
                    this.setBorder(new EmptyBorder(0, 3, 0, 3));
                }

                Object[] v = (Object[]) value;
                //System.out.println(v[0]);
                this.setFont(new Font("Arial", Font.PLAIN, 12));
                this.setBackground(Color.white);

                String s = (String) v[0];
                String lowerS = s.toLowerCase();
                String sf = (String) v[1];
                String lowerSf = sf.toLowerCase();
                ArrayList<String> notMatching = new ArrayList<String>();

                if (!sf.equals("")){
                    int fs = -1;
                    int lastFs = 0;
                    while ((fs = lowerS.indexOf((String) lowerSf, (lastFs == 0) ? -1 : lastFs)) > -1) {
                        notMatching.add(s.substring(lastFs, fs));
                        lastFs = fs + sf.length();
                        //System.out.println(fs+sf.length());
                    }
                    notMatching.add(s.substring(lastFs));
                    //System.out.println(notMatching);
                }

                String html = "";

                if (notMatching.size() > 1) {
                    html = notMatching.get(0);
                    int start = html.length();
                    int sfl = sf.length();
                    for (int i = 1; i < notMatching.size(); i++) {
                        String t = notMatching.get(i);
                        html += "<b style=\"color: black;\">" + s.substring(start, start + sfl) + "</b>" + t;
                        start += sfl + t.length();
                    }
                }

                System.out.println(index + html);

                this.setText("<html><head></head><body style=\"color: gray;\">" + html + "</body></head>");
                return this;
            }

        }  

        this.setRenderer(new searchRenderer());

        // leeres Element oben einfügen
        int size = this.getModel().getSize();
        Object[] tmp = new Object[this.getModel().getSize()];

        for (int i = 0; i < size; i++) {
            tmp[i] = this.getModel().getElementAt(i);
            itemBackup.add(tmp[i]);
        }

        this.removeAllItems();

        this.getModel().addElement(new Object[]{"", "", 0});
        for (int i = 0; i < tmp.length; i++) {
            this.getModel().addElement(tmp[i]);
        }

        // keylistener hinzufügen
        this.getEditor().getEditorComponent().addKeyListener(new KeyListener() {

            @Override
            public void keyPressed(KeyEvent e) {
                // TODO Auto-generated method stub
            }

            @Override
            public void keyReleased(KeyEvent e) {
                // TODO Auto-generated method stub
                searchAndListEntries(((JTextField)JIntelligentComboBox.this.getEditor().getEditorComponent()).getText());
                //System.out.println(((JTextField)JIntelligentComboBox.this.getEditor().getEditorComponent()).getText());
            }

            @Override
            public void keyTyped(KeyEvent e) {
                // TODO Auto-generated method stub
            }
        });
    }

    public JIntelligentComboBox(){
        super();
    }

    public JIntelligentComboBox(MutableComboBoxModel aModel){
        super(aModel);
        init();
    }

    public JIntelligentComboBox(Object[] items){
        super(items);
        init();
    }

    public JIntelligentComboBox(Vector<?> items){
        super(items);
        init();
    }

    @Override
    public MutableComboBoxModel getModel(){
        return (MutableComboBoxModel) super.getModel();
    }

    private void searchAndListEntries(Object searchFor){        
        ArrayList<Object> found = new ArrayList<Object>();

        //System.out.println("sf: "+searchFor);

        for (int i = 0; i < this.itemBackup.size(); i++) {
            Object tmp = this.itemBackup.get(i);
            if (tmp == null || searchFor == null) continue;

            Object[] o = (Object[]) tmp;
            String s = (String) o[0];
            if (s.matches("(?i).*" + searchFor + ".*")){
                found.add(new Object[]{((Object[])tmp)[0], searchFor, ((Object[])tmp)[2]});
            }
        }

        this.removeAllItems();          

        this.getModel().addElement(new Object[] {searchFor, searchFor, 0});

        for (int i = 0; i < found.size(); i++) {
            this.getModel().addElement(found.get(i));
        }

        this.setPopupVisible(true);     
    }



}
4

5 回答 5

7

我在下面修改了你的sscce,我注意到了一些事情:

  1. 您观察到的异常在使用时并不明显apple.laf.AquaComboBoxUI。特别是,输入和删除文本会按预期增长和缩小列表。您可以在您的平台上尝试修改后的代码。

  2. 为了方便起见,我从 切换KeyListenerKeyAdapter,但这不是解决方案。您可能应该使用DocumentListener. 它不能在使用时变异,就像你现在正在做的那样,所以我没有进一步追求这个。

  3. 始终在事件调度线程上构建 GUI 。

  4. 硬编码的尺寸和新颖的字体很少在其他外观实现中看起来正确。我只是删除了你的以获得显示的外观。

  5. 您的构造函数在构造父模型后修改模型,因此结果取决于实例化的顺序。单独的模型可能更易于管理。

更新:添加了验证@camickr解决方案的代码。

组合图像

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Window;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JTextField;
import javax.swing.MutableComboBoxModel;
import javax.swing.SwingUtilities;
import javax.swing.plaf.basic.BasicComboBoxEditor;
import javax.swing.plaf.basic.BasicComboBoxRenderer;
import javax.swing.plaf.basic.BasicComboPopup;

public class CBoxTest extends JFrame {

    private CBoxTest() {
        DefaultComboBoxModel dcm = new DefaultComboBoxModel();
        StringBuilder s = new StringBuilder();
        for (char i = 'a'; i < 'm'; i++) {
            s.append(i);
            dcm.addElement(new Object[]{s.toString(), "", 0});
        }
        JIntelligentComboBox icb = new JIntelligentComboBox(dcm);
        this.setDefaultCloseOperation(EXIT_ON_CLOSE);
        this.add(icb, BorderLayout.CENTER);
        this.add(new JButton("Button"), BorderLayout.EAST);
        this.pack();
        this.setLocationRelativeTo(null);
        this.setVisible(true);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                CBoxTest cbt = new CBoxTest();
            }
        });
    }

    class JIntelligentComboBox extends JComboBox {

        private List<Object> itemBackup = new ArrayList<Object>();

        public JIntelligentComboBox(MutableComboBoxModel aModel) {
            super(aModel);
            init();
        }

        private void init() {
            this.setRenderer(new searchRenderer());
            this.setEditor(new searchComboBoxEditor());
            this.setEditable(true);
            int size = this.getModel().getSize();
            Object[] tmp = new Object[this.getModel().getSize()];
            for (int i = 0; i < size; i++) {
                tmp[i] = this.getModel().getElementAt(i);
                itemBackup.add(tmp[i]);
            }
            this.removeAllItems();
            this.getModel().addElement(new Object[]{"", "", 0});
            for (int i = 0; i < tmp.length; i++) {
                this.getModel().addElement(tmp[i]);
            }
            final JTextField jtf = (JTextField) this.getEditor().getEditorComponent();
            jtf.addKeyListener(new KeyAdapter() {

                @Override
                public void keyReleased(KeyEvent e) {
                    searchAndListEntries(jtf.getText());
                }
            });
        }

        @Override
        public MutableComboBoxModel getModel() {
            return (MutableComboBoxModel) super.getModel();
        }

        private void searchAndListEntries(Object searchFor) {
            List<Object> found = new ArrayList<Object>();
            for (int i = 0; i < this.itemBackup.size(); i++) {
                Object tmp = this.itemBackup.get(i);
                if (tmp == null || searchFor == null) {
                    continue;
                }
                Object[] o = (Object[]) tmp;
                String s = (String) o[0];
                if (s.matches("(?i).*" + searchFor + ".*")) {
                    found.add(new Object[]{
                            ((Object[]) tmp)[0], searchFor, ((Object[]) tmp)[2]
                        });
                }
            }
            this.removeAllItems();
            this.getModel().addElement(new Object[]{searchFor, searchFor, 0});
            for (int i = 0; i < found.size(); i++) {
                this.getModel().addElement(found.get(i));
            }
            this.setPopupVisible(true);
            // https://stackoverflow.com/questions/7605995
            BasicComboPopup popup =
                (BasicComboPopup) this.getAccessibleContext().getAccessibleChild(0);
            Window popupWindow = SwingUtilities.windowForComponent(popup);
            Window comboWindow = SwingUtilities.windowForComponent(this);

            if (comboWindow.equals(popupWindow)) {
                Component c = popup.getParent();
                Dimension d = c.getPreferredSize();
                c.setSize(d);
            } else {
                popupWindow.pack();
            }
        }

        class searchRenderer extends BasicComboBoxRenderer {

            @Override
            public Component getListCellRendererComponent(JList list,
                Object value, int index, boolean isSelected, boolean cellHasFocus) {
                if (index == 0) {
                    setText("");
                    return this;
                }
                Object[] v = (Object[]) value;
                String s = (String) v[0];
                String lowerS = s.toLowerCase();
                String sf = (String) v[1];
                String lowerSf = sf.toLowerCase();
                List<String> notMatching = new ArrayList<String>();

                if (!sf.equals("")) {
                    int fs = -1;
                    int lastFs = 0;
                    while ((fs = lowerS.indexOf(lowerSf, (lastFs == 0) ? -1 : lastFs)) > -1) {
                        notMatching.add(s.substring(lastFs, fs));
                        lastFs = fs + sf.length();
                    }
                    notMatching.add(s.substring(lastFs));
                }
                String html = "";
                if (notMatching.size() > 1) {
                    html = notMatching.get(0);
                    int start = html.length();
                    int sfl = sf.length();
                    for (int i = 1; i < notMatching.size(); i++) {
                        String t = notMatching.get(i);
                        html += "<b style=\"color: black;\">"
                            + s.substring(start, start + sfl) + "</b>" + t;
                        start += sfl + t.length();
                    }
                }
                this.setText("<html><head></head><body style=\"color: gray;\">"
                    + html + "</body></head>");
                return this;
            }
        }

        class searchComboBoxEditor extends BasicComboBoxEditor {

            public searchComboBoxEditor() {
                super();
            }

            @Override
            public void setItem(Object anObject) {
                if (anObject == null) {
                    super.setItem(anObject);
                } else {
                    Object[] o = (Object[]) anObject;
                    super.setItem(o[0]);
                }
            }

            @Override
            public Object getItem() {
                return new Object[]{super.getItem(), super.getItem(), 0};
            }
        }
    }
}
于 2011-09-30T03:57:07.737 回答
5

以下解决方案的基础是每次调用searchAndListRoutine. 您需要考虑,当弹出窗口显示在父框架的边界之外时,弹出窗口可以显示在自己的窗口中,也可以显示在父框架的分层窗格中:

import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.Vector;

import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
import javax.swing.plaf.basic.*;
import javax.swing.plaf.metal.*;
import javax.swing.plaf.basic.*;


public class JIntelligentComboBox extends JComboBox {

    private ArrayList<Object> itemBackup = new ArrayList<Object>();

    /**  Initisiert die JIntelligentComboBox */
    private void init(){

        class searchComboBoxEditor extends BasicComboBoxEditor {
            public searchComboBoxEditor(){
                super();
            }

            @Override
            public void setItem(Object anObject){
                if (anObject == null) {
                    super.setItem(anObject);
                } else {
                    Object[] o = (Object[]) anObject;
                    super.setItem(o[0]);
                }
            }

            @Override
            public Object getItem(){
                return new Object[]{super.getItem(), super.getItem(), 0};
            }
        }

        this.setEditor(new searchComboBoxEditor()); 

        this.setEditable(true); 

        class searchRenderer extends BasicComboBoxRenderer { 

            public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus){
                if (index == 0) { 
                    setText(""); 
                    this.setPreferredSize(new Dimension(1, 1)); 
                    return this; 
                } 

                this.setPreferredSize(new Dimension(160, 17)); 

                if (index == list.getModel().getSize() - 1) { 
                    this.setBorder(new EmptyBorder(0, 3, 1, 3)); 
                } else { 
                    this.setBorder(new EmptyBorder(0, 3, 0, 3)); 
                }

                Object[] v = (Object[]) value; 
                //System.out.println(v[0]);
                this.setFont(new Font("Arial", Font.PLAIN, 12));
                this.setBackground(Color.white);

                String s = (String) v[0];
                String lowerS = s.toLowerCase();
                String sf = (String) v[1];
                String lowerSf = sf.toLowerCase();
                ArrayList<String> notMatching = new ArrayList<String>();

                if (!sf.equals("")){
                    int fs = -1;
                    int lastFs = 0;
                    while ((fs = lowerS.indexOf((String) lowerSf, (lastFs == 0) ? -1 : lastFs)) > -1) {
                        notMatching.add(s.substring(lastFs, fs));
                        lastFs = fs + sf.length();
                        //System.out.println(fs+sf.length());
                    }
                    notMatching.add(s.substring(lastFs));
                    //System.out.println(notMatching);
                }

                String html = "";

                if (notMatching.size() > 1) {
                    html = notMatching.get(0);
                    int start = html.length();
                    int sfl = sf.length();
                    for (int i = 1; i < notMatching.size(); i++) {
                        String t = notMatching.get(i);
                        html += "<b style=\"color: black;\">" + s.substring(start, start + sfl) + "</b>" + t;
                        start += sfl + t.length();
                    }
                }

                this.setText("<html><head></head><body style=\"color: gray;\">" + html + "</body></head>");
                return this;
            }

        }

        this.setRenderer(new searchRenderer());

        //
        int size = this.getModel().getSize();
        Object[] tmp = new Object[this.getModel().getSize()];

        for (int i = 0; i < size; i++) {
            tmp[i] = this.getModel().getElementAt(i);
            itemBackup.add(tmp[i]);
        }

        this.removeAllItems();

        this.getModel().addElement(new Object[]{"", "", 0});
        for (int i = 0; i < tmp.length; i++) {
            this.getModel().addElement(tmp[i]);
        }

        //
        this.getEditor().getEditorComponent().addKeyListener(new KeyListener() { 

            @Override 
            public void keyPressed(KeyEvent e) { 
                // TODO Auto-generated method stub 
            } 

            @Override 
            public void keyReleased(KeyEvent e) { 
                // TODO Auto-generated method stub 
                searchAndListEntries(((JTextField)JIntelligentComboBox.this.getEditor().getEditorComponent()).getText()); 
                //System.out.println(((JTextField)JIntelligentComboBox.this.getEditor().getEditorComponent()).getText()); 
            } 

            @Override 
            public void keyTyped(KeyEvent e) {
                // TODO Auto-generated method stub 
            }
        });
    }

    public JIntelligentComboBox(){ 
        super(); 
    } 

    public JIntelligentComboBox(MutableComboBoxModel aModel){
        super(aModel); 
        init(); 
    } 

    public JIntelligentComboBox(Object[] items){
        super(items);
        init();
    }

    public JIntelligentComboBox(Vector<?> items){
        super(items);
        init();
    }

    @Override
    public MutableComboBoxModel getModel(){
        return (MutableComboBoxModel) super.getModel();
    }

    private void searchAndListEntries(Object searchFor){
        ArrayList<Object> found = new ArrayList<Object>();

        //System.out.println("sf: "+searchFor);

        for (int i = 0; i < this.itemBackup.size(); i++) {
            Object tmp = this.itemBackup.get(i);
            if (tmp == null || searchFor == null) continue;

            Object[] o = (Object[]) tmp;
            String s = (String) o[0];
            if (s.matches("(?i).*" + searchFor + ".*")){
                found.add(new Object[]{((Object[])tmp)[0], searchFor, ((Object[])tmp)[2]});
            }
        }

        this.removeAllItems();

        this.getModel().addElement(new Object[] {searchFor, searchFor, 0});

        for (int i = 0; i < found.size(); i++) {
            this.getModel().addElement(found.get(i));
        }

        //this.setPopupVisible(true);
        int size = this.getModel().getSize() - 1;

        System.out.println("Elements: " + size);

        if (size == 0)
        {
            this.setPopupVisible( false );
            return;
        }

        this.setPopupVisible(true);

        BasicComboPopup popup =
            (BasicComboPopup)this.getAccessibleContext().getAccessibleChild(0);
        Window popupWindow = SwingUtilities.windowForComponent(popup);
        Window comboWindow = SwingUtilities.windowForComponent(this);

        if (comboWindow.equals(popupWindow))
        {
            Component c = popup.getParent();
            Dimension d = c.getPreferredSize();
            c.setSize(d);
        }
        else
        {
            popupWindow.pack();
        }
    }
}

一个问题是,当组合框字段为空时,模型包含 4 个条目。我猜这是您的匹配逻辑的问题。

于 2011-09-30T04:35:54.160 回答
3

没有测试你的代码,

请在此处为 Renderer 的内容和此处的 AutoComplete JComboBox提供建议

于 2011-09-29T22:36:19.180 回答
1

在扩展 AbstractListModel 并实现 MutableComboBoxModel 的 ComboboxModel 中使用 Vector 数组时遇到了同样的问题。使用 setMaximumRowCount 求解:

  1. 遍历数据库值并将它们保存到 public ArrayList listInCombobox = new ArrayList();

  2. myComboBox.setMaximumRowCount(listInCombobox.size());

  3. 在 myComboBox MouseListener (mousePressed) 中执行上述操作。

于 2012-01-11T12:55:57.687 回答
-3

你试过这个吗?

http://download.oracle.com/javase/6/docs/api/javax/swing/JComboBox.html#updateUI()

于 2011-09-29T22:30:21.057 回答