Search code examples
javaswingjtablerenderer

Using JComboBox with custom renderer as JTable cell renderer/editor (architecture)


I'm new to GUI design and I'm trying to plan this out before I go too far the wrong way, any help would be nice. I'm trying to display a JTable with rows of Employee, which itself has datatypes of String and ArrayList<Cert>. Cert contains a String.

I'd like to have the table present the data for editing, but for a few of the columns I'd like to implement a JComboBox for selection of a String from a set of valid strings, as well as color each option differently (different background colors in the JComboBox).

Also, the ArrayList<Cert> currently displays in a cell as [xxx, xxx, ...] where XXX is the return from the toString() function for each item in the ArrayList. I think I'd like to display that ArrayList<Cert> as a read-only JComboBox, but I'm not as concerned with this item.

I'm questioning how many classes I need to create to make this happen. I already have a custom model for the JTable extending AbstractTableModel. Do I need to write an extension of JComboBox or do I just need to extend the appropriate renderer for a JComboBox as a cell and do the magic there, then assign that custom renderer to the cell renderer for the String cell?

Here's what I have so far, lightly abridged:

public class EmployeeTableModel extends AbstractTableModel {
  ...
  private ArrayList<Employee> myDataObjects = new ArrayList<Employee>();
  ...
  @Override
  public Object getValueAt(int row, int column) {
      Employee emp = myDataObjects.get(row);

      switch (column) {
          case 0:
              return emp.getName();
          case 1:
              return emp.getShift();
          case 2:
              return emp.getCertifications();
          default:
              return "";
      }
   }
}

Employees:

public class Employee {
  private String name;
  private String shift;
  private ArrayList<Cert> certs;
  ...
  public String getName() {
    return name;
  }

  public String getShift() {
    return shift;
  }

  public ArrayList<Cert> getCerts() {
    return certs;
  }
  ...
}    

And the initializations:

EmployeeTableModel etm = new EmployeeTableModel();
JTable employeeTable = new JTable();
employeeTable.setModel( etm );

