Tables

JTables allow for the tabular layout of information within an applet. The table header and table details are passed into the JTable constructor, as shown in the example below. The table header is always an array of String. The table data is an array of Object, which means that it can contain non-text details.

Table Demo (Run Example)

import java.awt.*;
import javax.swing.*;

public class TableDemo extends JApplet
{
    @Override
    public void init()
    {
        this.setContentPane(new View());
    }

    public class View extends JPanel
    {
        public View()
        {
            String[] columnNames =
            {
                "Name", "Surname", "Age"
            };

            Object[][] data =
            {
                {
                    "Mary", "Murphy", new Integer(24)
                },
                {
                    "Brian", "Brady", new Integer(25)
                },
                {
                    "Adam", "Andrews", new Integer(23)
                }
            };

            JTable table = new JTable(data, columnNames);

            this.setLayout(new GridLayout(1, 1));
            this.add(new JScrollPane(table));
        }
    }
}

Why is it good to seperate the table header and the table data from each other?

Is it good that the table header and table data are declared outside of the table and then passed into the table as Constructor parameters?

Extending JTable

We can extend JTable to create our own Table class.

The example below allows the user to sort the data by clicking on the header titles, by using the JTable method below:

// allow user to sort rows by clicking on column headers
this.setAutoCreateRowSorter(true);

This example also disables the ability to move columns, by using the JTable method below:

// do not allow the columns to move
this.getTableHeader().setReorderingAllowed(false);

 

User Defined Table (Run Example)

import java.awt.*;
import javax.swing.*;
import javax.swing.table.TableColumn;

public class Table_User_Defined_Demo extends JApplet
{
    @Override
    public void init()
    {
        this.setContentPane(new View());
    }

    public class View extends JPanel
    {
        public View()
        {
            String[] columnNames =
            {
                "Name", "Number"
            };

            Object[][] data =
            {
                {
                    "Adam", new Integer(1)
                },
                {
                    "Bill", new Integer(2)
                },
                {
                    "Colm", new Integer(3)
                },
                {
                    "David", new Integer(24)
                },
                {
                    "Eric", new Integer(5)
                },
                {
                    "Frank", new Integer(17)
                },
                {
                    "Gary", new Integer(29)
                },
                {
                    "Hiram", new Integer(33)
                },
                {
                    "Ivan", new Integer(32)
                },
                {
                    "Joseph", new Integer(45)
                },
                {
                    "Keith", new Integer(10)
                }
            };

            NameTable nameTable = new NameTable(data, columnNames);

            this.setLayout(new GridLayout(1, 1));
            this.add(new JScrollPane(nameTable));
        }
    }
}

class NameTable extends JTable
{
    public NameTable(Object[][] data, String[] columnNames)
    {
        super(data, columnNames);

        TableColumn column;
        for (int i = 0; i < this.getModel().getColumnCount(); i++)
        {
            column = this.getColumnModel().getColumn(i);

            // set a default width
            column.setPreferredWidth(10);
        }
        // set column 0 (team name) width to 150
        this.getColumnModel().getColumn(0).setPreferredWidth(150);

        // do not allow the columns to move
        this.getTableHeader().setReorderingAllowed(false);

        // allow user to sort rows by clicking on column headers
        this.setAutoCreateRowSorter(true);
    }
}

Write a programme to create a table that holds country capital city details.

Table Model

Implementing the AbstractTableModel class gives access to a table's data. The header and data can be loaded. Individual cells can be written to/read from and be made editable or read only. The count of the number of columns and rows is also available.

User Defined Model (Run Example)

import java.awt.*;
import javax.swing.*;
import javax.swing.table.AbstractTableModel;

public class Table_User_Defined_Model_Demo extends JApplet
{
    @Override
    public void init()
    {
        this.setContentPane(new View());
    }

