The JTable component can be used to represent tabular data in our GUI applications. Let’s focus more on this Swing component.

Creating simple tables

To set up a table, we will need to specify the columns of the table, as well as the data that is to be displayed in the table. The names of the columns are represented as an array of type String, where each entry in the array is a new column. The data in the table is represented as a 2-D array, which is typically declared with the Object type since each column might have a different data type in it.

Every JTable has an underlying TableModel which is responsible for controlling the data contained within the table. It is possible to use the default TableModel to construct a JTable, however, it is preferred to implement a custom TableModel which extends the AbstractTableModel class. The reason for this is that it enables more control over the data contained in the table, and provides a natural extension to changing the table data later.

To extend the AbstractTableModel, we need to implement getRowCount, which returns the number of rows in the table, getColumnCount, which returns the number of columns in the table, and getValueAt, which defines how to retrieve a value from the table data. The code below shows an example of how these methods can be defined using an array of columns, and a 2-D array of data.

class MyTableModel extends AbstractTableModel {
    String[] columns = {"Employee Name" , "Job Title" , "Salary"};
    Object[][] data = {{"Bob" , "Programmer" , 19000} , {"Alice" , "Programmer" , 19000}};

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

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

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        return data[rowIndex][columnIndex];
    }

    @Override
    public String getColumnName(int columnIndex) {
        return columns[columnIndex];
    }

}

Once we have defined AbstractTableModel we can instantiate an instance of it, and use the instance to create a JTable. This table can then be added to the Swing JFrame as shown below.

public class JTableExample extends JFrame {
    public static void main(String[] args) {
        JTableExample table = new JTableExample();
        table.setVisible(true);
    }

    public JTableExample() {
        super("JTable Example");
        setSize(500, 500);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        getContentPane().setBackground(Color.white);

        TableModel tableModel = new MyTableModel();

        JTable table = new JTable(tableModel);

        JScrollPane sp = new JScrollPane(table);
        this.add(sp);


    }
}

When we add the JTable component, we typically choose to place it within a JScrollPane. The main reason to do this is to ensure that if there is more table data than what can be contained on the screen, the user can scroll to view the remaining data. This results in a simple table of data as shown below.

DefaultTableModel

There is a standard class that implements AbstractTableModel which is called DefaultTableModel. It is often a short and convenient way to define TableModel.

Object[] columns = new Object[] { "Name", "Race" };
Object[][] data = new Object[][] {
    {"Frodo", "Hobbit"},
    {"Legolas", "Elf"},
    {"Gimli", "Dwarf"}
};

DefaultTableModel model = new DefaultTableModel();
model.setColumnIdentifiers(columns);
for (Object[] row : data) {
    model.addRow(row);
}

If you need to update or insert table content you may use setValueAt or addRow method. Both of them generate tableChanged notifications.

Adjusting column widths

Sometimes the width of a column needs to be adjusted so that the data is fully visible. We can adjust the width of any column in our table using the setPreferredWidth method. This method takes one integer value as an argument, which is used to adjust the width of the column provided. In order to use this method, we must select a column from our JTable to adjust. We can do this by accessing the JTable column model using the getColumnModel method. Using the column model, we can invoke the getColumn method to get a column at a specified index. By default, the column is zero-indexed, and will match the column array we specified for the JTable. The code below shows how we can adjust the first column in our table to have a larger width.

public JTableExample() {
    super("JTable Example");
    setSize(500 , 500);
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    getContentPane().setBackground(Color.white);

    TableModel tableModel = new MyTableModel();

    JTable table = new JTable(tableModel);

    TableColumn column = table.getColumnModel().getColumn(0);
    column.setPreferredWidth(200);

    JScrollPane sp = new JScrollPane(table);
    this.add(sp);

}

Detecting content changes with event listeners

Often when we display data in JTable format, it is being taken from some dynamic data source, such as a database. When this data source updates, we want to be able to update our JTable as well. To do this, we can utilize event listeners in order to update the table on various events related to it.

The event listener TableModelListener can be used to detect changes to a JTable and take action based on the event. In order to add this event listener, we will need to implement it in our class definition. Once it is implemented, we can add it to the model of the JTable as shown below.

public class JTableExample extends JFrame {
    public static void main(String[] args) {
        JTableExample table = new JTableExample();
        table.setVisible(true);
    }

