javaswingjcombobox

Repopulate a JComboBox model while maintaining current selection


I am new to Java and Java Swing but it is what I inherited. I am trying to figure out the best way to keep a combobox up to date with a changing list. If the user has already selected an item and that item is still in the new list I want to keep it selected. The following code seems to work but I'm looking for suggestions o make it better, cleaner, etc. especially in the "done" method of the swing worker. Currently I am dealing with the combobox and the model (I thought the model alone would be enough).

In my example the list of cars will be the same first 4 and the last 5-7 are randomly added or removed.

class CBItem {

    private int id;
    private String description;

    public CBItem(int id, String description) {
        this.id = id;
        this.description = description;
    }

    public int getId() {
        return id;
    }

    public String getDescription() {
        return description;
    }

    @Override
    public String toString() {
        return description;
    }
    
    @Override
    public boolean equals(Object o) {
        boolean equal = true;
        if (!(o instanceof CBItem)) {
            equal = false;
        }
        else {
            CBItem other = (CBItem)o;
            equal = other.description.equals(this.description);
        }
        return equal;
    }
}

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
import javax.swing.Timer;

public class JComboBoxFiller extends JFrame {

    private static final long serialVersionUID = 1L;
    JPanel pnlMain;
    DefaultComboBoxModel<CBItem> mdlCars = new DefaultComboBoxModel<CBItem>();
    List<CBItem> lstCurrentCars = new ArrayList<CBItem>();
    JComboBox<CBItem> cbxCars;

    final String[] arrCarNames = new String[] {"Audi", "BMW", "Chevrolet", "Dodge", "Ford", "Hyundai", "Jaguar"};

    public JComboBoxFiller() {

        setTitle("ComboBox Filler Demo");
        setBounds(100, 100, 400, 200);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        pnlMain = new JPanel(new BorderLayout());
        setContentPane(pnlMain);

        cbxCars = new JComboBox<CBItem>(mdlCars);

        JPanel pnlCenter = new JPanel();
        pnlCenter.add(cbxCars, BorderLayout.CENTER);

        pnlMain.add(pnlCenter, BorderLayout.CENTER);

        setVisible(true);

        // Every 5 seconds repopulate the list of cars
        Timer timer = new Timer(3000, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                new RepopulateCarsWorker().execute();

            }
        });
        timer.start();
    }

    // This will be called every 5 seconds.  It populates a list of 4 to 7 cars (CBItems)
    // then updates the combobox (cbxCars) with the new values.
    // It needs to keep the current car selected (if one was and still exists)
    private class RepopulateCarsWorker extends SwingWorker<ArrayList<CBItem>, Void>
    {
        ArrayList<CBItem> lstNewCars = new ArrayList<CBItem>();

        protected ArrayList<CBItem> doInBackground()
        {
            Random rand = new Random();
            int lastCar = 4 + rand.nextInt(3);

            for (int c = 0; c < lastCar; c++) {
                lstNewCars.add(new CBItem(c, arrCarNames[c]));
            }

            return lstNewCars;
        }

        protected void done()
        {
            try
            {
                ArrayList<CBItem> lstNewCars = get();

                // If list stayed the same, nothing to do
                if (lstNewCars.equals(lstCurrentCars)) {
                    return;
                }

                // Save the current selection so it can be reselected
                CBItem cbCurrentSelection = (CBItem) mdlCars.getSelectedItem();

                mdlCars.removeAllElements();
                mdlCars.addAll(lstNewCars);

                // Reselect if there was a selected item and it still exists
                // Question: Why do I have to deal with the combobox?
                //           I thought model would be enough...
                mdlCars.setSelectedItem(cbCurrentSelection);
                cbxCars.setSelectedIndex(mdlCars.getIndexOf(cbCurrentSelection));

                // Update current list for next comparison
                lstCurrentCars = lstNewCars;
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    JComboBoxFiller frame = new JComboBoxFiller();
                    frame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

Solution

  • Why do I have to deal with the combobox? I thought model would be enough...

    I agree, updating the model should cause the view to be updated.

    Your code is always setting the selected item which may be causing the problem.

    I modified your code to clear the selected item when it is not found in the model and you don't need to reference the combo box:

    // Save the current selection so it can be reselected
    CBItem cbCurrentSelection = (CBItem) mdlCars.getSelectedItem();
    
    // Reselect if there was a selected item and it still exists
    // Question: Why do I have to deal with the combobox?
    //           I thought model would be enough...
    mdlCars.removeAllElements();
    mdlCars.addAll(lstNewCars);
    
    if (mdlCars.getIndexOf(cbCurrentSelection) == -1)
        mdlCars.setSelectedItem(null);
    else
        mdlCars.setSelectedItem(cbCurrentSelection);
    //cbxCars.setSelectedIndex(mdlCars.getIndexOf(cbCurrentSelection));
    
    // Update current list for next comparison
    lstCurrentCars = lstNewCars;