javaswingcastingjcomboboxdefaulttablemodel

How to setSelectedItem OR setSelectedIndex to DefaultComboBoxModel from table object


Class ComboItem

public class ComboItems {

    private Long key;
    private String value;

    public ComboItems(Long key, String value) {
        this.key = key;
        this.value = value;
    }

    @Override
    public String toString() {
        return value;
    }

    public Long getKey() {
        return key;
    }

    public String getValue() {
        return value;
    }
}

Set Class ComboItem to ComboBox

private void loadSupplier(FormBuy formBuy) {
        List<Supplier> suppliers = supplierJdbc.selectSuppliers("%%");
        DefaultComboBoxModel defaultComboBoxModel = new DefaultComboBoxModel();
        suppliers.forEach(supplier -> {
            defaultComboBoxModel.addElement(new ComboItems(supplier.getId(), supplier.getName()));
        });
        formBuy.getjComboBoxSupplier().setModel(defaultComboBoxModel);
    }

Show to DefautTableModel

    private void loadTable(DefaultTableModel defaultTableModel, FormBuy formBuy) {
            defaultTableModel.getDataVector().removeAllElements();
            defaultTableModel.fireTableDataChanged();
            

List<ResponseListTableBuy> buys = buyJdbc.selectBuys("%" + formBuy.getjTextFieldSearch().getText() + "%");
        Object[] objects = new Object[10];
        for (ResponseListTableBuy buy : buys) {
            objects[0] = buy.getId();
            objects[1] = buy.getSupplier().getName();
            objects[2] = buy.getCategory().getName();
            objects[3] = buy.getItem().getName();
            objects[4] = buy.getUnit().getName();
            objects[5] = buy.getCountItem();
            objects[6] = buy.getBuyPrice();
            objects[7] = new BigDecimal(buy.getCountItem() * buy.getBuyPrice().intValue());
            objects[8] = buy.getSellPrice();
            objects[9] = buy.getDate();
            defaultTableModel.addRow(objects);
        }
}

so far it's still fine!. I got an error when I put this code

get code from table to combobox

formBuy.getjComboBoxSupplier().getModel().setSelectedItem(defaultTableModel.getValueAt(formBuy.getjTableBuy().getSelectedRow(), 1).toString());

and put this an error

System.out.println(((ComboItems) formBuy.getjComboBoxSupplier().getModel().getSelectedItem()).getKey());

Exception in thread "AWT-EventQueue-0" java.lang.ClassCastException: java.lang.String cannot be cast to aliimron.combobox.ComboItems
    at aliimron.controller.ControllerBuy$1.mouseClicked(ControllerBuy.java:97)
    at java.awt.AWTEventMulticaster.mouseClicked(AWTEventMulticaster.java:270)
    at java.awt.Component.processMouseEvent(Component.java:6542)
    at javax.swing.JComponent.processMouseEvent(JComponent.java:3324)
    at java.awt.Component.processEvent(Component.java:6304)
    at java.awt.Container.processEvent(Container.java:2239)
    at java.awt.Component.dispatchEventImpl(Component.java:4889)
    at java.awt.Container.dispatchEventImpl(Container.java:2297)
    at java.awt.Component.dispatchEvent(Component.java:4711)
    at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4904)
    at java.awt.LightweightDispatcher.processMouseEvent(Container.java:4544)
    at java.awt.LightweightDispatcher.dispatchEvent(Container.java:4476)
    at java.awt.Container.dispatchEventImpl(Container.java:2283)
    at java.awt.Window.dispatchEventImpl(Window.java:2746)
    at java.awt.Component.dispatchEvent(Component.java:4711)
    at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:760)
    at java.awt.EventQueue.access$500(EventQueue.java:97)
    at java.awt.EventQueue$3.run(EventQueue.java:709)
    at java.awt.EventQueue$3.run(EventQueue.java:703)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:74)
    at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:84)
    at java.awt.EventQueue$4.run(EventQueue.java:733)
    at java.awt.EventQueue$4.run(EventQueue.java:731)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:74)
    at java.awt.EventQueue.dispatchEvent(EventQueue.java:730)
    at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:205)
    at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116)
    at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105)
    at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
    at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93)
    at java.awt.EventDispatchThread.run(EventDispatchThread.java:82)

so the flow is from comboitem to combobox to table and back from table to combobox. how do you get it from the table back into the combobox