Solution

  • you can to start with, simplest code as is possible, maybe depends if you want to see JComboBox as Renderer too by @aterai

    import java.awt.BorderLayout;
    import javax.swing.DefaultCellEditor;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.JScrollPane;
    import javax.swing.JTable;
    import javax.swing.table.AbstractTableModel;
    import javax.swing.table.DefaultTableCellRenderer;
    import javax.swing.table.TableCellRenderer;
    import javax.swing.table.TableColumn;
    import java.awt.Component;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.util.ArrayList;
    import javax.swing.JButton;
    import javax.swing.JComboBox;
    import javax.swing.JList;
    import javax.swing.plaf.basic.BasicComboBoxRenderer;
    
    public class TableRenderDemo extends JPanel {
    
        private static final long serialVersionUID = 1L;
    
        public TableRenderDemo() {
            super(new BorderLayout(5, 5));
            final JTable table = new JTable(new MyTableModel());
            table.setPreferredScrollableViewportSize(table.getPreferredSize());
            table.setFillsViewportHeight(true);
            table.setRowHeight(20);
            JScrollPane scrollPane = new JScrollPane(table);
            initColumnSizes(table);
            setUpSportColumn(table, table.getColumnModel().getColumn(2));
            add(scrollPane, BorderLayout.CENTER);
            JButton resetButton = new JButton("Reset to default");
            resetButton.addActionListener(new ActionListener() {
    
                public void actionPerformed(ActionEvent e) {
                    for (int i = 0; i < table.getRowCount(); i++) {
                        table.getModel().setValueAt("None of the above", i, 2);
                    }
                }
            });
            add(resetButton, BorderLayout.SOUTH);
        }
    
        private void initColumnSizes(JTable table) {
            MyTableModel model = (MyTableModel) table.getModel();
            TableColumn column = null;
            Component comp = null;
            int headerWidth = 0;
            int cellWidth = 0;
            Object[] longValues = model.longValues;
            TableCellRenderer headerRenderer = table.getTableHeader().getDefaultRenderer();
            for (int i = 0; i < 5; i++) {
                column = table.getColumnModel().getColumn(i);
                comp = headerRenderer.getTableCellRendererComponent(null, column.getHeaderValue(), false, false, 0, 0);
                headerWidth = comp.getPreferredSize().width;
                comp = table.getDefaultRenderer(model.getColumnClass(i)).getTableCellRendererComponent(table, longValues[i], false, false, 0, i);
                cellWidth = comp.getPreferredSize().width;
                column.setPreferredWidth(Math.max(headerWidth, cellWidth));
            }
        }
    
        private void setUpSportColumn(JTable table, TableColumn sportColumn) {
            ArrayList<String> listSomeString = new ArrayList<String>();
            listSomeString.add("Snowboarding");
            listSomeString.add("Rowing");
            listSomeString.add("Knitting");
            listSomeString.add("Speed reading");
            listSomeString.add("Pool");
            listSomeString.add("None of the above");
            JComboBox comboBox = new JComboBox();
            comboBox.addItem(new Item(1, "-"));
            comboBox.addItem(new Item(2, "Snowboarding"));
            comboBox.addItem(new Item(3, "Rowing"));
            comboBox.addItem(new Item(4, "Knitting"));
            comboBox.addItem(new Item(5, "Speed reading"));
            comboBox.addItem(new Item(6, "Pool"));
            comboBox.addItem(new Item(7, "None of the above"));
            comboBox.setMaximumRowCount(3);
            comboBox.addActionListener(new ActionListener() {
    
                public void actionPerformed(ActionEvent e) {
                    JComboBox comboBox = (JComboBox) e.getSource();
                    Item item = (Item) comboBox.getSelectedItem();
                    System.out.println(item.getId() + " : " + item.getDescription());
                }
            });
            comboBox.setRenderer(new ItemRenderer());
            sportColumn.setCellEditor(new DefaultCellEditor(comboBox));
            DefaultTableCellRenderer renderer = new DefaultTableCellRenderer();
            renderer.setToolTipText("Click for combo box");
            sportColumn.setCellRenderer(renderer);
        }
    
        class ItemRenderer extends BasicComboBoxRenderer {
    
            @Override
            public Component getListCellRendererComponent(JList list, Object value,
                    int index, boolean isSelected, boolean cellHasFocus) {
                super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
                if (value != null) {
                    Item item = (Item) value;
                    setText(item.getDescription().toUpperCase());
                }
                if (index == -1) {
                    Item item = (Item) value;
                    setText("" + item.getId());
                }
                return this;
            }
        }
    
        class Item {
    
            private int id;
            private String description;
    
            public Item(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;
            }
        }
    
        class MyTableModel extends AbstractTableModel {
    
            private static final long serialVersionUID = 1L;
            private String[] columnNames = {"First Name", "Last Name", "Sport", "# of Years", "Vegetarian"};
            private Object[][] data = {{"Kathy", "Smith", "Snowboarding", new Integer(5), false},
                {"John", "Doe", "Rowing", new Integer(3), true}, {"Sue", "Black", "Knitting", new Integer(2), false},
                {"Jane", "White", "Speed reading", new Integer(20), true}, {"Joe", "Brown", "Pool", new Integer(10), false}};
            public final Object[] longValues = {"Jane", "Kathy", "None of the above", new Integer(20), Boolean.TRUE};
    
            @Override
            public int getColumnCount() {
                return columnNames.length;
            }
    
            @Override
            public int getRowCount() {
                return data.length;
            }
    
            @Override
            public String getColumnName(int col) {
                return columnNames[col];
            }
    
            @Override
            public Object getValueAt(int row, int col) {
                return data[row][col];
            }
    
            @Override
            public Class<?> getColumnClass(int c) {
                return getValueAt(0, c).getClass();
            }
    
            @Override
            public boolean isCellEditable(int row, int col) {
                if (col < 2) {
                    return false;
                } else {
                    return true;
                }
            }
    
            @Override
            public void setValueAt(Object value, int row, int col) {
                data[row][col] = value;
                fireTableCellUpdated(row, col);
                System.out.println("New value of data: " + getValueAt(row, col));
            }
        }
    
        private static void createAndShowGUI() {
            JFrame frame = new JFrame("TableRenderDemo");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            TableRenderDemo newContentPane = new TableRenderDemo();
            frame.setContentPane(newContentPane);
            frame.pack();
            frame.setVisible(true);
        }
    
        public static void main(String[] args) {
            javax.swing.SwingUtilities.invokeLater(new Runnable() {
    
                @Override
                public void run() {
                    createAndShowGUI();
                }
            });
        }
    }