Is this your first time here? SwingWiki is a Java Swing Developer community site with an big archive of Swing-related usenet groups and mailing lists, but also tips, tricks and articles and book reviews written by your colleagues from around the world. If you came here through a search engine and did not find what you were looking for, make sure to check the wiki table of contents.

Standard JTable does not come with an interactive mode such as those found in simple spreadsheets. Adding a simple record oftentimes requires an ‘Add’ button, a dialog box asking for values then updating the JTable itself.

This example will show you an audio listing application containing Title, Artist and Album, without the need for an ‘Add’ button. All we need is our own-defined TableModel, a TableModelListener, a customized TableCellRenderer and a few auxillary methods.

First, we’ll create a AudioRecord class to act as a value object:

public class AudioRecord {
     protected String title;
     protected String artist;
     protected String album;
     
     public AudioRecord() {
         title = "";
         artist = "";
         album = "";
     }
     
     public String getTitle() {
         return title;
     }
     
     public void setTitle(String title) {
         this.title = title;
     }
     
     public String getArtist() {
         return artist;
     }
     
     public void setArtist(String artist) {
         this.artist = artist;
     }
     
     public String getAlbum() {
         return album;
     }
     
     public void setAlbum(String album) {
         this.album = album;
     }
 }

Next, we’ll create InteractiveTableModel which implements AbstractTableModel:

 import java.util.Collection;
 import java.util.Vector;
 import javax.swing.table.AbstractTableModel;
 
 public class InteractiveTableModel extends AbstractTableModel {
     public static final int TITLE_INDEX = 0;
     public static final int ARTIST_INDEX = 1;
     public static final int ALBUM_INDEX = 2;
     public static final int HIDDEN_INDEX = 3;
     
     protected String[] columnNames;
     protected Vector dataVector;
     
     public InteractiveTableModel(String[] columnNames) {
         this.columnNames = columnNames;
         dataVector = new Vector();
     }
     
     public String getColumnName(int column) {
         return columnNames[column];
     }
     
     public boolean isCellEditable(int row, int column) {
         if (column == HIDDEN_INDEX) return false;
         else return true;
     }
     
     public Class getColumnClass(int column) {
         switch (column) {
             case TITLE_INDEX:
             case ARTIST_INDEX:
             case ALBUM_INDEX:
                return String.class;
             default:
                return Object.class;
         }
     }
     
     public Object getValueAt(int row, int column) {
         AudioRecord record = (AudioRecord)dataVector.get(row);
         switch (column) {
             case TITLE_INDEX:
                return record.getTitle();
             case ARTIST_INDEX:
                return record.getArtist();
             case ALBUM_INDEX:
                return record.getAlbum();
             default:
                return new Object();
         }
     }
     
     public void setValueAt(Object value, int row, int column) {
         AudioRecord record = (AudioRecord)dataVector.get(row);
         switch (column) {
             case TITLE_INDEX:
                record.setTitle((String)value);
                break;
             case ARTIST_INDEX:
                record.setArtist((String)value);
                break;
             case ALBUM_INDEX:
                record.setAlbum((String)value);
                break;
             default:
                System.out.println("invalid index");
         }
         fireTableCellUpdated(row, column);
     }
     
     public int getRowCount() {
         return dataVector.size();
     }
     
     public int getColumnCount() {
         return columnNames.length;
     }
 
     public boolean hasEmptyRow() {
         if (dataVector.size() == 0) return false;
         AudioRecord audioRecord = (AudioRecord)dataVector.get(dataVector.size() - 1);
         if (audioRecord.getTitle().trim().equals("") &&
            audioRecord.getArtist().trim().equals("") &&
            audioRecord.getAlbum().trim().equals("")) 
         {
            return true;
         }
         else return false;
     }
     
     public void addEmptyRow() {
         dataVector.add(new AudioRecord());
         fireTableRowsInserted(
            dataVector.size() - 1,
            dataVector.size() - 1);
     }
 }

