javaswingjcomboboxuirefreshcontrolcomboboxmodel

Swing MVC - refresh the content of JComboBox while already visible


I set the model of my combobox in my controller class

cboCategory.setModel(new ModernDefaultComboBoxModel(model.getProductCategories()));

productCategories is a List of String. ModernDefaultComboBoxModel is just model that extends DefaultComboBoxModel.

public class ModernDefaultComboBoxModel extends DefaultComboBoxModel{
    public ModernDefaultComboBoxModel(List<String> elements){
        super(elements.toArray());
    }
}

Now in my model, productCategories is populated from the DB, in a SwingWorker

SwingWorker<Void, String> worker = new SwingWorker<Void, String>() {
    @Override
    protected Void doInBackground() throws Exception {
        //query and resultset stuff
        while (rs.next()) {
            publish(rs.getString(1));
        }
        //cleanup stuff
    }
    @Override protected void process(List<String> chunks){
        List<String> oldCategories = new ArrayList<String>(productCategories);
        for(String cat : chunks){
            productCategories.add(cat);
        }
        fireModelPropertyChange(PRODUCT_CATEGORIES, oldCategories, productCategories);
    }
    @Override
    protected void done(){
        //some stuff
    }
};
worker.execute();

You see every publish, it fires a property change event to its listener (fireModelPropertyChange is just a wrapper for firePropertyChange).

Now in my model listener,

@Override
    public void propertyChange(PropertyChangeEvent evt) {
        String propName = evt.getPropertyName();

        //some branching for the other models

        else if(ProductModel.PRODUCT_CATEGORIES.equals(propName)){
            List<String> newVal = (List<String>)evt.getNewValue();

            //notify the model of the combobox that the data is changed, so refresh urself
        }

        //some stuff
    }

I'm stuck in the part where my ModelListener needs to notify the combobox in the view that data in its model are changed. I have the same situation with JTable but with JTable I can just call fireTableRowsInserted of from its model which is implemented from AbstractTableModel.

Actually, in the AbstractListModel there is a method fireContentsChanged but unlike in the JTable, this method is protected so I can't access it.

I know I can just create an instance of ModernDefaultComboBoxModel then call the setModel method of the combobox to refresh the combobox, but I'm just wondering if there is a "cleaner" way as clean as that of JTable


Solution

  • JComboBox implements ListDataListener in order to listen to its own ComboBoxModel. Any change to your DefaultComboBoxModel should invoke the relevant fireXxxx() method in AbstractListModel, and the JComboBox should see the change. Just update the combo's model in process().

    Addendum: Here's a minimal example that updates the model. Set a breakpoint on model.addElement(), debug, click on Add, and step into the method to see the call to fireIntervalAdded(), which subsequently updates the view.

    JFrame f = new JFrame("ComboWorkerTest");
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    f.setLayout(new GridLayout(0, 1));
    final JComboBox jcb = new JComboBox(new Integer[]{value});
    f.add(new JButton(new AbstractAction("Add") {
        @Override
        public void actionPerformed(ActionEvent e) {
            DefaultComboBoxModel model = (DefaultComboBoxModel) jcb.getModel();
            model.addElement(++value);
        }
    }));
    f.add(jcb);
    f.pack();
    f.setLocationRelativeTo(null);
    f.setVisible(true);