|
The Java Specialists' Newsletter
Issue 106 2005-04-14
Category:
GUI
Java version: Sun JDKs 1.3.1_12, 1.4.2_05, 1.5.0_02 Multi-line cells in JTable in JDK 1.4+by Dr. Heinz M. Kabutz
Welcome to the 106th edition of The Java(tm) Specialists' Newsletter, sent to you from
Vienna, Austria. The great staff at Alcatel booked me into
a hotel that has free ADSL+WiFi access. This is still
rather unusual in Europe, I am told. I am in Vienna,
Austria, until the 22nd of April, so please let me know if
you are available for a cup of coffee and chat after work.
We had a great time last night at the Schweizerhaus, really
experiencing Austrian hospitality and atmosphere at its best.
On the first day of the courses, we noticed that the
projector was playing up, changing colours intermittently.
Changing the projector did not help, so we traced the
problem to my Dell D800 heavyweight notebook. I phoned DELL
Austria at about 15:00 on Tuesday, and by 14:00 on Wednesday
someone had arrived at Alcatel and had swopped out my
motherboard. No charge at all! Super service, thanks DELL!
Upcoming Java Specialist Master Courses:
"This course embodies my Java knowledge and experience gained publishing 180 advanced Java newsletters, teaching hundreds of seminars and writing hundreds of thousands of lines of Java code." Heinz Kabutz, The Java Specialists NewsletterParis, France, Feb 9-12 2010, €2500 - click to sign up. Düsseldorf, Germany (in German), Mar 2-5 2010, €2500 - click to sign up. San Jose CA, Mar 16-19 2010, $3500 - click to sign up. Oslo, Norway, Apr 13-16 2010, Kr 24500 - click to sign up. Chania, Crete, May 25-28 2010, €2500 - click to sign up.
In-house courses if these dates or locations do not suit you - click here for more information. Multi-line cells in JTable in JDK 1.4+
Three years ago, I played an
April Fools' joke on my readers, as is customary in
South Africa. However, with 109 countries
on the subscription list, I have stopped playing the fool,
since this is can cause confusion in cultures that do not
celebrate the 1st of April. In a follow-up
newsletter, I explained that the unsubscription fees
were nothing but a joke.
Coincidentally, almost three years ago to the day, I wrote
about Multi-line
cells in the JTable. I discussed how to wrap text
inside cells of a JTable. My solution only worked pre-JDK
1.4 , so I asked readers to send me a solution if they found
it.
Since I wrote that letter, several readers have sent
solutions that worked for JDK 1.4 and JDK 5.0: Scott
Sauyet, Dave Combs, Hitesh Patel, Debbie Utley, Wolf Siberski,
Dirk Hillbrecht, Andrew Cole and Sami. Thank you very much!
Most of the solutions consisted of adding one line to my code:
setSize(columnModel.getColumn(column).getWidth(), 100000);
This magically made the renderer work under JDK 1.4 and 1.5.
However, it did not address the other issues that I mentioned
in the original newsletter:
- It worked when only one column contained the TextAreaRenderer.
- I did not implement a TextAreaEditor.
Something else which bothered me with the proposed solutions
was that the cells with our renderers looked slightly
different to the cells with default renderers.
Here is a renderer followed by an editor that address all
these issues:
import javax.swing.*;
import javax.swing.table.*;
import java.awt.*;
import java.util.*;
public class TextAreaRenderer extends JTextArea
implements TableCellRenderer {
private final DefaultTableCellRenderer adaptee =
new DefaultTableCellRenderer();
/** map from table to map of rows to map of column heights */
private final Map cellSizes = new HashMap();
public TextAreaRenderer() {
setLineWrap(true);
setWrapStyleWord(true);
}
public Component getTableCellRendererComponent(//
JTable table, Object obj, boolean isSelected,
boolean hasFocus, int row, int column) {
// set the colours, etc. using the standard for that platform
adaptee.getTableCellRendererComponent(table, obj,
isSelected, hasFocus, row, column);
setForeground(adaptee.getForeground());
setBackground(adaptee.getBackground());
setBorder(adaptee.getBorder());
setFont(adaptee.getFont());
setText(adaptee.getText());
// This line was very important to get it working with JDK1.4
TableColumnModel columnModel = table.getColumnModel();
setSize(columnModel.getColumn(column).getWidth(), 100000);
int height_wanted = (int) getPreferredSize().getHeight();
addSize(table, row, column, height_wanted);
height_wanted = findTotalMaximumRowSize(table, row);
if (height_wanted != table.getRowHeight(row)) {
table.setRowHeight(row, height_wanted);
}
return this;
}
private void addSize(JTable table, int row, int column,
int height) {
Map rows = (Map) cellSizes.get(table);
if (rows == null) {
cellSizes.put(table, rows = new HashMap());
}
Map rowheights = (Map) rows.get(new Integer(row));
if (rowheights == null) {
rows.put(new Integer(row), rowheights = new HashMap());
}
rowheights.put(new Integer(column), new Integer(height));
}
/**
* Look through all columns and get the renderer. If it is
* also a TextAreaRenderer, we look at the maximum height in
* its hash table for this row.
*/
private int findTotalMaximumRowSize(JTable table, int row) {
int maximum_height = 0;
Enumeration columns = table.getColumnModel().getColumns();
while (columns.hasMoreElements()) {
TableColumn tc = (TableColumn) columns.nextElement();
TableCellRenderer cellRenderer = tc.getCellRenderer();
if (cellRenderer instanceof TextAreaRenderer) {
TextAreaRenderer tar = (TextAreaRenderer) cellRenderer;
maximum_height = Math.max(maximum_height,
tar.findMaximumRowSize(table, row));
}
}
return maximum_height;
}
private int findMaximumRowSize(JTable table, int row) {
Map rows = (Map) cellSizes.get(table);
if (rows == null) return 0;
Map rowheights = (Map) rows.get(new Integer(row));
if (rowheights == null) return 0;
int maximum_height = 0;
for (Iterator it = rowheights.entrySet().iterator();
it.hasNext();) {
Map.Entry entry = (Map.Entry) it.next();
int cellHeight = ((Integer) entry.getValue()).intValue();
maximum_height = Math.max(maximum_height, cellHeight);
}
return maximum_height;
}
}
I know that the mechanism for handling the cell renderer for
several columns is rather complex and inefficient. However,
it caters for the general case where one instance of the cell
renderer is used for several columns, or even for several
tables.
You will notice that I am borrowing the formatting of the
DefaultTableCellRenderer, so that the Text Area looks the
same as the other cells, for the particular platform and JVM
version.
As predicted in my newsletter three years ago, it was a
"Kinderspiel" (child's play) to write the TextAreaEditor. A
few points to note: I subclass the DefaultCellEditor, which
only caters for JTextField, JComboBox and JCheckBox as
editors. I therefore replace the editorComponent with my
own, and also set my own delegate. In order to make it
possible to edit more than the current cell allows, I put the
TextArea for editing into a JScrollPane, and I set its border
to null, to prevent it from showing the scrollbar
unnecessarily.
import javax.swing.*;
public class TextAreaEditor extends DefaultCellEditor {
public TextAreaEditor() {
super(new JTextField());
final JTextArea textArea = new JTextArea();
textArea.setWrapStyleWord(true);
textArea.setLineWrap(true);
JScrollPane scrollPane = new JScrollPane(textArea);
scrollPane.setBorder(null);
editorComponent = scrollPane;
delegate = new DefaultCellEditor.EditorDelegate() {
public void setValue(Object value) {
textArea.setText((value != null) ? value.toString() : "");
}
public Object getCellEditorValue() {
return textArea.getText();
}
};
}
}
I tested this on JDK 1.3.1 right up to JDK 1.5.0, under
the Windows, Motif and Metal Look and Feels, and it appears
the same as a normal JTable cell. Here is some test code:
import javax.swing.*;
import javax.swing.table.*;
public class TextAreaRendererTest extends JFrame {
private final JTable table = new JTable(10, 4);
public TextAreaRendererTest() {
super(System.getProperty("java.vm.version"));
// We use our cell renderer for columns 1, 2, 3
TableColumnModel cmodel = table.getColumnModel();
TextAreaRenderer textAreaRenderer = new TextAreaRenderer();
cmodel.getColumn(1).setCellRenderer(textAreaRenderer);
cmodel.getColumn(2).setCellRenderer(new TextAreaRenderer());
// I am demonstrating that you can have several renderers in
// one table, and they communicate with one another in
// deciding the row height.
cmodel.getColumn(3).setCellRenderer(textAreaRenderer);
TextAreaEditor textEditor = new TextAreaEditor();
cmodel.getColumn(1).setCellEditor(textEditor);
cmodel.getColumn(2).setCellEditor(textEditor);
cmodel.getColumn(3).setCellEditor(textEditor);
String test = "The lazy dog jumps over the quick brown fox";
for (int column = 0; column < 4; column++) {
table.setValueAt(test, 0, column);
table.setValueAt(test, 4, column);
}
test = test + test + test + test + test + test + test + test;
table.setValueAt(test, 4, 2);
getContentPane().add(new JScrollPane(table));
setSize(600, 600);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
}
public static void main(String[] args) {
new TextAreaRendererTest();
}
}
Well, it is late, and I need to hit the sack so that I can be
fresh tomorrow for the Design Patterns Course at Alcatel
Austria :)
Don't forget to let me know if you are currently in Austria!
Kind regards
Heinz
GUI Articles
Related Java Course
|