Home | Contact Us | FAQ | Search & Site Map | Link to Us
Sign In | Join | Other 45 Sites in Network
HomeAnnouncementsWhite Papers
Discussion GroupsFirst AidDatabasesJavaBeansGUIJava 3DVirtual MachineCORBASecurityToolsGeneral
Java DirectoryOpen Source ProjectsSample Book ChaptersUser GroupsWeb Resources
Related Topics
Databases.NETMore Topics ...

Java Forum / GUI / May 2005

Tip: Looking for answers? Try searching our database.

HOWTO: Multiline (word wrap) JTable cells with automatic height - SIMPLE

Thread view: 
wasteheap@email.com - 20 May 2005 17:09 GMT
After trying various methods, I found one that is pretty simple and I'd
like to share it for any other poor souls who have to do the same
thing.  Sorry if my solution is not general enough.  I am using Java
5.0.

SHORTCOMINGS OF OTHER SOLUTIONS

http://www.codeguru.com/java/articles/305.shtml
Wernitz' code almost worked for me.  First I fixed the getFont() = null
problem by using UIManager.getDefaults().getFont("Table.font").  Then
when I added a row to my table the "row added" event fired and then it
tried to use getRowHeight(void), which is undefined in Wernitz' code
because it assumes all rows are the same height, which they are not.
The result is that the cells painted correctly but the entire table
height was based (I suppose) on the default row height and ended up
clipping most of the bottom rows.  His MultiLineTable.java code was so
confusing for me that I just didn't want to have to override yet
another method in another class to resolve the calls to getRowHeight().

http://archive.devx.com/java/free/articles/Kabutz07/Kabutz07-1.asp
Kabutz' solution really became my solution, although with a slight
modification (of course!)

MY SOLUTION

Below is the *only* class I needed to create.  I keep the original
author's comments for credit, but note that I changed it a bit by (1)
cleaning up some empty methods and unnecessary imports and deleting the
"implements Serializable", which JTable already implements, (2) by
modifying the constructor to get the JTextArea to behave as I wanted
(only break at newlines) and (3) modifying
getTableCellRendererComponent() by inserting a variation of Kabutz'
solution (after "setValue(value);").

The magic is to simply call
setDefaultRenderer(Object.class, new MultiLineCellRenderer());
against the JTable or JTable descendant. I called it in the
constructor.  I don't know what Kabutz was talking about coordinating
cell renderers for various columns.  The default renderer applies to
all columns, and I figured a default renderer that handled Objects (and
Object descendants) should be general enough to fit most people's
needs.  I don't know what happens with primitive types.
Setting the preferredHeight in the cell renderer is so much easier than
all the math that Wernitz was doing, and it WORKED for me!

/**
* MultiLineCellRenderer.java
*
* Created: Mon May 17 09:41:53 1999
*
* @author Thomas Wernitz, Da Vinci Communications Ltd
<thomas_wernitz@clear.net.nz>
*
* credit to Zafir Anjum for JTableEx and thanks to SUN for their
source code ;)
*/

package multiLineTable;

import javax.swing.*;
import javax.swing.table.TableCellRenderer;
import javax.swing.border.*;

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

public class MultiLineCellRenderer extends JTextArea implements
TableCellRenderer {
 protected static Border noFocusBorder;

 private Color unselectedForeground;
 private Color unselectedBackground;

 public MultiLineCellRenderer() {
   super();
   noFocusBorder = new EmptyBorder(1, 2, 1, 2);
   setLineWrap(false);
   setWrapStyleWord(false);
   setOpaque(true);
   setBorder(noFocusBorder);
 }

 public void setForeground(Color c) {
   super.setForeground(c);
   unselectedForeground = c;
 }

 public void setBackground(Color c) {
   super.setBackground(c);
   unselectedBackground = c;
 }

 public void updateUI() {
   super.updateUI();
   setForeground(null);
   setBackground(null);
 }

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

   if (isSelected) {
     super.setForeground(table.getSelectionForeground());
     super.setBackground(table.getSelectionBackground());
   }
   else {
     super.setForeground((unselectedForeground != null) ?
unselectedForeground
             : table.getForeground());
     super.setBackground((unselectedBackground != null) ?
unselectedBackground
             : table.getBackground());
   }

   setFont(table.getFont());

   if (hasFocus) {
     setBorder( UIManager.getBorder("Table.focusCellHighlightBorder")
);
     if (table.isCellEditable(row, column)) {
    super.setForeground( UIManager.getColor("Table.focusCellForeground")
);
    super.setBackground( UIManager.getColor("Table.focusCellBackground")
);
     }
   } else {
     setBorder(noFocusBorder);
   }

   setValue(value);
    int rowHeight = (int)getPreferredSize().getHeight();
    if (table.getRowHeight() != rowHeight) table.setRowHeight(rowHeight);

   return this;
 }

 protected void setValue(Object value) {
   setText((value == null) ? "" : value.toString());
 }
}
Christian Kaufhold - 21 May 2005 12:15 GMT

>  public void updateUI() {
>    super.updateUI();
[quoted text clipped - 19 lines]
>                          : table.getBackground());
>    }

