title: How to create a dynamic combo box as an editor in a JTable
body: <p>I want to have a dialog to specify filters for data in a <code>JTable</code>.  The dialog will have a second <code>JTable</code>; one of the columns will represent the column in the 1st <code>JTable</code> to which the filter applies; one of the other columns would be the string to use for filtering.  </p>
  
  <p>I've got other columns in the 2nd <code>JTable</code> in the real app—whether the filter ensures the string is present or absent, etc.—but those aren't important to the problem.</p>
  
  <p>I created and installed a custom renderer and editor for a <code>TableColumn</code>, and registered those with the 2nd table for the <code>TableColumn</code> class; I want the string representing the column in the 2nd table to be the header from the <code>TableColumn</code>, and to be able to reference the actual <code>TableColumn</code> as the item being edited.  </p>
  
  <p>In the code I have so far, when I first display the dialog with a row of fake data in it, the header value from the column in the fake data is indeed being shown.  When I click on the value, however, the dropdown value for each column is the string you get when you execute <code>tableColumn.toString()</code>.</p>
  
  <p>My code that builds the cell editor dropdown puts <code>TableColumn</code> values in the <code>JComboBox&lt;TableColumn&gt;</code>, as I thought it would have to; how can I get the editor to use the header value from the tableColumn instead of using its toString(), or whatever it's doing?</p>
  
  <p>One more thing: in the real app, the columns in the 1st <code>JTable</code> are dynamic; I cannot simply list the columns in the code.  I derive the number of columns and their headers from the data.  Please don't just give me examples that assume that the columns to be edited are known ahead of time.  If someone can explain how it works instead of just giving examples, that'd be great, but if we have to communicate entirely by examples, please include the dynamic nature of the tables.</p>
  
  <pre><code>package lincoln.ui;
  
  import java.awt.BorderLayout;
  import java.awt.Component;
  import java.awt.FlowLayout;
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
  import java.util.ArrayList;
  import java.util.Enumeration;
  
  import javax.swing.AbstractCellEditor;
  import javax.swing.JButton;
  import javax.swing.JComboBox;
  import javax.swing.JDialog;
  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.DefaultTableModel;
  import javax.swing.table.TableCellEditor;
  import javax.swing.table.TableColumn;
  
  @SuppressWarnings("serial")
  public class ExampleColumnCombo extends JFrame
  {
    private JButton getDialogButton = null;
  
    public static void main(String ... arguments)
    {
      ExampleColumnCombo me = new ExampleColumnCombo();
      me.go(arguments);
    }
  
    private void go(String ... arguments)
    {
      createUI(this);
      setVisible(true);
    }
  
    // defines one filter test: each item defines a column to test 
    // and the value to test in it.
    class FilterItem
    {
      private TableColumn column            = null;
      private String      targetString      = null;
  
      public FilterItem  (TableColumn tc, String targetString)
      {
        column = tc;
        this.targetString = targetString;
      }
      public TableColumn getColumn()        { return column; }
      public String      getTargetString()  { return targetString; }
    }
  
    private void createUI(final ExampleColumnCombo mainFrame)
    {
      Object[][] data = { { "color", "red" }, {"shape", "square"}, {"fruit", "banana"}, {"plain", "text"}};
      String[] columnNames = {"type", "value" };
      DefaultTableModel model = new DefaultTableModel(data, columnNames);
      final JTable table = new JTable(model);
  
      getDialogButton = new JButton("filters");
      getDialogButton.addActionListener
      (
          new ActionListener()
          {
            public void actionPerformed(ActionEvent event)
            {
              FilterModel filterModel = new FilterModel();
              filterModel.addItem(new FilterItem(table.getColumnModel().getColumn(0), "color"));
              FilterDialog dialog = new FilterDialog(mainFrame, filterModel);
              dialog.setVisible(true);
            }
          }
      );
  
      JPanel upperPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
      upperPanel.add(getDialogButton);
      add(upperPanel, BorderLayout.NORTH);
  
      JScrollPane scrollPane = new JScrollPane(table);
      add(scrollPane, BorderLayout.CENTER);
  
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      setSize(300,300);
      setLocationByPlatform(true);
    }
  
    class FilterModel extends AbstractTableModel
    {
      private ArrayList&lt;FilterItem&gt; filters = new ArrayList&lt;&gt;();
  
      public int getColumnCount()                         { return 2; }
      public boolean isCellEditable(int row, int column)  { return true; }
  
      public Class&lt;?&gt; getColumnClass(int i)
      {
        switch (i)
        { 
        case 0: return TableColumn.class; 
        case 1: return String.class;
        default: System.err.println("error, bad column given for class");
        }
        return null;
      }
  
      @Override
      public int getRowCount()    {      return filters.size();    }
  
      @Override
      public Object getValueAt(int rowIndex, int columnIndex)
      {
        FilterItem item = filters.get(rowIndex);
        switch(columnIndex)
        {
        case 0: return item.getColumn();       //break;
        case 1: return item.getTargetString(); //break;
        default: System.err.println("wrong"); new Exception().printStackTrace(); break;
        }
        return null;
      } 
  
      public void addItem(FilterItem item)
      {
        filters.add(item);
      }
    }
  
    class FilterDialog extends JDialog
    {
      public FilterDialog(ExampleColumnCombo mainDisplay, 
                          final FilterModel filterModel)
      {
        JTable table = new JTable(filterModel);
  
        table.setDefaultRenderer(TableColumn.class, new TableColumnCellRenderer());
        table.setDefaultEditor(TableColumn.class, new TableColumnCellEditor());
  
        JScrollPane scrollPane = new JScrollPane(table);
        add(scrollPane);
        setSize(200,200);
        pack();
        setVisible(true);
      }
    }
  
    class TableColumnCellEditor extends AbstractCellEditor implements
        TableCellEditor, ActionListener
    {
      private TableColumn tableColumn;
      public TableColumnCellEditor() {}
  
      @Override
      public Object getCellEditorValue()
      {
        return tableColumn;
      }
  
      @Override
      public Component getTableCellEditorComponent(JTable table, Object value,
          boolean isSelected, int row, int col)
      {
        if (value instanceof TableColumn) 
        { 
          tableColumn = (TableColumn)value;
        }
  
        JComboBox&lt;TableColumn&gt; comboTableColumnType = new JComboBox&lt;&gt;();
  
        Enumeration&lt;TableColumn&gt; columnEnumeration = table.getColumnModel().getColumns();
        while (columnEnumeration.hasMoreElements())
        {
          TableColumn column = (TableColumn)columnEnumeration.nextElement();
          comboTableColumnType.addItem(column);
        }
        comboTableColumnType.setSelectedItem(value);
        comboTableColumnType.addActionListener(this);
  
        return comboTableColumnType;
      }
  
      @Override
      public void actionPerformed(ActionEvent e)
      {
        @SuppressWarnings("unchecked")
        JComboBox&lt;TableColumn&gt; comboTableColumnType = (JComboBox&lt;TableColumn&gt;) e.getSource();
        this.tableColumn = (TableColumn)comboTableColumnType.getSelectedItem();
      }
  
      @Override
      public String toString()
      {
        return this.tableColumn.getHeaderValue().toString();
      }
  
    }
  
    public class TableColumnCellRenderer extends DefaultTableCellRenderer
    {
      public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
          int row, int column)
      {
        if (value instanceof TableColumn)
        {
          TableColumn tableColumn = (TableColumn) value;
          Object o = tableColumn.getHeaderValue();
          setText((String)o);
        }
        return this;
      }
    }
  
  }
  </code></pre>