Table cells with HTML strings inconsistently displayed as multi-line

The cells in one of the columns in my table are HTML strings. HTML is used to provide some color indication. Usually the column width is enough to contain the entire row. But when this is not enough, the line is beautifully cut at the border of the word. This is the desired behavior. The default cell renderer is used.

I noticed that sometimes some interaction with the table launches a renderer to wrap the line. As I understand it, wrapping an HTML string is the normal behavior of JLabel from which DefaultTableCellRenderer is DefaultTableCellRenderer . It is not clear why this behavior is so inconsistent and what causes its deviation. What is the reason JLabel jump back and forth, as if it was constantly being reviewed? See the attached image for an example.

To solve the problem, I can either add <nobr> to the HTML string to prevent it from wrapping, or use a more sophisticated rendering to render the colored strings. But I am wondering if there is a way to make JLabel good.

I managed to reduce the whole case to a simple example. What am I doing to reproduce the problem, click a few lines to change the selection.

enter image description here

 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.

+6
source share
2 answers

The main problem is how the DefaultTableCellRenderer method (which is used by DefaultTableCellRenderer ) tries to format the HTML, which allows the HTML to wrap when the available width is short to accommodate the text. This is the default behavior for JLabel

Why this seems to happen only after the cell is selected is one of those wonderful mysterious Swing events ... because it "must" happen all the time ...

One solution could be to use a layout manager that will prevent (or discourage) JLabel from packing at an "accessible" width point ... This, however, will require providing your own TableCellRenderer , for example ...

Table

 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); } }); } } 

Updating ...

I am pleased to dig through the JTable and BasicTableUI , and the TableCellRenderer component was β€œbrought up” to the requirements of a separate cell, which means that when JLabel is displayed, it automatically wraps the text without considering why this causes layout problems, possibly due to default verticalAlignment ...

Updated with an alternative ...

Another alternative could be to set verticalAlignment in JLabel.TOP for DefaultTableCellRenderer , which is supported by JLabel , for example ...

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 it comes down to your individual needs ...

+4
source

Perhaps this was caused by JLabel vertical alignment:

 // Works for me (Java 1.7.0_65, Windows 7) ((JLabel) table.getDefaultRenderer(Object.class)).setVerticalAlignment(JLabel.TOP); 

EDIT

Here is my test code:

 import java.awt.*; import java.awt.event.*; import java.util.Arrays; import javax.swing.*; import javax.swing.table.*; public class TestTable2 extends JPanel { public TestTable2() { super(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 Class<?> getColumnClass(int column) { return String.class; } @Override public boolean isCellEditable(int row, int column) { return false; } }; final JTable table = new JTable(model); //table.setRowHeight(table.getFont().getSize() * 2); table.setRowHeight(20); add(new JScrollPane(table)); final JRadioButton centerRadio = new JRadioButton("CENTER"); final JRadioButton topRadio = new JRadioButton("TOP"); final JRadioButton bottomRadio = new JRadioButton("BOTTOM"); ActionListener al = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { TableCellRenderer r = table.getDefaultRenderer(String.class); if (r instanceof JLabel) { JLabel label = (JLabel) r; if (topRadio.isSelected()) { label.setVerticalAlignment(SwingConstants.TOP); } else if (bottomRadio.isSelected()) { label.setVerticalAlignment(SwingConstants.BOTTOM); } else { label.setVerticalAlignment(SwingConstants.CENTER); } table.repaint(); } } }; ButtonGroup bg = new ButtonGroup(); JPanel p = new JPanel(); for (JRadioButton b : Arrays.asList(centerRadio, topRadio, bottomRadio)) { b.addActionListener(al); bg.add(b); p.add(b); } centerRadio.setSelected(true); add(p, BorderLayout.SOUTH); } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { JFrame frame = new JFrame("Test"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setLocationByPlatform(true); frame.add(new TestTable2()); frame.setSize(320, 240); frame.setVisible(true); } }); } } 
+2
source

Source: https://habr.com/ru/post/973119/


All Articles