import javax.swing.ComboBoxModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.PlainDocument;public class FilterJComboBox extends JComboBox{

private FilterJComboBox self;
// private UpperCaseDocument upperCaseDocument;
private MyDocumentListener documentListener;
private JTextField jtf;
private String[] listItems ;
 
public static void main(String...args){
JFrame frame = new JFrame("FilterJComboBoxTest");
FilterJComboBox filterComboBox = new FilterJComboBox();
frame.getContentPane().add(filterComboBox);
frame.setVisible(true);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

// BasicComboBoxEditor b; 
}

public FilterJComboBox(){
super();
self = this;
this.setEditable(true);
this.setModel(new FilterModel());
jtf = (JTextField)this.getEditor().getEditorComponent();
// upperCaseDocument = new UpperCaseDocument(jtf);
// upperCaseDocument.SetOnlyFirstLetterCapitalize(true);
documentListener = new MyDocumentListener();

listItems = new String[]{"A","AB","AC","B","BC","BD","C","CD","CE"};
for(int i=0;i<listItems.length;i++){
this.addItem(listItems[i]);
}
// jtf.setDocument(upperCaseDocument);
jtf.setDocument(new PlainDocument());
jtf.getDocument().addDocumentListener(documentListener);
}

class FilterModel extends DefaultComboBoxModel{

public FilterModel(){
super();
}

public void refilter(){
String term = ((JTextField)self.getEditor().getEditorComponent()).getText();
DefaultComboBoxModel comboBoxModel = (DefaultComboBoxModel) self.getModel();
for(int i=0;i<comboBoxModel.getSize();i++){
if(!comboBoxModel.getElementAt(i).toString().startsWith(term)){
comboBoxModel.removeElement(comboBoxModel.getElementAt(i));
}
}
/*for(int i=0;i<listItems.length;i++){
if(!listItems[i].startsWith(term)){
self.removeItem(listItems[i]);
}    
}*/
fireContentsChanged(this,0,getSize());
}
}

//
class MyDocumentListener implements DocumentListener{ @Override
public void changedUpdate(DocumentEvent e) {
((FilterModel)self.getModel()).refilter();
} @Override
public void insertUpdate(DocumentEvent e) {
((FilterModel)self.getModel()).refilter();
} @Override
public void removeUpdate(DocumentEvent e) {
((FilterModel)self.getModel()).refilter();
}

/*private void printModelData(){
ComboBoxModel comboBoxModel = self.getModel();
for(int i=0;i<comboBoxModel.getSize();i++){
System.out.println(comboBoxModel.getElementAt(i));
}
}*/
}
}在調用refilter的時候會報錯,Attempt to mutate in notification。請問怎麼解決?
我想實現的功能就是:當用戶鍵入首字母時,從model中過濾出符合條件的數據。
不要用KeyListener,那樣效率太低了

解决方案 »

  1.   

    出错的位置在AbsractDocument,也就是所有document的基类中,一个writeLock方法。读一下注释和代码:
    原因在于在处理修改的事件中,你又要再次修改,从而导致再次触发修改。无论直接还是间接,这都是不允许的,一次也不允许。
    至于你的想法,你得说的详细点。我敲键盘,打了一个a,这个时候你的编辑区域,是出现什么?选中的那个吗?如果是选中的,那么必须修改编辑区域,这就导致再次修改。即使你调用的是jcombobox的方法,但只要选中项改变(你的remove。insert操作都会触发这个操作),就会使得编辑区域的值再次改变。尽管也许你经过有限的步数之后会终止,不会无限的循环操作下去,但是,如之前所言。不允许。
    所以,请调整你的思路,明白事件的限制,明白那些可做,那些不行,再进行设计,不然,一些东西,很可能无法实现,即使,勉强实现了,也有许多的漏洞。倒不如,从一开始就寻求一种可以接受的替代方法。一家之言,仅供参考交流。 protected synchronized final void writeLock() {
    try {
        while ((numReaders > 0) || (currWriter != null)) {
    if (Thread.currentThread() == currWriter) {
                        if (notifyingListeners) {
                            // Assuming one doesn't do something wrong in a
                            // subclass this should only happen if a
                            // DocumentListener tries to mutate the document.
                            throw new IllegalStateException(
                                          "Attempt to mutate in notification");
                        }
                        numWriters++;
                        return;
                    }
    wait();
        }
        currWriter = Thread.currentThread();
                numWriters = 1;
    } catch (InterruptedException e) {
        throw new Error("Interrupted attempt to aquire write lock");
    }
        }
      

  2.   

    to gentalguo:
    感謝你的解答。我想實現的功能是:
    假設原先有如下數據:A AB AC AE BD ,當用戶輸入A時,在下拉框中只顯示以A開頭的數據。 之所以這樣寫,是因為我參照《swing hacks》這本書上的程序實例,清華譯版第71頁的過濾Jlist,作者用的是JTextField+JList來實現。因此我想在JComboBox上也實現類似的功能,可是報錯了。不想用KeyListener事件,因為KeyListener不能捕獲粘貼事件。請問我的這個需求怎麼設計好呢? 給個思路就可以了。多謝!
      

  3.   

    哦,呵呵,说实话,我想到的也是这样的组合。
    其实jcombobox里面的事件是很复杂的。说实话,我是没弄很明白。
    像一般类似combobox的弹出树,弹出时间控件等等,都是这样模拟的,