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.:
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);
}
}
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
to KeyAdapter
for expedience, but that's not a solution. You should probably use a DocumentListener
. 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.
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};
}
}
}
}
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:
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();
}
}
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With