    public class View extends JPanel
    {
        public View()
        {
            String[] columnNames =
            {
                "Manufacturer", "Model", "Year", "Color"
            };

            Object[][] data =
            {
                {
                    "Toyota", "Yaris", new Integer(2011), "Red"
                },
                {
                    "Ford", "Focus", new Integer(2010), "Blue"
                },
                {
                    "Toyota", "Yaris", new Integer(2011), "Red"
                },
                {
                    "VW", "Beetle", new Integer(2010), "Green"
                }
            };
            CarTable carTable = new CarTable(data, columnNames);

            this.setLayout(new GridLayout(1, 1));
            this.add(new JScrollPane(carTable));
        }
    }
}

class CarTable extends JTable
{
    public CarTable(Object[][] data, String[] columnNames)
    {
        super(new CarTableModel(data, columnNames));
    }
}

class CarTableModel extends AbstractTableModel
{
    private String[] columnNames;
    private Object[][] data;

    public CarTableModel()
    {
        data = null;
        columnNames = null;
    }

    public CarTableModel(Object[][] data, String[] columnNames)
    {
        this.columnNames = columnNames;
        this.data = data;
    }

    public void setColumnNames(String[] columnNames)
    {
        this.columnNames = columnNames;
    }

    public void setData(Object[][] data)
    {
        this.data = data;
    }

    @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)
    {
        // do not allow user to edit cells
        return false;
    }

    @Override
    public void setValueAt(Object value, int row, int col)
    {
        data[row][col] = value;
        fireTableCellUpdated(row, col);
    }
}

Adjust the code above to allow the user to edit the 'year' details for any car. Ensure that you validate your input.

Cell Rendering

The java DefaultTableCellRenderer class is used by JTable to render cells. By extending the DefaultTableCellRenderer class, we can control the appearance of the cells in a table. In order to render cells in a non-default way, we need to extend this class.

In the example below, the user defined ColorTableCellRenderer class is applied to the table header and every second row of the table.

Basic Cell Rending Example (Run Example)
import java.awt.*;
import javax.swing.*;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableColumn;

public class Table_Basic_Cell_Rendering_Demo extends JApplet
{
    @Override
    public void init()
    {
        this.setContentPane(new View());
    }

    public class View extends JPanel
    {
        public View()
        {
            String[] columnNames =
            {
                "Name", "Number"
            };

            Object[][] data =
            {
                {
                    "Adam", new Integer(1)
                },
                {
                    "Bill", new Integer(2)
                },
                {
                    "Colm", new Integer(3)
                },
                {
                    "David", new Integer(24)
                },
                {
                    "Eric", new Integer(5)
                },
                {
                    "Frank", new Integer(17)
                },
                {
                    "Gary", new Integer(29)
                },
                {
                    "Hiram", new Integer(33)
                },
                {
                    "Ivan", new Integer(32)
                },
                {
                    "Joseph", new Integer(45)
                },
                {
                    "Keith", new Integer(10)
                }
            };

            NameTable nameTable = new NameTable(data, columnNames);

            this.setLayout(new GridLayout(1, 1));
            this.add(new JScrollPane(nameTable));
        }
    }
}

class NameTable extends JTable
{
    public NameTable(Object[][] data, String[] columnNames)
    {
        super(data, columnNames);

        // also use the cell renderer on the table header
        this.getTableHeader().setDefaultRenderer(new ColorTableCellRenderer());

        TableColumn column;
        for (int i = 0; i < this.getModel().getColumnCount(); i++)
        {
            column = this.getColumnModel().getColumn(i);            

            // set the row background colours
            column.setCellRenderer(new ColorTableCellRenderer());
        }       
    }
}

class ColorTableCellRenderer extends DefaultTableCellRenderer
{
    @Override
    public Component getTableCellRendererComponent(JTable table,
                                                   Object obj, boolean isSelected, boolean hasFocus, int row, int column)
    {
        Component cell = super.getTableCellRendererComponent(
                table, obj, isSelected, hasFocus, row, column);
        if (row == -1) // row -1 is the table header
        {
            cell.setBackground(new Color(255, 255, 200));
        }
        else
        {
            if (isSelected) // the selected row
            {
                cell.setBackground(new Color(200, 200, 255));
            }
            else // all non-selected rows
            {
                if (row % 2 == 0)
                {
                    cell.setBackground(Color.white);
                }
                else
                {
                    cell.setBackground(new Color(240, 240, 240));
                }
            }
        }
        return cell;
    }
}

