Skip to main content

JTable

23 replies [Last post]
sfshaza
Offline
Joined: 2004-06-03
Points: 0

We often receive feedback that the table tutorial (http://java.sun.com/docs/books/tutorial/uiswing/components/table.html) could use more fleshing out. Do you agree? If so, what specific subjects or examples would you add?

Sharon

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
aberrant
Offline
Joined: 2006-02-02
Points: 0

I found this : http://www.swingwiki.org/howto:column_spanning

It looks like other code I've seen to do this. It might be simpler for you because you don't have arbitrary spans.

Collin

java_ghost
Offline
Joined: 2009-08-25
Points: 0

I just tried your code Mr. Aberrant, about frozen columns. Basically, it has 2 separated table, hasn't it? I try to sort the table, but it seems the two tables not related each other, so just one table that being sort (the one that doesn't have frozen columns). Any idea so the table can be sortable?

jnthodge
Offline
Joined: 2008-05-17
Points: 0

Does anyone know how to do a multi-row record table like you see in Quicken or QuickBooks? They have things like date, check number, payee, and amount fields on top, and then on the 2nd row per record they have things like category and memo fields.

I need to do something like that where the column headers must align to the top row of each record, and the bottom row is just one long continuous field that spans something like 15 fields. It's a long comments field for a banking app.

Any thoughts? Seen any tutorials like that?

Thanks,

Jason

aberrant
Offline
Joined: 2006-02-02
Points: 0

**************************

After rereading you request I decided that this is not what you are looking for. I ran into the same issue when I needed to do "totals". I know it's possible but not easy.

**********************************

I'm not quite sure this is what you are looking for but you can take a look at this and see if it helps. It's just renderer based on a JPanel and a bunch of JLabels. I cheated some to write it quickly, but I'm sure you can clean it up if the idea is what you are looking for.

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.FlowLayout;
import java.util.Date;
import java.util.Random;

import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;

/**
* RowHighlightDemo:
*
* Demonstrates the use of a TableCellRenderer to color an entire row of a table
* based on some property of a single column.
*
* @author Collin Fagan
*/
public class RowHighlightDemo extends JPanel {

private static final long serialVersionUID = 1L;

private JTable table = new JTable();

/**
* sample data column names
*/
private String[] columnNames = { "ID", "Info" };

private class QuickInfo {
private Date date;
private int checkNumber;
private String payee;
private String category;
private String memo;

public QuickInfo(Date date, int checkNumber, String payee,
String category, String memo) {
super();
this.date = date;
this.checkNumber = checkNumber;
this.payee = payee;
this.category = category;
this.memo = memo;
}
}

private Random rnd = new Random();
/**
* sample table data
*/
private Object[][] data = {
{
rnd.nextInt(100),
new QuickInfo(new Date(rnd.nextLong()
% System.currentTimeMillis()), rnd.nextInt(1000),
"Jason Hodge", "paycheck", "for services rendered") },

{
rnd.nextInt(1000),
new QuickInfo(new Date(rnd.nextLong()
% System.currentTimeMillis()), rnd.nextInt(1000),
"Jason Hodge", "paycheck", "for services rendered") },

{
rnd.nextInt(1000),
new QuickInfo(new Date(rnd.nextLong()
% System.currentTimeMillis()), rnd.nextInt(1000),
"Jason Hodge", "paycheck", "for services rendered") },

{
rnd.nextInt(1000),
new QuickInfo(new Date(rnd.nextLong()
% System.currentTimeMillis()), rnd.nextInt(1000),
"Jason Hodge", "paycheck", "for services rendered") },
{
rnd.nextInt(1000),
new QuickInfo(new Date(rnd.nextLong()
% System.currentTimeMillis()), rnd.nextInt(1000),
"Jason Hodge", "paycheck", "for services rendered") },

};

/**
* Constructor builds the demo creating the table and associated renderer.
*/
public RowHighlightDemo() {
DefaultTableModel model = new DefaultTableModel(data, columnNames) {
@Override
public boolean isCellEditable(int row, int column) {

return false;
}
};

table.setModel(model);

table.setRowHeight(60);

setLayout(new BorderLayout());
JScrollPane scrollPane = new JScrollPane(table);
table.setFillsViewportHeight(true);

QuickInfoRenderer rowRenderer = new QuickInfoRenderer();
TableColumn column = table.getColumnModel().getColumn(1);
column.setCellRenderer(rowRenderer);

add(scrollPane, BorderLayout.CENTER);
}

private class QuickInfoRenderer extends JPanel implements TableCellRenderer {

private JLabel dateLabel = new JLabel();
private JLabel checkNumberLabel = new JLabel();
private JLabel payeeLabel = new JLabel();
private JLabel categoryLabel = new JLabel();
private JLabel memoLabel = new JLabel();

public QuickInfoRenderer() {
setLayout(new FlowLayout(FlowLayout.LEFT));
add(new JLabel("Date:"));
add(dateLabel);
add(new JLabel("Check Number:"));
add(checkNumberLabel);
add(new JLabel("Payee:"));
add(payeeLabel);
add(new JLabel("Category:"));
add(categoryLabel);
add(new JLabel("Memo:"));
add(memoLabel);
}

@Override
public Component getTableCellRendererComponent(JTable table,
Object value, boolean isSelected, boolean hasFocus, int row,
int column) {

if (!isSelected) {
setBackground(table.getBackground());
} else {
setBackground(table.getSelectionBackground());
}

QuickInfo qi = (QuickInfo) value;
dateLabel.setText(qi.date.toString());
checkNumberLabel.setText(String.valueOf(qi.checkNumber));
payeeLabel.setText(qi.payee);
categoryLabel.setText(qi.payee);
memoLabel.setText(qi.memo);
return this;
}

}

/**
* Start the demo on the Event Dispatch Thread.
*
* @param args
* the command line arguments
*/
public static void main(String[] args) {

// turn bold fonts off in metal
UIManager.put("swing.boldMetal", Boolean.FALSE);

SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame demoFrame = new JFrame();
demoFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
demoFrame.setContentPane(new RowHighlightDemo());
demoFrame.pack();
demoFrame.setVisible(true);
}
});
}
}