Cargo-cult code.


>        int rowHeight = (int)getPreferredSize().getHeight();
>        if (table.getRowHeight() != rowHeight) table.setRowHeight(rowHeight);

Have you actually tried this? With contents of different length?

Christian
wasteheap@email.com - 23 May 2005 19:30 GMT
> Cargo-cult code.

cargo cult programming: n.

   A style of (incompetent) programming dominated by ritual inclusion
of code or program structures that serve no real purpose. A cargo cult
programmer will usually explain the extra code as a way of working
around some bug encountered in the past, but usually neither the bug
nor the reason the code apparently avoided the bug was ever fully
understood.

Dr. Heinz Kabutz:

"On first glimpse the program seemed to work correctly-except that my
poor CPU was running at 100 percent. The problem was that when I set
the row height I invalidated the table, which caused the program to
call getTableCellRendererComponent() in order to render all the cells
again. This, in turn, set the row height, which invalidated the table
again. In order to put a stop to this cycle of invalidation, I needed
to check whether the row was already the correct height before setting
it."

Are you saying this is not/no longer the case?

> Have you actually tried this? With contents of different length?

Yes, in fact my interface has two tables in side-by-side scoll panes.
This allows me to synchronize row selections between two tables with
rows of varying heights.  The application generates the table data,
which varies in length.
wasteheap@email.com - 23 May 2005 23:39 GMT
Small correction:

int rowHeight = (int)getPreferredSize().getHeight();
if (table.getRowHeight() != rowHeight) table.setRowHeight(rowHeight);

should be

int rowHeight = (int)getPreferredSize().getHeight();
if (table.getRowHeight(row) != rowHeight) table.setRowHeight(row,
rowHeight);

Note that getRowHeight/setRowHeight is overloaded and I mistakenly used
the wrong one the first time (should be row-specific).
Christian Kaufhold - 24 May 2005 13:46 GMT
> Small correction:
>
[quoted text clipped - 9 lines]
> Note that getRowHeight/setRowHeight is overloaded and I mistakenly used
> the wrong one the first time (should be row-specific).

It does not matter, it is still wrong. If the preferred heights of two
different columns are different, they will still fight forever setting
their row height (whether the global one or just the row itself does not
matter)..

import javax.swing.JTextArea;
import javax.swing.JTable;

import javax.swing.table.TableCellRenderer;

import javax.swing.UIManager;

import javax.swing.BorderFactory;
import javax.swing.border.Border;

import java.awt.Color;

import java.awt.Insets;

import java.awt.Component;

import javax.swing.JFrame;
import javax.swing.JScrollPane;

public class MultiLineCellRenderer
   extends JTextArea
   implements TableCellRenderer
{
   private Border focusBorder, noFocusBorder;

   public MultiLineCellRenderer()
   {
    setOpaque(true);

    setLineWrap(false);
    setWrapStyleWord(false);
   }

   public void updateUI()
   {
    super.updateUI();

    focusBorder = UIManager.getBorder("Table.focusCellHighlightBorder");
   
    noFocusBorder = focusBorder == null ? null : newEmptyBorder(focusBorder.getBorderInsets(this));
   
   }

   private static Border newEmptyBorder(Insets n)
   {
    return BorderFactory.createEmptyBorder(n.top, n.left, n.bottom, n.right);
   }

   private void setForeground_maybe(Color value)
   {
    if (value != null)
       setForeground(value);
   }

   private void setBackground_maybe(Color value)
   {
    if (value != null)
       setBackground(value);
   }

       

   public Component getTableCellRendererComponent
    (JTable table, Object value, boolean selected, boolean focused, int row, int column)
   {
    setEnabled(table.isEnabled());
    setComponentOrientation(table.getComponentOrientation());

    if (selected)
    {
       setForeground(table.getSelectionForeground());
       setBackground(table.getSelectionBackground());
    }
    else
    {
       setForeground(table.getForeground());
       setBackground(table.getBackground());
    }

    setFont(table.getFont());
   
    if (focused)
    {
       setBorder(focusBorder);

       if (table.isCellEditable(row, column))
       {
        setForeground_maybe(UIManager.getColor("Table.focusCellForeground"));
        setBackground_maybe(UIManager.getColor("Table.focusCellBackground"));
       }
    }
    else
    {
       setBorder(noFocusBorder);
    }

    setValue(value);

    int rowHeight = getPreferredSize().height;

    if (table.getRowHeight(row) != rowHeight) table.setRowHeight(row, rowHeight);

    return this;
   }

   protected void setValue(Object value)
   {
    setText(value == null ? "" : value.toString());
   }

   public static void main(String[] args)
   {
    JTable t = new JTable(1, 2);

    t.setValueAt("line", 0, 0);
    t.setValueAt("two\nlines", 0, 1);

    t.setDefaultRenderer(Object.class, new MultiLineCellRenderer());

    JFrame f = new JFrame();

    f.getContentPane().add(new JScrollPane(t));

    f.pack(); f.setVisible(true);
   }
}

Christian
Christian Kaufhold - 24 May 2005 14:03 GMT
>> Cargo-cult code.
>
[quoted text clipped - 6 lines]
> nor the reason the code apparently avoided the bug was ever fully
> understood.

