Cells of one of columns in my table are HTML strings. HTML is used to provide some color indication. Usually the width of the column is enough to contain the whole string. But when it is not enough then the string is nicely cut on a word boundary. This is the desired behavior. The default cell renderer is used.
I noticed that occasionally, some interaction with the table triggers the rendererer to wrap the string. As I understand, wrapping the HTML string is a normal behavior of JLabel
from which DefaultTableCellRenderer
derives. What is not clear is why is this behavior so inconsistent and what triggers its deviation. What is the reason for JLabel
to jump back and forth, as if its being constantly re-measured? See attached image for an example.
To solve the problem I can either add <nobr>
to the HTML string to prevent wrapping, or use a more sophisticated renderer to render colored strings. But I wonder if there is a way to make JLabel
play nice.
I managed to reduce the whole case to a simple example. What I do to reproduce the issue is click various rows to change selection.
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.util.Locale;
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.table.DefaultTableModel;
public class TestTable extends JPanel{
public TestTable() {
setLayout(new BorderLayout());
Object[][] rows = {
{ "<html><font color=red>1 Lorem ipsum</font> dolor sit amet, " +
"consectetur adipiscing elit. In lectus dolor</html>"},
{ "<html><font color=green>2 Lorem ipsum</font> dolor sit amet, " +
"consectetur adipiscing elit. In lectus dolor</html>"},
{ "<html><font color=blue>3 Lorem ipsum</font> dolor sit amet, " +
"consectetur adipiscing elit. In lectus dolor</html>"},
{ "<html><font color=red>4 Lorem ipsum</font> dolor sit amet, " +
"consectetur adipiscing elit. In lectus dolor</html>"},
{ "<html><font color=green>5 Lorem ipsum</font> dolor sit amet, " +
"consectetur adipiscing elit. In lectus dolor</html>"},
};
Object[] columns = {"Column"};
DefaultTableModel model = new DefaultTableModel(rows, columns) {
@Override
public boolean isCellEditable(int row, int column) {
return false;
}
};
JTable table = new JTable(model);
table.setRowHeight(table.getFont().getSize() * 2);
add(new JScrollPane(table));
add(new JLabel(String.format("%s, %s, JRE %s (%s)",
System.getProperty("os.name"), System.getProperty("os.arch"),
System.getProperty("java.version"), Locale.getDefault().toString())),
BorderLayout.SOUTH);
}
public Dimension getPreferredSize() {
return new Dimension(300, 200);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationByPlatform(true);
TestTable panel = new TestTable();
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
});
}
}
My environment is Java 7 Win 7 x64, also tested with Java 6 and 8 and it looks the same.
The core problem is the way that the JLabel
(which the DefaultTableCellRenderer
is using) is trying to format the HTML, it's allowing the HTML to wrap when it's available width is to short to accommodate the text. This is the default behaviour for JLabel
Why this seems to only happen after the cell is selected is one of those wonderful mysterious of Swing...cause it "should" be happening all the time...
One solution might be to use a layout manager which will prevent (or discourage) the JLabel
from wrapping at the "available" width point...This, however, would require to provide your own TableCellRenderer
, for example...
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.util.Locale;
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.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
import sun.swing.DefaultLookup;
public class TestTable extends JPanel {
public TestTable() {
setLayout(new BorderLayout());
Object[][] rows = {
{"<html><font color=red>1 Lorem ipsum</font> dolor sit amet, "
+ "consectetur adipiscing elit. In lectus dolor</html>"},
{"<html><font color=green>2 Lorem ipsum</font> dolor sit amet, "
+ "consectetur adipiscing elit. In lectus dolor</html>"},
{"<html><font color=blue>3 Lorem ipsum</font> dolor sit amet, "
+ "consectetur adipiscing elit. In lectus dolor</html>"},
{"<html><font color=red>4 Lorem ipsum</font> dolor sit amet, "
+ "consectetur adipiscing elit. In lectus dolor</html>"},
{"<html><font color=green>5 Lorem ipsum</font> dolor sit amet, "
+ "consectetur adipiscing elit. In lectus dolor</html>"},};
Object[] columns = {"Column"};
DefaultTableModel model = new DefaultTableModel(rows, columns) {
@Override
public boolean isCellEditable(int row, int column) {
return false;
}
};
JTable table = new JTable(model);
table.setDefaultRenderer(Object.class, new HTMLRenderer());
table.setRowHeight(table.getFont().getSize() * 2);
add(new JScrollPane(table));
add(new JLabel(String.format("%s, %s, JRE %s (%s)",
System.getProperty("os.name"), System.getProperty("os.arch"),
System.getProperty("java.version"), Locale.getDefault().toString())),
BorderLayout.SOUTH);
}
public Dimension getPreferredSize() {
return new Dimension(300, 200);
}
public static class HTMLRenderer extends JPanel implements TableCellRenderer {
private JLabel label;
private static final Border SAFE_NO_FOCUS_BORDER = new EmptyBorder(1, 1, 1, 1);
private static final Border DEFAULT_NO_FOCUS_BORDER = new EmptyBorder(1, 1, 1, 1);
protected static Border noFocusBorder = DEFAULT_NO_FOCUS_BORDER;
public HTMLRenderer() {
label = new DefaultTableCellRenderer();
// setOpaque(false);
setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
add(label);
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
if (table == null) {
return this;
}
Color fg = null;
Color bg = null;
JTable.DropLocation dropLocation = table.getDropLocation();
if (dropLocation != null
&& !dropLocation.isInsertRow()
&& !dropLocation.isInsertColumn()
&& dropLocation.getRow() == row
&& dropLocation.getColumn() == column) {
fg = UIManager.getColor("Table.dropCellForeground");
bg = UIManager.getColor("Table.dropCellBackground");
isSelected = true;
}
if (isSelected) {
super.setForeground(fg == null ? table.getSelectionForeground()
: fg);
super.setBackground(bg == null ? table.getSelectionBackground()
: bg);
} else {
Color background = table.getBackground();
if (background == null || background instanceof javax.swing.plaf.UIResource) {
Color alternateColor = UIManager.getColor("Table.alternateRowColor");
if (alternateColor != null && row % 2 != 0) {
background = alternateColor;
}
}
super.setForeground(table.getForeground());
super.setBackground(background);
}
setFont(table.getFont());
if (hasFocus) {
Border border = null;
if (isSelected) {
border = UIManager.getBorder("Table.focusSelectedCellHighlightBorder");
}
if (border == null) {
border = UIManager.getBorder("Table.focusCellHighlightBorder");
}
setBorder(border);
if (!isSelected && table.isCellEditable(row, column)) {
Color col;
col = UIManager.getColor("Table.focusCellForeground");
if (col != null) {
super.setForeground(col);
}
col = UIManager.getColor("Table.focusCellBackground");
if (col != null) {
super.setBackground(col);
}
}
} else {
setBorder(getNoFocusBorder());
}
label.setText(value == null ? "" : value.toString());
return this;
}
protected Border getNoFocusBorder() {
Border border = UIManager.getBorder("Table.cellNoFocusBorder");
if (System.getSecurityManager() != null) {
if (border != null) return border;
return SAFE_NO_FOCUS_BORDER;
} else if (border != null) {
if (noFocusBorder == null || noFocusBorder == DEFAULT_NO_FOCUS_BORDER) {
return border;
}
}
return noFocusBorder;
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationByPlatform(true);
TestTable panel = new TestTable();
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
});
}
}
Updated...
I had a nice dig through the JTable
and BasicTableUI
code and the TableCellRenderer
component is been "sized" to the requirements of the individual cell, meaning that when the JLabel
is rendered, it is automatically wrapping the text without consideration, why this then causes issues with the layout may have to do with the fact that the default verticalAlignment
...
Updated with alternative...
Another alternative might be to set the verticalAlignment
to JLabel.TOP
of a DefaultTableCellRenderer
, which is backed by a JLabel
, for example...
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.util.Locale;
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.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
public class TestTable extends JPanel {
public TestTable() {
setLayout(new BorderLayout());
Object[][] rows = {
{"<html><font color=red>1 Lorem ipsum</font> dolor sit amet, "
+ "consectetur adipiscing elit. In lectus dolor</html>"},
{"<html><font color=green>2 Lorem ipsum</font> dolor sit amet, "
+ "consectetur adipiscing elit. In lectus dolor</html>"},
{"<html><font color=blue>3 Lorem ipsum</font> dolor sit amet, "
+ "consectetur adipiscing elit. In lectus dolor</html>"},
{"<html><font color=red>4 Lorem ipsum</font> dolor sit amet, "
+ "consectetur adipiscing elit. In lectus dolor</html>"},
{"<html><font color=green>5 Lorem ipsum</font> dolor sit amet, "
+ "consectetur adipiscing elit. In lectus dolor</html>"},};
Object[] columns = {"Column"};
DefaultTableModel model = new DefaultTableModel(rows, columns) {
@Override
public boolean isCellEditable(int row, int column) {
return false;
}
};
JTable table = new JTable(model);
table.setDefaultRenderer(Object.class, new HTMLRenderer());
table.setRowHeight(table.getFont().getSize() * 2);
add(new JScrollPane(table));
add(new JLabel(String.format("%s, %s, JRE %s (%s)",
System.getProperty("os.name"), System.getProperty("os.arch"),
System.getProperty("java.version"), Locale.getDefault().toString())),
BorderLayout.SOUTH);
}
public Dimension getPreferredSize() {
return new Dimension(300, 200);
}
public static class HTMLRenderer extends DefaultTableCellRenderer {
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
Component comp = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
setVerticalAlignment(JLabel.TOP);
return comp;
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationByPlatform(true);
TestTable panel = new TestTable();
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
});
}
}
But this will come down to your individual needs...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With