Adjust the code above to make the cells un-editable and make the 'Name' column 150 wide.

Write a program to display every odd row red, every even row blue and the text white.

Write a program to display every first row red, every second row green and every third row blue.

Write a program to display every second column red.

The DefaultTableCellRenderer class only renders interactive components. It does not display the actual interactive components. Therefore, we cannot embed most interactive components, such as JButtons inside cells. However, as an exception, both ComboBoxs and CheckBoxs can be added to cells. In the example below, ComboBox, CheckBox, ImageIcon and TextArea are all embedded into cells.

Advanced Cell Rendering (Run Example)

import java.awt.*;
import java.awt.image.BufferedImage;
import java.net.URL;
import javax.swing.*;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableColumn;

public class Table_Cell_Rendering_Demo extends JApplet
{
    @Override
    public void init()
    {
        this.setContentPane(new View());
    }

    public class View extends JPanel
    {
        public View()
        {
            final ResizedImageIcon tigerLogo = new ResizedImageIcon(getClass().getClassLoader().getResource("images/tiger.jpg"), AnimalTable.IMAGE_HEIGHT, AnimalTable.IMAGE_HEIGHT);
            final ResizedImageIcon sharkLogo = new ResizedImageIcon(getClass().getClassLoader().getResource("images/shark.jpg"), AnimalTable.IMAGE_HEIGHT, AnimalTable.IMAGE_HEIGHT);
            final ResizedImageIcon crododileLogo = new ResizedImageIcon(getClass().getClassLoader().getResource("images/crocodile.jpg"), AnimalTable.IMAGE_HEIGHT, AnimalTable.IMAGE_HEIGHT);


            String[] columnNames =
            {
                "Name", "Photo", "Details", "check", "Score"
            };
            Object[][] data =
            {
                {
                    "Tiger", tigerLogo, "Tigers are the largest cat species, reaching a total body length of up to 3.3 m (11 ft) and weighing up to 306 kg (670 lb). Its most recognizable feature is a pattern of dark vertical stripes on reddish-orange fur with a lighter underside.", new Boolean(true), "***"
                },
                {
                    "Shark", sharkLogo, "Sharks are a group of fish characterized by a cartilaginous skeleton, five to seven gill slits on the sides of the head, and pectoral fins that are not fused to the head.", new Boolean(true), "***"
                },
                {
                    "Crocodile", crododileLogo, "Crocodiles are large aquatic tetrapods that live throughout the tropics in Africa, Asia, the Americas and Australia.", new Boolean(true), "***"
                }
            };
            final String[] scores =
            {
                "*", "**", "***", "****", "*****"
            };

            AnimalTable newTable = new AnimalTable(data, columnNames);

            this.setLayout(new GridLayout(1, 1));
            this.add(new JScrollPane(newTable));
        }
    }
}

class ResizedImageIcon extends ImageIcon
{
    public ResizedImageIcon(URL location, int width, int height)
    {
        super(location);
        final BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        img.getGraphics().drawImage(this.getImage(), 0, 0, width, height, null);

        this.setImage(img);
    }
}

class AnimalTable extends JTable
{
    // Column positions
    public final static int NAME = 0;
    public final static int PHOTO = 1;
    public final static int DETAILS = 2;
    public final static int CHECKBOX = 3;
    public final static int SCORE = 4;
    // The height of the images used in this table
    public final static int IMAGE_HEIGHT = 100;
    public final static int IMAGE_PADDING = 10;

