Search code examples
javaswingjtablejcomboboxtablecellrenderer

How to add a table, with one row made of JComboboxes, on top an existing JTable


I am trying to add a one row JTable on top of an existing JTable which is usually created dynamically and which changes its data model quite often.

JTable

As you can see the yellow area represents just a row counter and It has been created using this source

The yellow column, therefore has been attached to the main table using the following code:

JTable rowTable = new RowNumberTable(table);
scrollTable.setRowHeaderView(rowTable);

The table columns A, B, C, and so on, if clicked, sort the JTable accordingly and this feature should be maintained (I do not want to put the JCombobox in the table header unless there is a clever way of doing both things).

My aim is to create a JTable with one row (See red row) which has as many columns as the main JTable

EDIT

Here there is the SSCCE

I would like to put JComboBoxes on top of A,B,C,D.....

The class that renders the JComboBoxes is ColumnJComboBoxTable which is called in TableDemo as follow:

//Column JCombobox
JTable columnTable = new ColumnJComboBoxTable(table);
scrollTable.setColumnHeaderView(columnTable);

Right now it is not working as I want (Well, It is not working at all)

enter image description here

Buttons at the top add and remove columns. Therefore if a column is deleted the respective JComboBox must be deleted too.

CODE

TableDemo

package com.table;
import javax.swing.*;
import javax.swing.table.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.AbstractAction;