Do you understand why this stuff with overriding setBackground/set-
Foreground and invoking super.setBackground/Foreground was done? Is it
useful that way?

> Dr. Heinz Kabutz:
>
[quoted text clipped - 6 lines]
> to check whether the row was already the correct height before setting
> it."

That's not the point. The JTable *could* do this check itself and avoid
revalidation (but it does not).
The problems with setting the row height from the renderer are:
* It fails unless all renderers agree on a row height. Otherwise the row
height will switch randomly, or even pseudo-recursive as each layout/
painting will cause relayout/repainting.
* It destroys the previously set row height. If the contents of the cell
are later modified (or the cell/column is removed), it cannot be restored.

See also previous postings by me (e.g. search for "setRowHeight" and my
name).

> Yes, in fact my interface has two tables in side-by-side scoll panes.
> This allows me to synchronize row selections between two tables with
> rows of varying heights.  The application generates the table data,
> which varies in length.

See the example code in the other posting.

Christian
wasteheap@email.com - 24 May 2005 16:57 GMT
> Do you understand why this stuff with overriding setBackground/set-
> Foreground and invoking super.setBackground/Foreground was done? Is it
> useful that way?

I don't know why Wernitz overrode setBackground/Foreground; I did not
focus on that.  I commented it out, and the table stills acts as I
expect in my test.  As I understand, super.setBackground/Foreground is
used to reproduce highlighting/borders for selected rows/cells.
Without it, there is no indication of selection.  Is there a different,
standard way this should be done?

> * It fails unless all renderers agree on a row height. Otherwise the row
> height will switch randomly, or even pseudo-recursive as each layout/
> painting will cause relayout/repainting.

I see your point.  Although the JTextArea's getPreferredSize()
calculates the size of the component based on its contents (and so
implies the cell's height), it is unnecessary to calculate the height
each time the cell is rendered.  I should use the renderer's
getPreferredSize() method at a more appropriate time.

The row height shall be equal to the maximum of its cells' heights.
Wernitz iterated through all columns in a row to determine the maximum
height.  In a dynamic table, it seems necessary to (re)calculate the
row height whenever a cell is initialized or edited.  In my case, my
tables are static, so I only need to worry about calculating the row
height when a row is added.  I guess I will use a TableModelListener to
listen for an INSERT. If I had a dynamic table, would I simply need to
also listen for an UPDATE?  That is, is the TableModelEvent guaranteed
to be fired when the contents of a cell are changed in a way that
affects cell height?  Does this depend on the type of editor?
Suggestions?

>  * It destroys the previously set row height. If the contents of the
cell
> are later modified (or the cell/column is removed), it cannot be restored.

I guess you mean if we, say, scrolled the table and the row height
decreased because the cell with greatest height was not rendered.  I
agree that render-time is not the right time to set row height.
Christian Kaufhold - 25 May 2005 17:01 GMT
>> Do you understand why this stuff with overriding setBackground/set-
>> Foreground and invoking super.setBackground/Foreground was done? Is
[quoted text clipped - 7 lines]
> Without it, there is no indication of selection.  Is there a different,
> standard way this should be done?

Of course you need to call setBackground/Foreground to set the colors
(taken from the table if you like). But overridding them etc. is confusing
(especially in subclasses) and, in this implemented, does not really work.

Christian
Christian Kaufhold - 25 May 2005 17:05 GMT
>> * It fails unless all renderers agree on a row height. Otherwise the
> row
[quoted text clipped - 8 lines]
>
> The row height shall be equal to the maximum of its cells' heights.

This is not what your code does. That's why it may still cause infinite
relayout.

> Wernitz iterated through all columns in a row to determine the maximum
> height.  In a dynamic table, it seems necessary to (re)calculate the
[quoted text clipped - 5 lines]
> to be fired when the contents of a cell are changed in a way that
> affects cell height?  Does this depend on the type of editor?

Yes. Yes. Yes. No. There is a catch that you need to make sure to know
whether the table has already updated itself (i.e. received the Table-
ModelEvent) or not - the easiest is to override tableChanged and do it
afterwards or in advance depending on what you want.

>>  * It destroys the previously set row height. If the contents of the
> cell
[quoted text clipped - 3 lines]
> I guess you mean if we, say, scrolled the table and the row height
> decreased because the cell with greatest height was not rendered.  I

I don't mean that. I mean if, e.g. if contents of the cell with the largest
height are modified so that the height would be smaller, then this
will not cause a decrease of the height.

> agree that render-time is not the right time to set row height.

True.

Christian


Free Magazines

Get these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...

Oracle MagazineNetwork ComputingComputer WorldBio-IT WorldeWeekInformation WeekInfosecurity
 
Sign In
Join
My Latest Posts
My Monitored Threads
My Blog
My Photo Gallery
My Profile
My Homepage

Start New Thread
Enable EMail Alerts
Rate this Thread



©2008 Advenet LLC   Privacy Policy - Terms of Use
This website includes both content owned or controlled by Advenet as well as content owned or controlled by third parties.