i am programming a custom component which extends a JComboBox. My problem is, the PopupMenu won't actualise its size if i am adding or removing an item. So there are e.g. 2 items in the list, but if there were 4 before i had 2 "empty" items in the PopupMenu as well.
The only workaround i found was to do (in JIntelligentComboBox.java line 213)
this.setPopupVisible(false);
this.setPopupVisible(true);
but the result will be a flickering PopupMenu :-(
So what else could i do to refresh/repaint the PopupMenu without flickering?
For testing: the component and a little test programm
To generate my problem you could e.g.:
- type "e"
- press "return"
- type "m"
Thanks in advance
Edit: My goal is a ComboBox that acts like e.g. the adressbar in Firefox or Chrome, i want to show all items of the PopupMenu that contain the typed chars.
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);
}
}
Did you tried this?
http://download.oracle.com/javase/6/docs/api/javax/swing/JComboBox.html#updateUI()
I have revised your sscce below, and I noticed a few things:
The anomaly you observe is not apparent when using
apple.laf.AquaComboBoxUI
. In particular, entering and deleting text grows and shrinks the list as expected. You might try the revised code on your platform.I switched from
KeyListener
toKeyAdapter
for expedience, but that's not a solution. You should probably use aDocumentListener
. It can't be mutated while in use, as you are doing now, so I didn't pursue this further.Always build the GUI on the event dispatch thread.
Hard-coded dimensions and novel fonts rarely look right on other look & feel implementations. I simply removed yours to get the appearance shown.
Your constructor modifies the model after constructing the parent, so the result depends on the order of instantiation. A separate model might be easier to manage.
Update: Added code to verify @camickr's solution.
Faced the same problem using Vector array in ComboboxModel that extends AbstractListModel & implements MutableComboBoxModel. Solved using setMaximumRowCount:
iterate over database values and save them into public ArrayList listInCombobox = new ArrayList();
myComboBox.setMaximumRowCount(listInCombobox.size());
do the above inside myComboBox MouseListener (mousePressed).
The basis of the solution below is to resize the popup every time you invoke the
searchAndListRoutine
. You need to take into account that the popup can be displayed in its own Window when the popup displays outside the bounds of the parent frame or it can be displayed on the layered pane of the parent frame:The one issue is that when the combo box field is empty the model contains 4 entries. I would guess that is a problem with your matching logic.
not tested your code,
please advice for content of Renderer here and for AutoComplete JComboBox here