public class TableDemo extends JFrame 
{

private static final long serialVersionUID = 1L;

public TableDemo() 
{
    super("TableDemo");

    Object[][] data = 
    {
        {"0","1","2","3","4","5","6","7","8","9","10"},
        {"0","1","2","3","4","5","6","7","8","9","10"},
        {"0","1","2","3","4","5","6","7","8","9","10"},
        {"0","1","2","3","4","5","6","7","8","9","10"},
        {"0","1","2","3","4","5","6","7","8","9","10"},
        {"0","1","2","3","4","5","6","7","8","9","10"},
        {"0","1","2","3","4","5","6","7","8","9","10"},
        {"0","1","2","3","4","5","6","7","8","9","10"},
        {"0","1","2","3","4","5","6","7","8","9","10"},
        {"0","1","2","3","4","5","6","7","8","9","10"},
        {"0","1","2","3","4","5","6","7","8","9","10"},
        {"0","1","2","3","4","5","6","7","8","9","10"},
        {"0","1","2","3","4","5","6","7","8","9","10"},
        {"0","1","2","3","4","5","6","7","8","9","10"},
    };

    String[] columnNames = 
    {       
                "A", 
                "B", 
                "C", 
                "D", 
                "E", 
                "F", 
                "G", 
                "H",
                "I", 
                "J",
                "K",
    };

    final JTable            table       = new JTable(data, columnNames);
    new JPopupMenu();
    final JToolBar          toolBar     = new JToolBar();
    final XTableColumnModel columnModel = new XTableColumnModel();

    table.setColumnModel(columnModel);
    table.createDefaultColumnsFromModel();

    toolBar.add(new JButton(new AbstractAction("ALL") {
        /**
         * 
         */
        private static final long serialVersionUID = 1L;

        public void actionPerformed(ActionEvent e) {
            columnModel.setAllColumnsVisible();
        }
    }));

    toolBar.add(new JButton(new AbstractAction("Column 0") {
        /**
         * 
         */
        private static final long serialVersionUID = 1L;

        public void actionPerformed(ActionEvent e) {
            TableColumn column  = columnModel.getColumnByModelIndex(0);
            boolean     visible = columnModel.isColumnVisible(column);
            columnModel.setColumnVisible(column, !visible);
        }
    }));
    toolBar.add(new JButton(new AbstractAction("Column 1") {
        /**
         * 
         */
        private static final long serialVersionUID = 1L;

        public void actionPerformed(ActionEvent e) {
            TableColumn column  = columnModel.getColumnByModelIndex(1);
            boolean     visible = columnModel.isColumnVisible(column);
            columnModel.setColumnVisible(column, !visible);
        }
    }));
    toolBar.add(new JButton(new AbstractAction("Column 2") {
        /**
         * 
         */
        private static final long serialVersionUID = 1L;

        public void actionPerformed(ActionEvent e) {
            TableColumn column  = columnModel.getColumnByModelIndex(2);
            boolean     visible = columnModel.isColumnVisible(column);
            columnModel.setColumnVisible(column, !visible);
        }
    }));
    toolBar.add(new JButton(new AbstractAction("Column 3") {
        /**
         * 
         */
        private static final long serialVersionUID = 1L;

        public void actionPerformed(ActionEvent e) {
            TableColumn column  = columnModel.getColumnByModelIndex(3);
            boolean     visible = columnModel.isColumnVisible(column);
            columnModel.setColumnVisible(column, !visible);
        }
    }));

    toolBar.add(new JButton(new AbstractAction("Column 4") {
        /**
         * 
         */
        private static final long serialVersionUID = 1L;

        public void actionPerformed(ActionEvent e) {
            TableColumn column  = columnModel.getColumnByModelIndex(4);
            boolean     visible = columnModel.isColumnVisible(column);
            columnModel.setColumnVisible(column, !visible);
        }
    }));
    toolBar.add(new JButton(new AbstractAction("Column 5") {
        /**
         * 
         */
        private static final long serialVersionUID = 1L;

        public void actionPerformed(ActionEvent e) {
            TableColumn column  = columnModel.getColumnByModelIndex(5);
            boolean     visible = columnModel.isColumnVisible(column);
            columnModel.setColumnVisible(column, !visible);
        }
    }));
    toolBar.add(new JButton(new AbstractAction("Column 6") {
        /**
         * 
         */
        private static final long serialVersionUID = 1L;

        public void actionPerformed(ActionEvent e) {
            TableColumn column  = columnModel.getColumnByModelIndex(6);
            boolean     visible = columnModel.isColumnVisible(column);
            columnModel.setColumnVisible(column, !visible);
        }
    }));
    toolBar.add(new JButton(new AbstractAction("Column 7") {
        /**
         * 
         */
        private static final long serialVersionUID = 1L;

        public void actionPerformed(ActionEvent e) {
            TableColumn column  = columnModel.getColumnByModelIndex(7);
            boolean     visible = columnModel.isColumnVisible(column);
            columnModel.setColumnVisible(column, !visible);
        }
    }));
    toolBar.add(new JButton(new AbstractAction("Column 8") {
        /**
         * 
         */
        private static final long serialVersionUID = 1L;

        public void actionPerformed(ActionEvent e) {
            TableColumn column  = columnModel.getColumnByModelIndex(8);
            boolean     visible = columnModel.isColumnVisible(column);
            columnModel.setColumnVisible(column, !visible);
        }
    }));
    toolBar.add(new JButton(new AbstractAction("Column 9") {
        /**
         * 
         */
        private static final long serialVersionUID = 1L;

        public void actionPerformed(ActionEvent e) {
            TableColumn column  = columnModel.getColumnByModelIndex(9);
            boolean     visible = columnModel.isColumnVisible(column);
            columnModel.setColumnVisible(column, !visible);
        }
    }));
    toolBar.add(new JButton(new AbstractAction("Column 10") {
        /**
         * 
         */
        private static final long serialVersionUID = 1L;

        public void actionPerformed(ActionEvent e) {
            TableColumn column  = columnModel.getColumnByModelIndex(10);
            boolean     visible = columnModel.isColumnVisible(column);
            columnModel.setColumnVisible(column, !visible);
        }
    }));


    //Row number
    table.setFillsViewportHeight(true);
    JScrollPane scrollTable = new JScrollPane(table,
                JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
                JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
    scrollTable.setViewportView(table);

    JTable rowTable = new RowNumberTable(table);
    scrollTable.setRowHeaderView(rowTable);
    scrollTable.setCorner(JScrollPane.UPPER_LEFT_CORNER, rowTable.getTableHeader());

    //Column JCombobox
    JTable columnTable = new ColumnJComboBoxTable(table);
    scrollTable.setColumnHeaderView(columnTable);

    getContentPane().add(toolBar,       BorderLayout.NORTH);
    getContentPane().add(scrollTable,    BorderLayout.CENTER);

    addWindowListener(new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
            System.exit(0);
        }
    });
}

