具有HTML字符串的表格单元格不一致地呈现为多行

我表中一列的单元格是HTML字符串。 HTML用于提供一些颜色指示。 通常,列的宽度足以包含整个字符串。 但是当它还不够时,字符串很好地切割在单词边界上。 这是理想的行为。 使用默认的单元格渲染器。

我注意到偶尔会有一些与表的交互触发渲染器包装字符串。 据我所知,包装HTML字符串是JLabel的正常行为, DefaultTableCellRenderer从该行为派生。 不清楚的是,为什么这种行为如此不一致,是什么引发了它的偏差。 JLabel来回跳跃的原因是什么,好像它经常被重新测量一样? 有关示例,请参见附图。

要解决这个问题,我可以将添加到HTML字符串以防止换行,或者使用更复杂的渲染器来渲染彩色字符串。 但我想知道是否有办法让JLabel发挥出色。

我设法将整个案例简化为一个简单的例子。 我要重现的问题是单击各行以更改选择。

在此处输入图像描述

 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 = { { "1 Lorem ipsum dolor sit amet, " + "consectetur adipiscing elit. In lectus dolor"}, { "2 Lorem ipsum dolor sit amet, " + "consectetur adipiscing elit. In lectus dolor"}, { "3 Lorem ipsum dolor sit amet, " + "consectetur adipiscing elit. In lectus dolor"}, { "4 Lorem ipsum dolor sit amet, " + "consectetur adipiscing elit. In lectus dolor"}, { "5 Lorem ipsum dolor sit amet, " + "consectetur adipiscing elit. In lectus dolor"}, }; 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); } }); } } 

我的环境是Java 7 Win 7 x64,也使用Java 6和8进行了测试,它看起来一样。

核心问题是JLabelDefaultTableCellRenderer正在使用)试图格式化HTML的方式,它允许HTML在可用宽度缩短以容纳文本时进行换行。 这是JLabel的默认行为

为什么这似乎只发生在选择单元格之后才是Swing那些神奇的神秘之一…因为它“应该”一直在发生……

一种解决方案可能是使用布局管理器来防止(或阻止) JLabel在“可用”宽度点包装……但是,这需要提供自己的TableCellRenderer ,例如……

表

 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 = { {"1 Lorem ipsum dolor sit amet, " + "consectetur adipiscing elit. In lectus dolor"}, {"2 Lorem ipsum dolor sit amet, " + "consectetur adipiscing elit. In lectus dolor"}, {"3 Lorem ipsum dolor sit amet, " + "consectetur adipiscing elit. In lectus dolor"}, {"4 Lorem ipsum dolor sit amet, " + "consectetur adipiscing elit. In lectus dolor"}, {"5 Lorem ipsum dolor sit amet, " + "consectetur adipiscing elit. In lectus dolor"},}; 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); } }); } } 

更新…

我通过JTableBasicTableUI代码进行了很好的挖掘,并且TableCellRenderer组件已根据单个单元格的要求“调整”,这意味着当呈现JLabel时,它会自动包装文本而不考虑,为什么这会导致问题与布局可能有关于默认verticalAlignment的事实…

更新了备选方案……

另一种替代方法可能是将verticalAlignment设置为JLabel.TOP ,该JLabel.TOPJLabel支持,例如……

例

 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 = { {"1 Lorem ipsum dolor sit amet, " + "consectetur adipiscing elit. In lectus dolor"}, {"2 Lorem ipsum dolor sit amet, " + "consectetur adipiscing elit. In lectus dolor"}, {"3 Lorem ipsum dolor sit amet, " + "consectetur adipiscing elit. In lectus dolor"}, {"4 Lorem ipsum dolor sit amet, " + "consectetur adipiscing elit. In lectus dolor"}, {"5 Lorem ipsum dolor sit amet, " + "consectetur adipiscing elit. In lectus dolor"},}; 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); } }); } } 

但这将归结为您的个人需求……

这可能是由JLabel垂直对齐引起的:

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

编辑

这是我的测试代码:

 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 = { { "1 Lorem ipsum dolor sit amet, " + "consectetur adipiscing elit. In lectus dolor" }, { "2 Lorem ipsum dolor sit amet, " + "consectetur adipiscing elit. In lectus dolor" }, { "3 Lorem ipsum dolor sit amet, " + "consectetur adipiscing elit. In lectus dolor" }, { "4 Lorem ipsum dolor sit amet, " + "consectetur adipiscing elit. In lectus dolor" }, { "5 Lorem ipsum dolor sit amet, " + "consectetur adipiscing elit. In lectus dolor" }, }; 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); } }); } }