-
Enhancement
-
Resolution: Unresolved
-
P4
-
21
-
generic
-
generic
ADDITIONAL SYSTEM INFORMATION :
openjdk version "21.0.1" 2023-10-17 LTS
OpenJDK Runtime Environment Zulu21.30+15-CA (build 21.0.1+12-LTS)
OpenJDK 64-Bit Server VM Zulu21.30+15-CA (build 21.0.1+12-LTS, mixed mode, sharing)
A DESCRIPTION OF THE PROBLEM :
Please restore access to these two methods, currently in class sun.swing.SwingUtilities2. It's very hard to write a good custom renderer without them. For example, I'm including sample code to highlight a misspelled word in a table cell. This comes from a class I wrote for a larger application, but which is no longer usable in the current version of Java.
When you run the program, you can see that I can create a reasonably good custom renderer using a JLabel with html text, or using a JTextPane, but I can't reproduce the "…" at the end of cell when it needs to clip the text. As a result, the last word gets entirely removed. You can drag the column separators and window size to see the full contents of the cells, and see how they get clipped.
This is not how renderers should behave. Without the "…" at the end, the JTable fails to convey to the user that more text will appear when the cell is made wider. This is important.
The JTable was designed to use customizable cell renderers, yet we don't have access to the methods that would make it easy to perform this basic function. This makes no sense. I have tried to write renderers that do this on their own, but it's not easy. Developing a user interface shouldn't be hard like that.
The code to do this already exists and works perfectly. This isn't a big change.
I wrote a HighlightRenderer many years ago that worked well, but it relied on the two methods in SwingUtilities2. I was aware that those APIs would eventually disappear, but it was the simplest way to accomplish my task, and it worked very well.
Here's the sample code:
package com.mm.bugs2;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextPane;
import javax.swing.WindowConstants;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
public final class TableRendererRFE extends JPanel {
public static void main(String[] args) {
JFrame frame = new JFrame("Table RendererRFE");
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
frame.setLocationByPlatform(true);
frame.add(new TableRendererRFE());
frame.pack();
frame.setVisible(true);
}
private static final String bad = "mispelled";
private TableRendererRFE() {
super(new BorderLayout());
TableCellRenderer[] renderers = {
new DefaultTableCellRenderer(),
new JLabelRenderer(),
new CustomJTextPaneRenderer()
};
AbstractTableModel tableModel = new AbstractTableModel() {
@Override
public int getRowCount() {
return renderers.length;
}
@Override
public int getColumnCount() {
return 3;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
switch(columnIndex) {
case 0: return renderers[rowIndex].getClass().getSimpleName();
case 1: return "The mispelled word should appear here.";
case 2: return "Clipping sometimes works when nothing is misspelled.";
default:
return "";
}
}
};
JTable table = new JTable(tableModel) {
@Override
public TableCellRenderer getCellRenderer(int row, int column) {
return renderers[row];
}
};
final TableColumnModel columnModel = table.getTableHeader().getColumnModel();
final TableColumn column = columnModel.getColumn(0);
column.setMaxWidth(250);
column.setPreferredWidth(175);
JScrollPane scrollPane = new JScrollPane(table, JScrollPane.VERTICAL_SCROLLBAR_NEVER, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
add(scrollPane, BorderLayout.CENTER);
}
private static final class JLabelRenderer extends DefaultTableCellRenderer {
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
String text = wrapInHtml(value);
return super.getTableCellRendererComponent(table, text, isSelected, hasFocus, row, column);
}
}
private static String wrapInHtml(Object value) {
String text = value.toString();
int index = text.indexOf(bad);
if (index >= 0) {
String pre = text.substring(0, index);
String post = text.substring(index + bad.length());
text = String.format("<html>%s<span style=\"color:red;\">%s</span>%s</html>", pre, bad, post);
}
return text;
}
private static class CustomJTextPaneRenderer implements TableCellRenderer {
private final JTextPane renderComponent = new JTextPane();
CustomJTextPaneRenderer() {
renderComponent.setFont(renderComponent.getFont().deriveFont(12.0f));
// For some reason, JTextPane uses a different font size than a JLabel.
Style style = renderComponent.addStyle("Red", null);
StyleConstants.setForeground(style, Color.RED);
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
final String string = value.toString();
renderComponent.setText(string);
int badIndex = string.indexOf(bad);
if (badIndex >= 0) {
StyledDocument doc = renderComponent.getStyledDocument();
doc.setCharacterAttributes(badIndex, bad.length(), doc.getStyle("Red"), true);
}
return renderComponent;
}
}
}
openjdk version "21.0.1" 2023-10-17 LTS
OpenJDK Runtime Environment Zulu21.30+15-CA (build 21.0.1+12-LTS)
OpenJDK 64-Bit Server VM Zulu21.30+15-CA (build 21.0.1+12-LTS, mixed mode, sharing)
A DESCRIPTION OF THE PROBLEM :
Please restore access to these two methods, currently in class sun.swing.SwingUtilities2. It's very hard to write a good custom renderer without them. For example, I'm including sample code to highlight a misspelled word in a table cell. This comes from a class I wrote for a larger application, but which is no longer usable in the current version of Java.
When you run the program, you can see that I can create a reasonably good custom renderer using a JLabel with html text, or using a JTextPane, but I can't reproduce the "…" at the end of cell when it needs to clip the text. As a result, the last word gets entirely removed. You can drag the column separators and window size to see the full contents of the cells, and see how they get clipped.
This is not how renderers should behave. Without the "…" at the end, the JTable fails to convey to the user that more text will appear when the cell is made wider. This is important.
The JTable was designed to use customizable cell renderers, yet we don't have access to the methods that would make it easy to perform this basic function. This makes no sense. I have tried to write renderers that do this on their own, but it's not easy. Developing a user interface shouldn't be hard like that.
The code to do this already exists and works perfectly. This isn't a big change.
I wrote a HighlightRenderer many years ago that worked well, but it relied on the two methods in SwingUtilities2. I was aware that those APIs would eventually disappear, but it was the simplest way to accomplish my task, and it worked very well.
Here's the sample code:
package com.mm.bugs2;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextPane;
import javax.swing.WindowConstants;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
public final class TableRendererRFE extends JPanel {
public static void main(String[] args) {
JFrame frame = new JFrame("Table RendererRFE");
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
frame.setLocationByPlatform(true);
frame.add(new TableRendererRFE());
frame.pack();
frame.setVisible(true);
}
private static final String bad = "mispelled";
private TableRendererRFE() {
super(new BorderLayout());
TableCellRenderer[] renderers = {
new DefaultTableCellRenderer(),
new JLabelRenderer(),
new CustomJTextPaneRenderer()
};
AbstractTableModel tableModel = new AbstractTableModel() {
@Override
public int getRowCount() {
return renderers.length;
}
@Override
public int getColumnCount() {
return 3;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
switch(columnIndex) {
case 0: return renderers[rowIndex].getClass().getSimpleName();
case 1: return "The mispelled word should appear here.";
case 2: return "Clipping sometimes works when nothing is misspelled.";
default:
return "";
}
}
};
JTable table = new JTable(tableModel) {
@Override
public TableCellRenderer getCellRenderer(int row, int column) {
return renderers[row];
}
};
final TableColumnModel columnModel = table.getTableHeader().getColumnModel();
final TableColumn column = columnModel.getColumn(0);
column.setMaxWidth(250);
column.setPreferredWidth(175);
JScrollPane scrollPane = new JScrollPane(table, JScrollPane.VERTICAL_SCROLLBAR_NEVER, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
add(scrollPane, BorderLayout.CENTER);
}
private static final class JLabelRenderer extends DefaultTableCellRenderer {
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
String text = wrapInHtml(value);
return super.getTableCellRendererComponent(table, text, isSelected, hasFocus, row, column);
}
}
private static String wrapInHtml(Object value) {
String text = value.toString();
int index = text.indexOf(bad);
if (index >= 0) {
String pre = text.substring(0, index);
String post = text.substring(index + bad.length());
text = String.format("<html>%s<span style=\"color:red;\">%s</span>%s</html>", pre, bad, post);
}
return text;
}
private static class CustomJTextPaneRenderer implements TableCellRenderer {
private final JTextPane renderComponent = new JTextPane();
CustomJTextPaneRenderer() {
renderComponent.setFont(renderComponent.getFont().deriveFont(12.0f));
// For some reason, JTextPane uses a different font size than a JLabel.
Style style = renderComponent.addStyle("Red", null);
StyleConstants.setForeground(style, Color.RED);
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
final String string = value.toString();
renderComponent.setText(string);
int badIndex = string.indexOf(bad);
if (badIndex >= 0) {
StyledDocument doc = renderComponent.getStyledDocument();
doc.setCharacterAttributes(badIndex, bad.length(), doc.getStyle("Red"), true);
}
return renderComponent;
}
}
}