Solution

  • So, you build your model using ...

    defaultComboBoxModel.addElement(new ComboItems(supplier.getId(), supplier.getName()));
    

    This is good, you're encapsulating the information into an object, but, then you use

    formBuy.getjComboBoxSupplier().getModel().setSelectedItem(defaultTableModel.getValueAt(formBuy.getjTableBuy().getSelectedRow(), 1).toString());
    

    Wait, that's a lot, lets see if we can clean it up a bit...

    formBuy
        .getjComboBoxSupplier()
        .getModel()
        .setSelectedItem(
            defaultTableModel.getValueAt(
                formBuy.getjTableBuy().getSelectedRow(), 1
            ).toString()
        );
    

    So, basically, you're trying to use a String to set the value of the model, but the model is expecting an instance of ComboItems ... 🤨

    So, you have a couple of choices...

    #1. You could...

    Supply the TableModel with a ComboItems and then use a custom cell renderer to render it, then you could use something like...

    formBuy
        .getjComboBoxSupplier()
        .getModel()
        .setSelectedItem(
            (ComboItems)defaultTableModel.getValueAt(
                formBuy.getjTableBuy().getSelectedRow(), 1
            )
        );
    

    to set the selected combo box item.

    For my money, this is the more "correct" approach, see

    for more details

    #2. You could...

    Look up the item in the model directly, based on the String from the table.

    Something maybe like this...

    String value = (String)defaultTableModel.getValueAt(formBuy.getjTableBuy().getSelectedRow(), 1);
    
    DefaultComboBoxModel model = formBuy.getjComboBoxSupplier().getModel()
    for (int index = 0; index < model.getSize(); index++) {
        ComboItems items = (ComboItems) model.getElementAt(index);
        if (items.getValue().equalsIgnoreCase(value)) {
            formBuy
                .getjComboBoxSupplier()
                .getModel()
                .setSelectedItem(items);
            break;
        }
    }
    

    But to be honest, you're better off with option #1

    Extensions...

    One of things I might be focusing on, is trying to tie your data together, in a more meaningful way, so you don't need to do these "side lookups", for example, your data seems to be expressible as a key/value pair, lets start there...

    public interface KeyValueExpressible {
        public String getKey();
        public String getValue();
    }
    
    public abstract class AbstractKeyValue implements KeyValueExpressible {
        private String key;
        private String value;
    
        public AbstractKeyValue(String key, String value) {
            this.key = key;
            this.value = value;
        }
    
        public String getKey() {
            return key;
        }
    
        public String getValue() {
            return value;
        }
        
    }
    

    Okay, but how's that going to help you? Well, now you can narrow the constraints to actual data types, for example...

    public class Supplier extends AbstractKeyValue {
    
        public Supplier(String key, String value) {
            super(key, value);
        }
        
    }
    

    The neat thing is, this is still usable anywhere you need KeyValueExpressible, but can provide a finer level constraint when you need the Supplier, sweet

    Now you could construct you combobox using something like...

    List<Supplier> suppliers = supplierJdbc.selectSuppliers("%%");
    DefaultComboBoxModel<KeyValueExpressible> defaultComboBoxModel = new DefaultComboBoxModel<>();
    suppliers.forEach(supplier -> {
        defaultComboBoxModel.addElement(supplier);
    });
    formBuy.getjComboBoxSupplier().setModel(defaultComboBoxModel);
    

    nb: DefaultComboBoxModel<KeyValueExpressible> could also be DefaultComboBoxModel<Supplier>, but DefaultComboBoxModel<KeyValueExpressible> allows us to do cool magic like...

    public class KeyValueExpressibleListCellRenderer extends DefaultListCellRenderer {
        public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
            super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
            if (value instanceof KeyValueExpressible) {
                KeyValueExpressible keyValue = (KeyValueExpressible) value;
                setText(keyValue.getValue());
            }
            return this;
        }
    }
    // Why doesn't DefaultListCellRenderer support generics 😭
    

    which can then be applied using something like...

    JComboBox<KeyValueExpressible> comboBox = new JComboBox<>(defaultComboBoxModel);
    comboBox.setRenderer(new KeyValueExpressibleListCellRenderer());
    

    Which is nice a reusable, sweet.

    Now, we can start extending the concept, for example, assuming we have something like...

    public interface ResponseListTableBuy {
        public String getId();
        public Supplier getSupplier();
        //...
    }
    

    Then we can encapsulate the TableModel, for example...

    public class BuyTableModel extends AbstractTableModel {
        
        private List<ResponseListTableBuy> buys;
        
        private String[] columnNames = new String[]{
            "id",
            "Supplier",
            "Category",
            "Item",
            "Unit",
            "Count",
            "Buy price",
            "Total",
            "Sell price",
            "Date"
        };
    
        public BuyTableModel(List<ResponseListTableBuy> buys) {
            this.buys = buys;
        }
    
        @Override
        public int getRowCount() {
            return buys.size();
        }
    
        @Override
        public int getColumnCount() {
            return columnNames.length;
        }
    
        @Override
        public String getColumnName(int column) {
            return columnNames[column];
        }
    
        @Override
        public Class<?> getColumnClass(int columnIndex) {
            switch (columnIndex) {
                case 0: return String.class;
                case 1: return KeyValueExpressible.class;
                case 2: return KeyValueExpressible.class;
                case 3: return KeyValueExpressible.class;
                case 4: return KeyValueExpressible.class;
                case 5: return Integer.class;
                case 6: return Double.class;
                case 7: return BigDecimal.class;
                case 8: return Double.class;
                case 9: return LocalDate.class;
            }
            return String.class;
        }
    
        @Override
        public Object getValueAt(int rowIndex, int columnIndex) {
            ResponseListTableBuy buy = buys.get(rowIndex);
            switch (columnIndex) {
                case 0: return buy.getId();
                case 1: return buy.getSupplier();
                case 2: return buy.getCategory();
                case 3: return buy.getItem();
                case 4: return buy.getUnit();
                case 5: return buy.getCountItem();
                case 6: return buy.getBuyPrice();
                // This should be a computed porperty, just saying...
                case 7: return new BigDecimal(buy.getCountItem() * buy.getBuyPrice().intValue());;
                case 8: return buy.getSellPrice();
                case 9: return buy.getDate();
            }
        }
        
    }
    

    And then....

    public class KeyValueExpressibleTableCellRenderer extends DefaultTableCellRenderer {
    
        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
            super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
            if (value instanceof KeyValueExpressible) {
                KeyValueExpressible keyValue = (KeyValueExpressible) value;
                setText(keyValue.getValue());
            }
            return this;
        }
        
    }
    

    more magic 🪄!

    You apply it using something like...

    JTable table = new JTable();
    table.setDefaultRenderer(KeyValueExpressible.class, new KeyValueExpressibleTableCellRenderer());
    

    And suddenly, every column you said was a KeyValueExpressible.class will be rendered through this renderer! Sweet!

    We've encapsulated the data in the model; we've got the view to render the data the way want it to and when needed...we can do...

    formBuy
        .getjComboBoxSupplier()
        .getModel()
        .setSelectedItem(
            (KeyValueExpressible)defaultTableModel.getValueAt(
                formBuy.getjTableBuy().getSelectedRow(), 1
            )
        );
    

    nb: You could make the BuyTableModel do the casting for you, but I think I might have already blown your mind 🤯

    A neat little trick...

    You might find yourself in a situation where you have two instances KeyValueExpressible representing the same data (you could use a common factory to manage it, but that's another topic).

    In those cases, you need some way to tell that those two instances are the same, this is where equals and hashCode come in, for example...

    public abstract class AbstractKeyValue implements KeyValueExpressible {
    
        private String key;
        private String value;
    
        public AbstractKeyValue(String key, String value) {
            this.key = key;
            this.value = value;
        }
    
        public String getKey() {
            return key;
        }
    
        public String getValue() {
            return value;
        }
    
        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }
            if (obj instanceof KeyValueExpressible) {
                return false;
            }
            KeyValueExpressible keyValue = (KeyValueExpressible) obj;
            return keyValue.getKey().equals(getKey()) && keyValue.getValue().equals(getValue());
        }
    
        @Override
        public int hashCode() {
            int hash = 7;
            hash = 29 * hash + Objects.hashCode(this.key);
            hash = 29 * hash + Objects.hashCode(this.value);
            return hash;
        }
        
    }
    

    Now, any two instances with the same key and value will be equal, sweet!

    Now, if you prefer to tighten the constraint a little more, you could also do something like...

    public class Supplier extends AbstractKeyValue {
    
        public Supplier(String key, String value) {
            super(key, value);
        }
    
        @Override
        public boolean equals(Object obj) {
            if (obj instanceof Supplier) {
                return false;
            }
            return super.equals(obj);
        }
    
    }
    

    Now only two Suppliers with the same key/value will be equal, sweet!