CHAPTER 1 – The JTable

This chapter introduces and explores the JTable. In this tutorial, we will use the example of a simple customer database.

Chapter Topics:

·  The JTable Swing component.

·  Using the JScrollPane as a container for the JTable

·  The setValueAt() and getValueAt() methods of JTable.

·  Using the DefaultTableModel

·  Adding, Inserting & Removing Rows

·  Using TableModelListener for a JTable's event.

·  Coloring and perfecting your table

A Swing Component: JTable

A JTable is exactly as the name suggests – it is a table. It is probably the most versatile of the Swing components and is useful in so many different ways.

You might ask, why use a JTable? Obviously, it allows you to view data in a tabular fashion. This can be useful in a number of scenarios where listing data is necessary. It also can respond to events which happen with respect to the table (for example, changing the value of a cell).

The JTable class is so huge, that a whole book could be written on it alone. If you’re ever unsure of how to do something with the JTable, the best place is to look at is the API.

So, what are the components of a table?

·  Cell – each cell displays an item of data

·  Rows – a continual horizontal series of cells. A new row usually corresponds to a new set of data

·  Columns – all cells in a particular column correspond to one type of data

·  Table header – displays the column headers

JTable Class Hierarchy

[image goes here]

Application with a JTable

So without further ado, let’s write a program which will display a table and some data in it.

import java.awt.*;

import javax.swing.*;

public class Table extends JFrame {

private JTable table;

public Table() {

getContentPane().setLayout( new FlowLayout() );

String[] columnNames = new String[]{"Column 1","Column 2","Column 3"};

Object[][] rowData = {

{"1", "2", "3"},

{"4", "5", "6"},

{"7", "8", "9"},

{"10", "11", "12"},

{"13", "14", "15"},

{"16", "17", "18"}

};

table = new JTable(rowData, columnNames);

getContentPane().add(table);

setVisible( true );

setSize( 300, 150 );

}

public static void main ( String[] args ) {

Table frm = new Table();

frm.setVisible(true);

frm.setSize( 300, 150 );

}

}

If you run this program, you will see a table with 3 columns and 5 rows. The values inside of the cells are determined by the 2-dimensional array, rowData. Note that if in each row, if the number of values in each row is not equal to the number of columns, an out of bounds exception will be thrown. For example, the following will throw an error:

….

Object[][] rowData = {

{"1", "2"}

};

table = new JTable(rowData, columnNames);

Similarly, an error would be thrown if row data consisted of “1”, “2”, “3”, “4”.

If you think this table sucks, you’re absolutely right. Don’t worry, it’s about to get a lot better.

Using a Scroll Pane

You will notice that if you have ten rows instead of five, the table will just continue off the frame, and all the values will not be visible. If you are using absolute positioning, and manually set the bounds (position & size) of the table, then the table will cut off if there are too many rows. We can solve this problem by putting the JTable inside a scrollable container, namely the JScrollPane.

Let’s look at the following code:

import java.awt.*;

import javax.swing.*;

public class Table extends JFrame {

private JTable table;

public Table() {

getContentPane().setLayout( new FlowLayout() );

Object[] columnNames = new Object[]{"Column 1","Column 2","Column 3"};

Object[][] rowData = {

{"1", "2", "3"},

{"4", "5", "6"},

{"7", "8", "9"},

{"10", "11", "12"},

{"13", "14", "15"},

{"16", "17", "18"},

{"19", "20", "21"},

{"22", "23", "24"}

};

table = new JTable(rowData, columnNames);

JScrollPane scrollPane = new JScrollPane(table);

table.setPreferredScrollableViewportSize(new Dimension(280, 100));

getContentPane().add(scrollPane);

setVisible( true );

setSize( 320, 350 );

}

public static void main ( String[] args ) {

Table frm = new Table();

frm.setVisible(true);

frm.setSize( 320, 175 );

}

}

Note that the scrollbar does not need to be declared as a global variable, since typically, the scrollbar does not need to be accessed after it has been declared.

The constructor for the scroll pane is

JScrollPane( Component componentToView )

We can easily define how much of the componentToView is visible at a time by using the setPreferredScrollableViewportSize(Dimension dimension) method.

Changing & Getting Cell Values

Now to add some more functionality to our program…what good is a table if the values cannot be changed?

The methods which do this are fairly simple.

To get a value, we use the method

getValueAt(int row, int column)

To set a value, we use the method

setValueAt(String valueToSet, int row, int column)