public static void main(String[] args) 
{
    TableDemo frame = new TableDemo();

    frame.pack();
    frame.repaint();
    frame.setVisible(true);
}
}

XTableColumnModel

The table model has been borrowed here

package com.table;
import javax.swing.table.*;
import java.util.Vector;
import java.util.Enumeration;


public class XTableColumnModel extends DefaultTableColumnModel {

private static final long serialVersionUID = 1L;

protected Vector allTableColumns            = new Vector();

XTableColumnModel() {
}

public void setColumnVisible(TableColumn column, boolean visible) {
    if(!visible) {
        super.removeColumn(column);
    }
    else 
    {
        int noVisibleColumns    = tableColumns.size();
        int noInvisibleColumns  = allTableColumns.size();
        int visibleIndex        = 0;

        for(int invisibleIndex = 0; invisibleIndex < noInvisibleColumns; ++invisibleIndex) {
            TableColumn visibleColumn   = (visibleIndex < noVisibleColumns ? (TableColumn)tableColumns.get(visibleIndex) : null);
            TableColumn testColumn      = (TableColumn)allTableColumns.get(invisibleIndex);

            if(testColumn == column) {
                if(visibleColumn != column) {
                    super.addColumn(column);
                    super.moveColumn(tableColumns.size() - 1, visibleIndex);
                }
                return;
            }
            if(testColumn == visibleColumn) {
                ++visibleIndex;
            }
        }
    }
}

public void setAllColumnsVisible() {
    int noColumns       = allTableColumns.size();

    for(int columnIndex = 0; columnIndex < noColumns; ++columnIndex) {
        TableColumn visibleColumn = (columnIndex < tableColumns.size() ? (TableColumn)tableColumns.get(columnIndex) : null);
        TableColumn invisibleColumn = (TableColumn)allTableColumns.get(columnIndex);

        if(visibleColumn != invisibleColumn) {
            super.addColumn(invisibleColumn);
            super.moveColumn(tableColumns.size() - 1, columnIndex);
        }
    }
}

public TableColumn getColumnByModelIndex(int modelColumnIndex) {
    for (int columnIndex = 0; columnIndex < allTableColumns.size(); ++columnIndex) {
        TableColumn column = (TableColumn)allTableColumns.elementAt(columnIndex);
        if(column.getModelIndex() == modelColumnIndex) {
            return column;
        }
    }
    return null;
}

public boolean isColumnVisible(TableColumn aColumn) {
    return (tableColumns.indexOf(aColumn) >= 0);
}

public void addColumn(TableColumn column) {
    allTableColumns.addElement(column);
    super.addColumn(column);
}

public void removeColumn(TableColumn column) {
    int allColumnsIndex = allTableColumns.indexOf(column);
    if(allColumnsIndex != -1) {
        allTableColumns.removeElementAt(allColumnsIndex);
    }
    super.removeColumn(column);
}

public void moveColumn(int oldIndex, int newIndex) {
if ((oldIndex < 0) || (oldIndex >= getColumnCount()) ||
    (newIndex < 0) || (newIndex >= getColumnCount()))
    throw new IllegalArgumentException("moveColumn() - Index out of range");

    TableColumn fromColumn  = (TableColumn) tableColumns.get(oldIndex);
    TableColumn toColumn    = (TableColumn) tableColumns.get(newIndex);

    int allColumnsOldIndex  = allTableColumns.indexOf(fromColumn);
    int allColumnsNewIndex  = allTableColumns.indexOf(toColumn);

    if(oldIndex != newIndex) {
        allTableColumns.removeElementAt(allColumnsOldIndex);
        allTableColumns.insertElementAt(fromColumn, allColumnsNewIndex);
    }

    super.moveColumn(oldIndex, newIndex);
}

public int getColumnCount(boolean onlyVisible) {
    Vector columns = (onlyVisible ? tableColumns : allTableColumns);
return columns.size();
}

public Enumeration getColumns(boolean onlyVisible) {
    Vector columns = (onlyVisible ? tableColumns : allTableColumns);

return columns.elements();
}

public int getColumnIndex(Object identifier, boolean onlyVisible) {
if (identifier == null) {
    throw new IllegalArgumentException("Identifier is null");
}

    Vector      columns     = (onlyVisible ? tableColumns : allTableColumns);
    int         noColumns   = columns.size();
    TableColumn column;

    for(int columnIndex = 0; columnIndex < noColumns; ++columnIndex) {
    column = (TableColumn)columns.get(columnIndex);

        if(identifier.equals(column.getIdentifier()))
    return columnIndex;
    }

throw new IllegalArgumentException("Identifier not found");
}

public TableColumn getColumn(int columnIndex, boolean onlyVisible) {
return (TableColumn)tableColumns.elementAt(columnIndex);
}
}