    public AnimalTable(Object[][] data, String[] columnNames)
    {
        super(new AnimalTableModel(data, columnNames));

        // get rid of the cells grid
        this.setIntercellSpacing(new Dimension(0, 0));

        // make the rows high enough to hold the image icons
        this.setRowHeight(AnimalTable.IMAGE_HEIGHT + AnimalTable.IMAGE_PADDING);

        TableColumn column;
        for (int i = 0; i < this.getModel().getColumnCount(); i++)
        {
            column = this.getColumnModel().getColumn(i);

            // set the row background colours
            if (i == AnimalTable.NAME)
            {
                column.setCellRenderer(new NameRenderer());
            }
            else
            {
                if (i == AnimalTable.PHOTO)
                {
                    column.setCellRenderer(new ImageIconRenderer());
                }
                else
                {
                    if (i == AnimalTable.DETAILS)
                    {
                        column.setCellRenderer(new DetailsRenderer());
                    }
                    // set the row background colours
                    else
                    {
                        if (i == AnimalTable.SCORE)
                        {
                            String[] scores =
                            {
                                "*", "**", "***", "****", "*****"
                            };
                            column.setCellRenderer(new ComboBoxRenderer(scores));
                            column.setCellEditor(new ComboBoxEditor(scores));
                        }
                        else
                        {
                            column.setCellRenderer(new CheckboxRenderer());

                        }
                    }
                }
            }
        }

        // set column widths
        this.getColumnModel().getColumn(AnimalTable.NAME).setMaxWidth(60);
        this.getColumnModel().getColumn(AnimalTable.PHOTO).setMinWidth(AnimalTable.IMAGE_HEIGHT + AnimalTable.IMAGE_PADDING);
        this.getColumnModel().getColumn(AnimalTable.PHOTO).setMaxWidth(AnimalTable.IMAGE_HEIGHT + AnimalTable.IMAGE_PADDING);
        this.getColumnModel().getColumn(AnimalTable.DETAILS).setPreferredWidth(150);

        // do not allow the columns to move
        this.getTableHeader().setReorderingAllowed(false);

        // get rid of the header titles
        // NOTE: this needs to be the last thing done in the constructor.
        // Otherwise the applet will not initialise
        this.setTableHeader(null);
    }
}

class ColorRowsRenderer extends DefaultTableCellRenderer
{
    @Override
    public Component getTableCellRendererComponent(JTable table,
                                                   Object obj, boolean isSelected, boolean hasFocus, int row, int column)
    {
        super.getTableCellRendererComponent(table, obj, isSelected, hasFocus, row, column);

        if (row % 2 == 0)
        {
            this.setBackground(new Color(255, 255, 200));
        }
        else
        {
            this.setBackground(new Color(200, 200, 255));
        }

        return this;
    }
}

class NameRenderer extends ColorRowsRenderer
{
    @Override
    public Component getTableCellRendererComponent(JTable table, Object obj, boolean isSelected,
                                                   boolean hasFocus, int row, int column)
    {
        super.getTableCellRendererComponent(table, obj, isSelected, hasFocus, row, column);

        // set up a text area
        JLabel cell = new JLabel();

        // take the background of the super-class
        cell.setBackground(super.getBackground());
        cell.setOpaque(true);

        // load the text
        cell.setText(obj.toString());

        cell.setVerticalAlignment(JLabel.CENTER);
        return cell;
    }
}

class DetailsRenderer extends ColorRowsRenderer
{
    @Override
    public Component getTableCellRendererComponent(JTable table, Object obj, boolean isSelected,
                                                   boolean hasFocus, int row, int column)
    {
        super.getTableCellRendererComponent(table, obj, isSelected, hasFocus, row, column);

        // set up a text area
        JTextArea cell = new JTextArea();

        // set the text line wrap
        cell.setWrapStyleWord(true);
        cell.setLineWrap(true);

        // take the background of the super-class
        cell.setBackground(super.getBackground());

        // load the text
        cell.setText(obj.toString());

        return cell;
    }
}