It is important to remember that the top left cell is at row number 0 and column number 0.

This next example will illustrate a few more important methods of the JTable.

Another Example

import java.awt.*;

import java.awt.event.*;

import javax.swing.*;

public class Table extends JFrame implements ActionListener {

private JTable table;

private JButton getValue, setValue;

public Table() {

getContentPane().setLayout( new FlowLayout() );

Object[] columnNames = new Object[]{"Column 1","Column 2","Column 3"};

Object[][] rowData = {

{"1", "2", "3"},

{"4", "5", "6"},

{"7", "8", "9"},

{"10", "11", "12"},

{"13", "14", "15"},

{"16", "17", "18"},

{"19", "20", "21"},

{"22", "23", "24"}

};

table = new JTable(rowData, columnNames);

JScrollPane scrollPane = new JScrollPane(table);

table.setPreferredScrollableViewportSize(new Dimension(280, 100));

getContentPane().add(scrollPane);

getValue = new JButton( "Get Value" );

getValue.addActionListener( this );

getContentPane().add( getValue );

setValue = new JButton( "Set Value" );

setValue.addActionListener( this );

getContentPane().add( setValue );

setVisible( true );

setSize( 320, 200 );

}

public void actionPerformed( ActionEvent evt ) {

int row = table.getSelectedRow();

int column = table.getSelectedColumn();

if ( evt.getSource() == getValue ) {

String value = String.valueOf( table.getValueAt(row,column) );

JOptionPane.showMessageDialog( this,

"Value at (" + row + "," + column + ") is " + "\'" + value + "\'");

}

else if ( evt.getSource() == setValue ) {

String valueToSet = JOptionPane.showInputDialog( this, "Value to set?" );

table.setValueAt( valueToSet, row, column );

}

}

public static void main ( String[] args ) {

Table frm = new Table();

frm.setVisible(true);

frm.setSize( 320, 200 );

}

}

OK…let’s walk through this.

So what does this program do? The user first selects a cell. They can then press either the ‘Get Value’ button or the ‘Set Value’ button. Pressing the ‘Get Value’ button displays the contents of the cell which is selected. Pressing the ‘Set Value’ button prompts the user to enter a value, which will then replace the contents of the selected cell with what the user just entered.

There are a few important method calls in the actionPerformed method.

Firstly, we are obtaining the row & column which is selected by the user, and assigning these values to the integers ‘row’ & ‘column’, respectively. It is important to note a few things here. Remember that the first row starts at zero and the first column starts at zero. As well, it is important to note that if the user has not selected any cell, the row and column returned is -1.

Keep in mind that the contents of table cells are objects, not strings or even integers for that matter. So that’s why we use the method valueOf from the String class when getting the values of cells.

Table Models

JTables use table models to determine how the data is handled. There are many limitations to the functionality of the JTables which were implemented in the examples presented. To get past these limitations, we will now explore the use of other table models, especially the default table model.

To create your own table model (which we will not explore in detail), you must extend an abstract table model. The biggest advantage to creating your own table model is that you can customize a table however you like. For instance, you could program a table model to interact with a database, or allow table cells to contain other Swing components such as radio buttons. However, the biggest down side to creating your own table model is that it can often become complex and time consuming. Luckily, there is already an excellent table model in the javax.swing.table package. This is the one we will be using.

You will find that the constructor for the DefaultTableModel is much easier to use than that of the JTable. Although there are a few of them, the following is the best, since typically, when loading a program, we know the names of the columns but have limited or no row data.

DefaultTableModel(Object[]columnNames, introwCount)

If you already know the row data, then the following constructor can be used:

DefaultTableModel(Object[][]rowData, Object[]columnNames)

Implementing the DefaultTableModel

So let’s write a program which does the exact same thing as the previous program, but using the default table model to do this.

import java.awt.*;

import java.awt.event.*;

import javax.swing.*;

import javax.swing.table.*;