NOTE: The HIDDEN_INDEX column acts as a dummy cell for later use.

Now, for our main frame:

 import java.awt.event.WindowAdapter;
 import java.awt.event.WindowEvent;
 import java.awt.BorderLayout;
 import java.awt.Component;
 import java.awt.FlowLayout;
 import javax.swing.event.TableModelEvent;
 import javax.swing.event.TableModelListener;
 import javax.swing.table.DefaultTableCellRenderer;
 import javax.swing.table.TableColumn;
 import javax.swing.JButton;
 import javax.swing.JFrame;
 import javax.swing.JLabel;
 import javax.swing.JPanel;
 import javax.swing.JScrollPane;
 import javax.swing.JTable;
 import javax.swing.JTextField;
 import javax.swing.UIManager;
 
 public class InteractiveForm extends JPanel {
     public static final String[] columnNames = {
         "Title", "Artist", "Album", ""
     };
     
     protected JTable table;
     protected JScrollPane scroller;
     protected InteractiveTableModel tableModel;
     
     public InteractiveForm() {
         initComponent();
     }
     
     public void initComponent() {
         tableModel = new InteractiveTableModel(columnNames);
         tableModel.addTableModelListener(new InteractiveForm.InteractiveTableModelListener());
         table = new JTable();
         table.setModel(tableModel);
         table.setSurrendersFocusOnKeystroke(true);
         if (!tableModel.hasEmptyRow()) {
             tableModel.addEmptyRow();
         }
         
         scroller = new javax.swing.JScrollPane(table);
         table.setPreferredScrollableViewportSize(new java.awt.Dimension(500, 300));
         TableColumn hidden = table.getColumnModel().getColumn(InteractiveTableModel.HIDDEN_INDEX);
         hidden.setMinWidth(2);
         hidden.setPreferredWidth(2);
         hidden.setMaxWidth(2);
         hidden.setCellRenderer(new InteractiveRenderer(InteractiveTableModel.HIDDEN_INDEX));
         
         setLayout(new BorderLayout());
         add(scroller, BorderLayout.CENTER);
     }
     
     public void highlightLastRow(int row) {
         int lastrow = tableModel.getRowCount();
         if (row == lastrow - 1) {
             table.setRowSelectionInterval(lastrow - 1, lastrow - 1);
         } else {
             table.setRowSelectionInterval(row + 1, row + 1);
         }
         
         table.setColumnSelectionInterval(0, 0);
     }
     
     class InteractiveRenderer extends DefaultTableCellRenderer {
         protected int interactiveColumn;
         
         public InteractiveRenderer(int interactiveColumn) {
             this.interactiveColumn = interactiveColumn;
         }
         
         public Component getTableCellRendererComponent(JTable table,
            Object value, boolean isSelected, boolean hasFocus, int row,
            int column) 
         {
             Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
             if (column == interactiveColumn && hasFocus) {
                 if ((InteractiveForm.this.tableModel.getRowCount() - 1) == row &&
                    !InteractiveForm.this.tableModel.hasEmptyRow()) 
                 {
                     InteractiveForm.this.tableModel.addEmptyRow();
                 }
                 
                 highlightLastRow(row);
             }
             
             return c;
         }
     }
     
     public class InteractiveTableModelListener implements TableModelListener {
         public void tableChanged(TableModelEvent evt) {
             if (evt.getType() == TableModelEvent.UPDATE) {
                 int column = evt.getColumn();
                 int row = evt.getFirstRow();
                 System.out.println("row: " + row + " column: " + column);
                 table.setColumnSelectionInterval(column + 1, column + 1);
                 table.setRowSelectionInterval(row, row);
             }
         }
     }
     
     public static void main(String[] args) {
         try {
             UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
             JFrame frame = new JFrame("Interactive Form");
             frame.addWindowListener(new WindowAdapter() {
                 public void windowClosing(WindowEvent evt) {
                     System.exit(0);
                 }
             });
             frame.getContentPane().add(new InteractiveForm());
             frame.pack();
             frame.setVisible(true);
         } catch (Exception e) {
             e.printStackTrace();
         }
     }
 }