Message was edited by: aberrant

aberrant
Offline
Joined: 2006-02-02
Points: 0

I did some research on the web and built a Row Labels / Frozen Columns demo. I'm not 100% sure this best way to accomplish this effect but it does seem like the most common way to approach it.

http://wiki.java.net/bin/view/Projects/JTableFrozenColumns

It seems to me that this kind of thing begs to be wrapped into a reusable component.

Collin

sfshaza
Offline
Joined: 2004-06-03
Points: 0

You are so darned prolific, Collin! :-)

That's a cool demo - it allows you to "freeze" the first table column (in this case) while you horizontally scroll the rest of the columns. I can see lots of applications for this.

For those of you not yet using JDK 6, you can comment out the two lines that invoke "setFillsViewportHeight" and it will work fine. If you wonder why that method is important, see this blog entry: http://blogs.sun.com/thejavatutorials/entry/dropping_data_onto_an_empty

Sharon

aberrant
Offline
Joined: 2006-02-02
Points: 0

I have an example of multi line rows in a JTable.

http://wiki.java.net/bin/view/Projects/JTableMultiLineRows

aberrant
Offline
Joined: 2006-02-02
Points: 0
jnthodge
Offline
Joined: 2008-05-17
Points: 0

It seems you could do this much easier by just overriding the prepareRenderer(renderer, row, column) method and possibly the prepareEditor(editor, row, column) method. Cast the renderer to the type that it is (or just JTextComponent or something) and set it's background to some color. You can use the row/column numbers to go into the model and ask for the value, being sure to translate the column/row(in java 6.0+) numbers using the convert[Row/Column]IndexTo[Model/View](int [col/row]Number). Wouldn't this be easier?

aberrant
Offline
Joined: 2006-02-02
Points: 0

I investigated your ideas by attempting to color even and odd rows differently in a table. It seems though that modifying the renderer based on the value of a column in prepareRenderer() sets up an infinite loop. Do you have a working example of this technique? (DefaultTableCellRenderer extends JLabel)

Also I'm not sure I see a big difference between.

int colIndex = table.getColumnModel().getColumnIndex(sportColumn);

and

int colIndex = table.convertColumnIndexToView(SPORTS_COLUMN_INDEX);

Both cases involve looping through each column, one just takes the name instead of the index. Is there something I'm missing.

jnthodge
Offline
Joined: 2008-05-17
Points: 0

Aberrant,

Here is an example that should run on JDK5+. Sorry for the bad indention (is there a code tag or something we can use?)

Cheers,

Jason

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.TableCellRenderer;