    public JTableExample() {
        super("JTable Example");
        setSize(500, 500);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        getContentPane().setBackground(Color.white);

        TableModel tableModel = new MyTableModel();

        JTable table = new JTable(tableModel);
        tableModel.addTableModelListener(new CustomListener()); //Adds the TableModelListener

        JScrollPane sp = new JScrollPane(table);
        this.add(sp);


    }

}
class CustomListener implements TableModelListener {
    @Override
    public void tableChanged(TableModelEvent e) {
        System.out.println("Table Updated!");
    }
}

The tableChanged method will trigger in any instance where the JTable is changed. There are a number of events that can be fired to trigger the tableChanged method. For instance, the fireTableCellUpdated method can be used to trigger the tableChanged method in the case that a specific cell in the table is updated. If we have updated a row in the table, we can use the fireTableRowsUpdated method. If the whole table has been updated, we would use the fireTableDataChanged method. Finally, in the case of a row being inserted or deleted, we would use fireTableRowsInserted, or fireTableRowsDeleted.

To be able to change data in our table, we can simply implement a new method in our AbstractTableModel to update the underlying data. Inside this method, we can fire the appropriate event depending on the changes being made. The example below shows how we can add a method called setValueAt to update data in the table. This method works by updating the underlying data array with the new value, and once this is completed, it fires the fireTableCellUpdated event, since it is used to change a single cell’s data.

class MyTableModel extends AbstractTableModel {
    String[] columns = {"Employee Name" , "Job Title" , "Salary"};
    Object[][] data = {{"Bob" , "Programmer" , 19000} , {"Alice" , "Programmer" , 19000}};

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

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

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        return data[rowIndex][columnIndex];
    }

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

When we trigger the setValueAt method, it will cause the tableChanged method to fire, since a table update event has taken place. Inside of this method, we can take any actions we want when data is updated. To see this in action, you can add a simple print statement to see that the method is firing. The example code below demonstrates how we can update a value. You will notice that the cell updates in the GUI and the message is printed to the console.

public class JTableExample extends JFrame {
    public static void main(String[] args) {
        JTableExample table = new JTableExample();
        table.setVisible(true);
    }

    public JTableExample() {
        super("JTable Example");
        setSize(500, 500);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        getContentPane().setBackground(Color.white);


        TableModel tableModel = new MyTableModel();

        JTable table = new JTable(tableModel);

        tableModel.addTableModelListener(new CustomListener()); //Adds the TableModelListener
        JScrollPane sp = new JScrollPane(table);
        this.add(sp);

        tableModel.setValueAt("James", 0, 0);

    }

}

class CustomListener implements TableModelListener {
    @Override
    public void tableChanged(TableModelEvent e) {
        System.out.println("Table Updated!");
    }
}

Sorting and filtering

We can enable sorting in our JTable using the setAutoCreateRowSorter method. Enabling this method just requires us to pass an argument of true into the method. Once this is done, the user will be able to click on any column name in the table, and it will be sorted by the selected column.

public JTableExample() {
    super("JTable Example");
    setSize(500, 500);
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    getContentPane().setBackground(Color.white);


    TableModel tableModel = new MyTableModel();

    JTable table = new JTable(tableModel);

    tableModel.addTableModelListener(new CustomListener()); //Adds the TableModelListener
    JScrollPane sp = new JScrollPane(table);
    this.add(sp);

    tableModel.setValueAt("James", 0, 0);

    table.setAutoCreateRowSorter(true);
}

To set up filters in our table, we can create a custom TableRowSorter object for our table. Using this sorter, we can create a filter based on a regular expression. We can add a text input for the filter, or just code one in statically. The example below shows how to use the setRowFilter method to add a new filter to our table.

public JTableExample() {
    super("JTable Example");
    setSize(500, 500);
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    getContentPane().setBackground(Color.white);


    TableModel tableModel = new MyTableModel();

    JTable table = new JTable(tableModel);

    tableModel.addTableModelListener(new CustomListener()); //Adds the TableModelListener
    JScrollPane sp = new JScrollPane(table);
    this.add(sp);

    tableModel.setValueAt("James", 0, 0);

    final TableRowSorter<TableModel> sorter = new TableRowSorter<>(tableModel);
    table.setRowSorter(sorter);
    sorter.setRowFilter(RowFilter.regexFilter("James"));

}

When this filter is added, the table will now display just a single value, which is where the name is equal to James.

Conclusion

In this topic we’ve covered the main points on creating tables in our GUI applications: how to create one, allow scrolling through the data, and adjust the column sizes to make all data visible, update and sort the data stored in the table. Try and practice using all these features of the JTable components by yourself!

Leave a Reply

Your email address will not be published.