In order to for JTable to move from left to right column, we added a InteractiveTableModelListener to listen for any updates done inside the cells:

public void tableChanged(TableModelEvent evt) {
    if (evt.getType() == TableModelEvent.UPDATE) {
        int column = evt.getColumn();
        int row = evt.getFirstRow();
        System.out.println("row: " + row + " column: " + column);
        table.setColumnSelectionInterval(column + 1, column + 1);
        table.setRowSelectionInterval(row, row);
    }
}

Also, this line should be added after initializing our JTable:

table.setSurrendersFocusOnKeystroke(true);

This line will initialize an empty row in our JTable for first time use:

if (!tableModel.hasEmptyRow()) {
    tableModel.addEmptyRow();
}

We need to make HIDDEN_INDEX table column’s width as small as possible and at the same time our table cell renderer could also be called when it receives the cell focus:

TableColumn hidden = table.getColumnModel().getColumn(InteractiveTableModel.HIDDEN_INDEX);
    hidden.setMinWidth(2);
    hidden.setPreferredWidth(2);
    hidden.setMaxWidth(2);
    hidden.setCellRenderer(new InteractiveRenderer(InteractiveTableModel.HIDDEN_INDEX));

HIDDEN_INDEX column will act as a dummy cell to intercept any focus it receives and in turn it will automatically add a new row below it, if it doesn’t have one.

NOTE: Setting column width to 1 doesn’t call our plugged-in table cell renderer.

The main trick to move from the upper row to the next row is found in InteractiveRenderer class:

    class InteractiveRenderer extends DefaultTableCellRenderer {
         protected int interactiveColumn;
         
         public InteractiveRenderer(int interactiveColumn) {
             this.interactiveColumn = interactiveColumn;
         }
         
         public Component getTableCellRendererComponent(JTable table,
            Object value, boolean isSelected, boolean hasFocus, int row,
            int column) 
         {
             Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
             if (column == interactiveColumn && hasFocus) {
                 if ((InteractiveForm.this.tableModel.getRowCount() - 1) == row &&
                    !InteractiveForm.this.tableModel.hasEmptyRow()) 
                 {
                     InteractiveForm.this.tableModel.addEmptyRow();
                 }
                 
                 highlightLastRow(row);
             }
             
             return c;
         }
     }

Take a closer look at getTableCellRendererComponent method. If our cell focus reaches our desired table column index, which is HIDDEN_INDEX, and the AudioRecord’s title attribute is not "", we’ll force the table model to add a new row:

         if (column == interactiveColumn && hasFocus) {
             if ((InteractiveForm.this.tableModel.getRowCount() - 1) == row &&
                 !InteractiveForm.this.tableModel.hasEmptyRow()) 
             {
                 InteractiveForm.this.tableModel.addEmptyRow();
             }
                 
             highlightLastRow(row);
         }

If we haven’t reached the last row, JTable is forced to select the next row; otherwise JTable is forced to select the newly-added empty row:

    public void highlightLastRow(int row) {
         int lastrow = tableModel.getRowCount();
         if (row == lastrow - 1) {
             table.setRowSelectionInterval(lastrow - 1, lastrow - 1);
         } else {
             table.setRowSelectionInterval(row + 1, row + 1);
         }
         
         table.setColumnSelectionInterval(0, 0);
     }
 

Comments? Corrections? Contact us or Login to edit pages directly (registration is free and takes less than displaying a JLabel)
  howto/interactive_jtable.txt · Last modified: 2005/04/27 12:30 by 222.126.29.146 (ming)
 
Recent changes | RSS changes | Table of contents | News Archive | Terms And Conditions | Register | The Quest For Software++| Ruby Resources

Sedo - Buy and Sell Domain Names and Websites project info: swingwiki.org Statistics for project swingwiki.org etracker� web controlling instead of log file analysis