public class RowColoringDemoApp extends JFrame {

private JPanel jContentPane = null;
private JScrollPane jScrollPane = null;
private JTable jTable = null;

public RowColoringDemoApp() {
super();
initialize();
}

private void initialize() {
this.setSize(300, 200);
this.setContentPane(getJContentPane());
this.setTitle("JFrame");
}

private JPanel getJContentPane() {
if (jContentPane == null) {
jContentPane = new JPanel();
jContentPane.setLayout(new BorderLayout());
jContentPane.add(getJScrollPane(), BorderLayout.CENTER);
}
return jContentPane;
}

private JScrollPane getJScrollPane() {
if (jScrollPane == null) {
jScrollPane = new JScrollPane();
jScrollPane.setViewportView(getJTable());
}
return jScrollPane;
}

private JTable getJTable() {
if (jTable == null) {
String columnNames[] = { "First", "Last", "Age" };
Object dataValues[][] =
{ { "Jason", "Hodge", 37 },
{ "Aberrant", "Behavior", 35 },
{ "Games", "Jostling", 55 }
};

// Create a new table instance
jTable = new JTable(dataValues, columnNames) {
Color red = new Color(255, 150, 150);
@Override
public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
Component rend = super.prepareRenderer(renderer, row, column);
// convertColumnIndexToModel() is needed if using the JTable convenience method for getValueAt()
// otherwise, column re-arrangement can break because JTable assumes column 2 to be the View's
// column, not the model's column.
// In JDK6+, Rows have the same dilemma since sorting was built into the table implementation.
Integer age = (Integer) getValueAt(row, this.convertColumnIndexToModel(2));
if (age > 35) { // Detect over the hill programmers :)
rend.setBackground(red);
}
else { // NOTE: you have to set the color every time since renderers are re-used
if (this.getSelectedRow() == row) {
rend.setBackground(this.getSelectionBackground());
}
else {
rend.setBackground(this.getBackground());
}
}
return rend;
}
};
}
return jTable;
}

public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new RowColoringDemoApp().setVisible(true);
}
});
}
}

jnthodge
Offline
Joined: 2008-05-17
Points: 0

Aberrant,

I forgot to note: In the example above, at runtime, rearrange the Age column to be before the others by dragging the column header and watch the code break. Then, swap out the convertColumnIndexToModel() with convertColumnIndexToView(), and it will fix this bug.

aberrant
Offline
Joined: 2006-02-02
Points: 0

Thanks,

I found my problem that let to the infinite loop, silly mistake. This is a very interesting way of approaching this. I like it because you don't have to make your own renderer. I don't like it because it means you have to inherit from table everyplace you want to use it. Also once you start getting into custom renderers per column you can't use this technique. It IS less code, I can't argue with that.

By the way for the record replacing

Integer age = (Integer) getValueAt(row, this.convertColumnIndexToView(2));

with

Integer age = (Integer) getValueAt(row, this.getColumnModel().getColumnIndex("Age"));

works even if you rearrange the columns in your demo.

Thanks for the feedback. I'll think about it.

I just noticed your data set ...

I'm 30 so I guess I'm not over the hill yet ;)

http://aberrantcode.blogspot.com/

Message was edited by: aberrant

jnthodge
Offline
Joined: 2008-05-17
Points: 0

Aberrant,

About the lines: Integer age = (Integer) getValueAt(row, this.getColumnModel().getColumnIndex("Age"));

Yes, I agree, your way does not use the convenience method on JTable, so going directly to the column model, yours will always work. I'm kind of a fan of the convenience methods, myself, since I've found little reasons here and there that they eliminate certain inconveniences (such as when you change a model but must also fire a "changed" event to let the JTable view know you did it.)

Also, I have a table with about 4 custom renderers. They all ultimately extend some component that has a background color. Therefore, I used the prepareRenderer() method to color the rows so that I didn't have to code it into each renderer. You might have to type-check and cast here or there, but it cleans up the code in complex situations by putting all the "row coloring" logic in one place. :)

Just my 2c.

Jason

aberrant
Offline
Joined: 2006-02-02
Points: 0

Jason you have a very good point. I added http://wiki.java.net/bin/view/Projects/AlternateRowshadingDemo that uses prepareRenderer() for color. I'm still undecided as to if it needs to replace JTableColorEntireRowDemo or just supplement it.

Thanks for your feedback,

Collin