RowNumberTable

package com.table;
import java.awt.*;
import java.beans.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;


public class RowNumberTable extends JTable
implements ChangeListener, PropertyChangeListener, TableModelListener
{

private static final long serialVersionUID = -3837585325898676144L;
private JTable main;

public RowNumberTable(JTable table)
{
    main = table;
    main.addPropertyChangeListener( this );
    main.getModel().addTableModelListener( this );

    setFocusable( false );
    setAutoCreateColumnsFromModel( false );
    setSelectionModel( main.getSelectionModel() );

    TableColumn column = new TableColumn();
    column.setHeaderValue(" ");
    addColumn( column );
    column.setCellRenderer(new RowNumberRenderer());

    getColumnModel().getColumn(0).setPreferredWidth(50);
    setPreferredScrollableViewportSize(getPreferredSize());
}

@Override
public void addNotify()
{
    super.addNotify();

    Component c = getParent();

    //  Keep scrolling of the row table in sync with the main table.

    if (c instanceof JViewport)
    {
        JViewport viewport = (JViewport)c;
        viewport.addChangeListener( this );
    }
}


@Override
public int getRowCount()
{
    return main.getRowCount();
}

@Override
public int getRowHeight(int row)
{
    int rowHeight = main.getRowHeight(row);

    if (rowHeight != super.getRowHeight(row))
    {
        super.setRowHeight(row, rowHeight);
    }

    return rowHeight;
}


@Override
public Object getValueAt(int row, int column)
{
    return Integer.toString(row + 1);
}


@Override
public boolean isCellEditable(int row, int column)
{
    return false;
}

@Override
public void setValueAt(Object value, int row, int column) {}

public void stateChanged(ChangeEvent e)
{
    //  Keep the scrolling of the row table in sync with main table

    JViewport viewport = (JViewport) e.getSource();
    JScrollPane scrollPane = (JScrollPane)viewport.getParent();
    scrollPane.getVerticalScrollBar().setValue(viewport.getViewPosition().y);
    viewport.setBackground(Color.WHITE);
}

public void propertyChange(PropertyChangeEvent e)
{
    //  Keep the row table in sync with the main table

    if ("selectionModel".equals(e.getPropertyName()))
    {
        setSelectionModel( main.getSelectionModel() );
    }

    if ("rowHeight".equals(e.getPropertyName()))
    {
        repaint();
    }

    if ("model".equals(e.getPropertyName()))
    {
        main.getModel().addTableModelListener( this );
        revalidate();
    }
}

@Override
public void tableChanged(TableModelEvent e)
{
    revalidate();
}

/*
 *  Attempt to mimic the table header renderer
 */
private static class RowNumberRenderer extends DefaultTableCellRenderer
{
    /**
     * 
     */
    private static final long serialVersionUID = 6579115025835194953L;

    public RowNumberRenderer()
    {
        setHorizontalAlignment(JLabel.CENTER);
    }

    public Component getTableCellRendererComponent(
        JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
    {
        if (table != null)
        {
            JTableHeader header = table.getTableHeader();

            if (header != null)
            {
                setForeground(header.getForeground());
                setBackground(header.getBackground());
                setFont(header.getFont());
            }
        }

        if (isSelected)
        {
            setFont( getFont().deriveFont(Font.ITALIC) );
        }

        setText((value == null) ? "" : value.toString());
        //setBorder(UIManager.getBorder("TableHeader.cellBorder"));

        return this;
    }
  }
}

ColumnJComboBoxTable

package com.table;
import java.awt.*;
import java.beans.*;

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;


public class ColumnJComboBoxTable extends JTable implements ChangeListener,PropertyChangeListener, TableModelListener
{

private static final long serialVersionUID = -3837585325898676144L;
private JTable main;

public ColumnJComboBoxTable(JTable table)
{
    main = table;
    main.addPropertyChangeListener( this );
    main.getModel().addTableModelListener( this );

    setFocusable( false );
    setAutoCreateColumnsFromModel( false );
    setSelectionModel( main.getSelectionModel() );

    for(int i =0; i<main.getModel().getColumnCount();i++)
    {
        TableColumn column = new TableColumn();
        column.setHeaderValue(i);
        addColumn( column );
        getColumnModel().getColumn(i).setPreferredWidth(50);
        column.setCellRenderer(new ColumnJComboBoxRenderer());
        column.setHeaderRenderer(new ColumnJComboBoxRenderer());
    }
    setPreferredScrollableViewportSize(getPreferredSize());

}

@Override
public void addNotify()
{
    super.addNotify();

    Component c = getParent();

    //  Keep scrolling of the row table in sync with the main table.

    if (c instanceof JViewport)
    {
        JViewport viewport = (JViewport)c;
        viewport.addChangeListener( this );
    }
}

@Override
public int getColumnCount()
{
    return main.getColumnCount();
}

@Override
public int getRowHeight(int row)
{
    int rowHeight = main.getRowHeight(row);

    if (rowHeight != super.getRowHeight(row))
    {
        super.setRowHeight(row, rowHeight);
    }
    return rowHeight;
}

/*
 *  No model is being used for this table so just use the row number
 *  as the value of the cell.
 */
@Override
public Object getValueAt(int row, int column)
{
    return Integer.toString(column + 1);
}

/*
 *  Don't edit data in the main TableModel by mistake
 */
@Override
public boolean isCellEditable(int row, int column)
{
    return false;
}

/*
 *  Do nothing since the table ignores the model
 */

@Override
public void setValueAt(Object value, int row, int column) {}
//
//  Implement the ChangeListener
//
public void stateChanged(ChangeEvent e)
{
    //  Keep the scrolling of the row table in sync with main table
    JViewport viewport = (JViewport) e.getSource();
    JScrollPane scrollPane = (JScrollPane)viewport.getParent();
    scrollPane.getHorizontalScrollBar().setValue(viewport.getViewPosition().x);
}

//
//  Implement the PropertyChangeListener
//

public void propertyChange(PropertyChangeEvent e)
{
    //  Keep the row table in sync with the main table

    if ("selectionModel".equals(e.getPropertyName()))
    {
        setSelectionModel( main.getSelectionModel() );
    }

    if ("rowHeight".equals(e.getPropertyName()))
    {
        repaint();
    }

    if ("model".equals(e.getPropertyName()))
    {
        main.getModel().addTableModelListener( this );
        revalidate();
    }
}

//
//  Implement the TableModelListener
//
@Override
public void tableChanged(TableModelEvent e)
{
    revalidate();
}

/*
 *  Attempt to mimic the table header renderer
 */
private static class ColumnJComboBoxRenderer extends DefaultTableCellRenderer
{
    /**
     * 
     */
    private static final long serialVersionUID = 6579115025835194953L;

    public ColumnJComboBoxRenderer()
    {
        setHorizontalAlignment(JLabel.HORIZONTAL);
    }

    public Component getTableCellRendererComponent(
        JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
    {
        if (table != null)
        {
            JTableHeader header = table.getTableHeader();

            if (header != null)
            {
                setForeground(header.getForeground());
                setBackground(header.getBackground());
                setFont(header.getFont());
            }
        }

        if (isSelected)
        {
            setFont( getFont().deriveFont(Font.ITALIC) );
        }
        setText((value == null) ? "" : value.toString());

        return this;
    }
  }
}

Anyone's help would be highly appreciated

Thanks


Solution

  • Thanks to @mKorbel for the help

    Here the code of a solution

    import java.awt.*;
    import javax.swing.*; 
    import javax.swing.event.*;
    import javax.swing.table.*;
    
    public class TableFilterRow implements TableColumnModelListener
    {
    
    private JTable table;
    private JPanel filterRow;
    
    public TableFilterRow(JTable table,JPanel filterRow) 
    {
        this.table = table;
        this.filterRow=filterRow;
        table.setPreferredScrollableViewportSize(table.getPreferredSize());
        table.getColumnModel().addColumnModelListener(this);
        table.columnMarginChanged(new ChangeEvent(table.getColumnModel()));
    }
    
    @Override
    public void columnMarginChanged(ChangeEvent e) 
    {
        TableColumnModel tcm = table.getColumnModel();
    
        int columns = tcm.getColumnCount();
    
        for (int i = 0; i < columns; i++)
        {
            JComboBox<?> comboBox = (JComboBox<?>) filterRow.getComponent(i);
            Dimension d = comboBox.getPreferredSize();
            d.width = tcm.getColumn(i).getWidth();
            comboBox.setPreferredSize(d);
        }
    
        SwingUtilities.invokeLater(new Runnable() 
        {
            @Override
            public void run() 
            {
            filterRow.revalidate();
            }
        });
    }
    
    @Override
    public void columnMoved(TableColumnModelEvent e) 
    {
         Component moved = filterRow.getComponent(e.getFromIndex());
         filterRow.remove(e.getFromIndex());
         filterRow.add(moved, e.getToIndex());
         filterRow.validate();
    }
    
    @Override
    public void columnAdded(TableColumnModelEvent e) 
    {
    
    }
    
    @Override
    public void columnRemoved(TableColumnModelEvent e) 
    {
    
    }
    
    @Override
    public void columnSelectionChanged(ListSelectionEvent e) 
    {
    
    }
    
    public static void main(String[] args) 
    {
        JFrame frame = new JFrame();
        frame.setLayout(new BorderLayout());
        JTable table = new JTable(3, 5);
        table.setPreferredScrollableViewportSize(table.getPreferredSize());
        table.columnMarginChanged(new ChangeEvent(table.getColumnModel()));
    
        ComboFields[] comboFields = ComboFields.values();
    
        JScrollPane scrollPane = new JScrollPane(table);
        frame.add(scrollPane, BorderLayout.CENTER);
    
        JPanel filterRow = new JPanel();
        filterRow.setLayout(new FlowLayout(FlowLayout.CENTER, 0, 0));
    
        for (int i = 0; i < table.getColumnCount(); i++) 
        {
            filterRow.add(new JComboBox<ComboFields>(comboFields));
        }
        new TableFilterRow(table, filterRow);
    
        frame.add(filterRow, BorderLayout.NORTH);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
    
    private enum ComboFields{
    
        VALUE_0(0), 
        VALUE_1(1),
        VALUE_2(2),
        VALUE_3(3),
        VALUE_4(4),
        VALUE_5(5);
        // Internal state
        private int fieldNumber;
    
        private ComboFields(final int fieldNumber) 
        {
            this.setFieldNumber(fieldNumber);
        }
    
        @SuppressWarnings("unused")
        public int getFieldNumber() {
            return fieldNumber;
        }
    
        public void setFieldNumber(int fieldNumber) 
        {
            this.fieldNumber = fieldNumber;
        }
      }
    }