public class Table extends JFrame implements ActionListener {

private JTable table;

private JButton getValue, setValue;

public Table() {

getContentPane().setLayout( new FlowLayout() );

DefaultTableModel tableModel;

Object[] columnNames = new Object[]{"Column 1","Column 2","Column 3"};

Object[][] rowData = {

{"1", "2", "3"},

{"4", "5", "6"},

{"7", "8", "9"},

{"10", "11", "12"},

{"13", "14", "15"},

{"16", "17", "18"},

{"19", "20", "21"},

{"22", "23", "24"}

};

tableModel = new DefaultTableModel(rowData, columnNames);

table = new JTable(tableModel);

JScrollPane scrollPane = new JScrollPane(table);

It is firstly important to remember that we must import the javax.swing.table.* package so we can make use of the DefaultTableModel. The second important thing to note is that the constructor for the JTable is changed to the following format:

JTable(TableModelmodel)

We could also have used table.setModel(TableModel model);

Other than that, it is fairly straightforward. The constructor for the DefaultTableModel is similar to that of the JTable in this example. In the next example, we will use a more practical constructor.

Now, you’re probably asking why would I use a DefaultTableModel? Well, you’re about to see why.

Adding, Inserting, & Removing Rows

The DefaultTableModel makes the addition, insertion and removal of rows so much easier than if we used the regular JTable built in model.

It is important to note that although we can call the setValueAt & getValueAt methods from either the JTable class or the DefaultTableModel class, the add, insert, and remove methods can only be called exclusively from the instance of the model.

Here are the methods which add, insert and remove a row, respectively:

addRow(Object[]rowData)

insertRow(introwNum, Object[]rowData)

removeRow(introwNum)

The addRow method just appends a row with the values rowData to the end of the table.

The insertRow method inserts a row, where the inserted row becomes the row number rowNum

The removeRow method remove a row, where the removed row is row number rowNum

Remember once again that row numbers start at zero.

Using the TableModelListener

To automatically detect changes that are made in the table, we can use a listener. Just like we can detect when the user has clicked a button using an ActionListener, we can use a TableModelListener to detect changes made in the table.

In order to do this, we must first import the library javax.swing.event.* to handle the events of the JTable Swing component. Then we must implement the listener, like we did with the ActionListener. The following example shows us how to use the listener.

import java.awt.*;

import java.awt.event.*;

import javax.swing.*;

import javax.swing.table.*;

import javax.swing.event.*;

public class Table extends JFrame implements ActionListener, TableModelListener {

private JTable table;

private JButton getValue;

public Table() {

getContentPane().setLayout( new FlowLayout() );

DefaultTableModel tableModel;

Object[] columnNames = new Object[]{"Column 1","Column 2","Column 3"};

Object[][] rowData = {

{"1", "2", "3"},

{"4", "5", "6"},

{"7", "8", "9"},

{"10", "11", "12"},

{"13", "14", "15"},

{"16", "17", "18"},

{"19", "20", "21"},

{"22", "23", "24"}

};

tableModel = new DefaultTableModel(rowData, columnNames);

tableModel.addTableModelListener( this );

table = new JTable(tableModel);

JScrollPane scrollPane = new JScrollPane(table);

table.setPreferredScrollableViewportSize(new Dimension(280, 100));

getContentPane().add(scrollPane);

getValue = new JButton( "Get Value" );

getValue.addActionListener( this );

getContentPane().add( getValue );

setVisible( true );

setSize( 320, 200 );

}

public void tableChanged( TableModelEvent e ) {

DefaultTableModel model = (DefaultTableModel)e.getSource();

int row = e.getFirstRow();

int column = e.getColumn();

String cellValue = String.valueOf( table.getValueAt(row, column) );

JOptionPane.showMessageDialog(this,

"Value at (" + row + "," + column + ") changed to

" + "\'" + cellValue + "\'");

}

public void actionPerformed( ActionEvent evt ) {

int row = table.getSelectedRow();

int column = table.getSelectedColumn();

if ( evt.getSource() == getValue ) {

String value = String.valueOf( table.getValueAt(row,column) );

JOptionPane.showMessageDialog( this,

"Value at (" + row + "," + column + ") is " + "\'" + value + "\'");

}

}

public static void main ( String[] args ) {

Table frm = new Table();

frm.setVisible(true);

frm.setSize( 320, 200 );

}

}

In this program, the Set Value button has been removed. Instead, when the user double clicks a cell and changes the value, and either presses Enter of deselects the cell, a message comes up saying what has been changed.

Does adding a row fire the tableChanged method?

Yes, in fact, adding, inserting and removing rows fires the tableChanged method.

Adding, inserting and removing rows fires the tableChanged method.

Suppose you do not want code in the tableChanged method to be fired upon these actions. Simply use the following code in the tableChanged method:

if (e.getType() != TableModelEvent.INSERT) {

//code to execute goes here…

}

The field constant INSERT involves the addition or insertion of a row. The INSERT can be replaced by DELETE for removal of rows.

Coloring & Perfecting your table