lawnworm
Offline
Joined: 2008-10-14
Points: 0

Dont know if you are interested but I have pretty much rewritten the DefaultTableModel class to accept row label and override the getColumnType in the AbstractTableListener. All the same constructors (modified for columnTypes) are there and all the power of the Default Table Model

callesollander
Offline
Joined: 2004-11-10
Points: 0

I'd love to see a good tutorial on how to use tab in a table.
Preferably it would show how to move only between the editable fields in the table and then how to tab out of the table and on to the next component outside of the table.

When your at it maybe show how to validate when tabbing out of the fields.
I'm struggling with this at the moment and find it hard to understand being not very good at swing.

Regards Calle
PS If anyone knows of a good tutorial that explains this please tell me :-)

mamonahan
Offline
Joined: 2008-07-23
Points: 0

One example I would love to see is "How to Remove All of the Selected Rows from a JTable". This seems like a simple task, and it turns out to be, but there are some pitfalls people could run into when trying to do this.

The first thing I thought of doing when I went to solve this problem was to get the List of selected indexes, iterate over them, and remove the rows with those indexes from the JTable's TableModel. This seems like it should work, if you iterate over the indexes in a way that ensures that you remove higher indexed rows first (so that removing a row doesn't shift the index of other rows you will remove). Unfortunately this can't be ensured for sortable tables, because one must convert from the "view index" used by JTable to the "model index" used by the table's TableModel. (Also the JTable.getSelectedRows() method documentation doesn't actually guarantee an order for the indexes it returns, it just seems to usually return them in the order of least to greatest).

The result of all of this is that one must determine how many rows are selected, and then use the JTable.getSelectedRow() method to get each row's view index individually, covert the view index to a model index, and remove this row from the model. Woah, seems a little crazy, but it's not:

//setting up the table
DefaultTableModel tableModel = new DefaultTableModel(rowCount, columnCount);
JTable table = new JTable(tableModel);

...

//removing all selected rows
int numSelected = table.getSelectedRowCount();
for (int i=0;i int viewIndex = table.getSelectedRow();
int modelIndex = table.convertRowIndexToModel(viewIndex);
tableModel.removeRow(modelIndex);
}

I hope this is constructive information. I'm new to java.net, but I've been using the java tutorial forever, and it's an incredible resource. Please let me know if I should throw together a demo or something for this. Thank you.

Matthew

aberrant
Offline
Joined: 2006-02-02
Points: 0

You are absolutely correct. This is very good information. Thank you. If you want to put a demo together go right ahead. If not then I'll see if I have time to build one.

Thanks,

Collin

Message was edited by: aberrant

aberrant
Offline
Joined: 2006-02-02
Points: 0

Every time I look at a code base involving tables I see someone trying to do the following:

1. Add and remove columns from the table.
2. Color/Highlight an entire row based on the the contents of one column.
3. Row Labels.
4. Use a checkbox for selection of rows.
5. Multi-Line Headers
6. Multi-Line Rows
7. Grouped/Custom Headers

1. Is all about columns and column models. It seems like something one could write a tutorial for without much problem.

2. Is all about renderers. There is already some renderer tutorial information. Maybe this could be added renderer section.

3. There are some examples of row labels on the net. I'm not sure what the correct approach actually should be.

4. I'm not sure this is a good idea. I think the desire stems from the fact that control+clicking is not oblivious to all users as a mechanism for multi-selection.

5. I've seen this question asked a lot. Again there are a few differing approaches on the web and I'm not sure what the "correct" answer is.

6. Another renderer example. Would not be too hard to implement.

7. This seems really advanced, and yet I've seen it asked for time and time again.

Some of these are API issues, but as with the original SwingWorker and TableSorter, they might need to be explained in the tutorial first before making it into the JDK.

Collin

sfshaza
Offline
Joined: 2004-06-03
Points: 0

That's a great list, Collin! Maybe after JavaOne we can ask one of the Swing engineers to address #3, #5 and #7. We could use a lot more table examples in the tutorial. (Of course there's always the problem that whenever you add a lot more examples, you're adding a lot more work to maintain and update them.)

If anyone has good examples (table or otherwise), please feel free to post them on our wiki and link to them from here.

Sharon

aberrant
Offline
Joined: 2006-02-02
Points: 0

I'll make #1 my next project. I hope to have something soon.

aberrant
Offline
Joined: 2006-02-02
Points: 0