class CheckboxRenderer extends ColorRowsRenderer
{
    @Override
    public Component getTableCellRendererComponent(JTable table, Object obj, boolean isSelected,
                                                   boolean hasFocus, int row, int column)
    {
        super.getTableCellRendererComponent(table, obj, isSelected, hasFocus, row, column);

        // set up a text area
        JCheckBox cell = new JCheckBox();

        if ("true".equals(obj.toString()))
        {
            cell.setSelected(true);
        }
        else
        {
            cell.setSelected(false);
        }
        // take the background of the super-class
        cell.setBackground(super.getBackground());

        cell.setHorizontalAlignment(JLabel.CENTER);

        return cell;
    }
}

class ImageIconRenderer extends ColorRowsRenderer
{
    @Override
    public Component getTableCellRendererComponent(JTable table,
                                                   Object obj, boolean isSelected, boolean hasFocus, int row, int column)
    {
        super.getTableCellRendererComponent(table, obj, isSelected, hasFocus, row, column);

        // set up a JLabel to hold the icon
        JLabel cell = new JLabel();

        // take the background of the super-class
        cell.setBackground(super.getBackground());
        cell.setOpaque(true);

        // load the image icon
        cell.setIcon((ImageIcon) obj);
        cell.setHorizontalAlignment(JLabel.CENTER);

        return cell;
    }
}

class ComboBoxRenderer extends ColorRowsRenderer
{
    String names[];

    public ComboBoxRenderer(String names[])
    {
        this.names = names;
    }

    @Override
    public Component getTableCellRendererComponent(JTable table, Object obj, boolean isSelected,
                                                   boolean hasFocus, int row, int column)
    {
        super.getTableCellRendererComponent(table, obj, isSelected, hasFocus, row, column);

        JComboBox cell = new JComboBox(this.names);

        cell.setSelectedItem(obj);

        // place the cell into a panel
        // otherwise the combobox will occupy the whole cell
        // this is only needed if the row height is greater than one
        // the grid size in the layout will need to be adjusted depending on the row height
        JPanel containerPanel = new JPanel();
        containerPanel.setLayout(new GridLayout(5, 1));
        containerPanel.add(new JLabel(""));
        containerPanel.add(new JLabel(""));
        containerPanel.add(cell);

        // take the background of the super-class
        containerPanel.setBackground(super.getBackground());
        containerPanel.setOpaque(true);

        return containerPanel;
    }
}

class ComboBoxEditor extends DefaultCellEditor
{
    public ComboBoxEditor(String[] items)
    {
        super(new JComboBox(items));
    }
}

class AnimalTableModel extends AbstractTableModel
{
    private String[] columnNames;
    private Object[][] data;

    public AnimalTableModel()
    {
        data = null;
        columnNames = null;
    }

    public AnimalTableModel(Object[][] data, String[] columnNames)
    {
        this.columnNames = columnNames;
        this.data = data;
    }

    public void setColumnNames(String[] columnNames)
    {
        this.columnNames = columnNames;
    }

    public void setData(Object[][] data)
    {
        this.data = data;
    }

    @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)
    {
        // the checkbox and combobox (score) need to be editable
        if ((col == AnimalTable.CHECKBOX) || (col == AnimalTable.SCORE))
        {
            return true;
        }
        return false;
    }

    @Override
    public void setValueAt(Object value, int row, int col)
    {
        data[row][col] = value;
        fireTableCellUpdated(row, col);
    }
}

Implement the above code using MVC.

Write an MVC programme that displays a table of names, each of which has a CheckBox and a rating (1..3) associated with it. When the user clicks a 'submit' button, the selected rows from the table should be displayed in a non-editiable second table. In the second table, the rating should be the single interger value that the user set in the first table.

Write an MVC programme that allows a user to add/modify/delete rows from a table that contains details of second hand cars that are for sale.

 
<div align="center"><a href="../versionC/index.html" title="DKIT Lecture notes homepage for Derek O&#39; Reilly, Dundalk Institute of Technology (DKIT), Dundalk, County Louth, Ireland. Copyright Derek O&#39; Reilly, DKIT." target="_parent" style='font-size:0;color:white;background-color:white'>&nbsp;</a></div>