Java Swing - Nimbus L & F overrides custom icon in JTable header after sorting

I am trying to create my own cell renderer that will display an image in a JTable header cell. I got the source code for working with Metal L & F, but I ran into problems with a halo. Under normal circumstances, Nimbus displays the image just fine. However, when the table is sorted, Nimbus draws a sort icon instead of the icon I specified. This is different from Metal L & F, as the icon I provided will always be displayed.

Example image demonstrating error in Nimbus L&F vs Metal L&F

Does anyone know how to make Nimbus draw an image even if the column is sorted?

I am using Java 6.29 and Nimbus. I cannot change the release of Java or L & F.

In addition, I tried to make some other workarounds, for example changing the shortcut to use HTML and the img tag to display the image, but this creates a strange visual effect. EDIT Text and image do not align well (even with the HTML alignment tag on the img tag). Here is an image, notice how the text in the Temp Hi column does not align:

example image of solution with HMTL and img tag

import java.awt.Component; import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableCellRenderer; public class ImageChangeDemo extends JFrame { public static void main(String args[]) { //comment out the code below to try in Metal L&F try { for(javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager. getInstalledLookAndFeels()) { if("Nimbus".equals(info.getName())) { javax.swing.UIManager.setLookAndFeel(info.getClassName()); break; } } } catch(Exception ex) { ex.printStackTrace(); } java.awt.EventQueue.invokeLater(new Runnable() { @Override public void run() { new ImageChangeDemo().setVisible(true); } }); } public ImageChangeDemo(){ setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); JScrollPane pane = new javax.swing.JScrollPane(); JTable table = new javax.swing.JTable(); table.setAutoCreateRowSorter(true); table.setModel(new javax.swing.table.DefaultTableModel( new Object [][] { {"a", "q", "h", "v"}, {"b", "m", "l", "h"}, {"d", "c", "a", "d"}, {"j", "o", "y", "e"} }, new String [] { "Col 1", "Col 2", "Col 3", "Col 4" } ) { Class[] types = new Class [] { String.class, String.class, String.class, String.class }; @Override public Class getColumnClass(int columnIndex) { return types [columnIndex]; } }); pane.setViewportView(table); this.add(pane); table.getTableHeader().setDefaultRenderer(new ImageRenderer(table)); pack(); } public class ImageRenderer extends DefaultTableCellRenderer{ TableCellRenderer orig; ImageIcon icon; ImageRenderer(JTable table){ orig = table.getTableHeader().getDefaultRenderer(); } @Override public Component getTableCellRendererComponent(final JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { Component c = orig.getTableCellRendererComponent( table, value, isSelected, hasFocus, row, column); if(c instanceof JLabel){ if(true){ JLabel label = (JLabel)c; label.setIcon(makeIcon()); } } return c; } public ImageIcon makeIcon(){ if(icon == null) icon = new ImageIcon( ImageChangeDemo.class.getResource("/resources/green_triangle_down.png")); return icon; } } } 

EDIT: Here is an example of a scenario of what my real application should do: if the table column contains certain data (for example, any rows starting with a specific word), it displays a warning icon next to the column name in the table header. I got this to work fine. Now, if the user sorts the column with the image, Nimbus deletes the image and replaces it with the sort icon - I still want the original warning icon to be displayed.

+4
source share
2 answers

So, after much trial and error, I was able to figure out how to create my own icon in the title bar, even if the column is sorted. Basically, I had the renderer return a user panel containing 2 children, an image in JLabel and the component that was originally created by the default renderer. (Note that this workaround is only needed for Nimbus L & F, and the source sample code works fine in Metal L & F)

This code uses the StackLayout created by Romain Guy, as shown in his book Filthy Rich Clients - see page p245. Here is the source for StackLayout

Here is the code I created for rendering. Be sure to download StackLayout, otherwise it will not compile.

 import java.awt.Component; import java.awt.Dimension; import java.awt.FontMetrics; import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableCellRenderer; public class ImageChangeDemo extends JFrame { public static void main(String args[]) { //comment out the code below to try in Metal L&F try { for(javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager. getInstalledLookAndFeels()) { if("Nimbus".equals(info.getName())) { javax.swing.UIManager.setLookAndFeel(info.getClassName()); break; } } } catch(Exception ex) { ex.printStackTrace(); } java.awt.EventQueue.invokeLater(new Runnable() { @Override public void run() { new ImageChangeDemo().setVisible(true); } }); } public ImageChangeDemo(){ setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); JScrollPane pane = new javax.swing.JScrollPane(); JTable table = new javax.swing.JTable(); table.setAutoCreateRowSorter(true); table.setModel(new javax.swing.table.DefaultTableModel( new Object [][] { {"a", "q", "h", "v"}, {"b", "m", "l", "h"}, {"d", "c", "a", "d"}, {"j", "o", "y", "e"} }, new String [] { "Col 1", "Col 2", "Col 3", "Col 4" } ) { Class[] types = new Class [] { String.class, String.class, String.class, String.class }; @Override public Class getColumnClass(int columnIndex) { return types [columnIndex]; } }); pane.setViewportView(table); this.add(pane); pack(); //set renderer after pack so header row has correct default height table.getTableHeader().setDefaultRenderer(new ImageRenderer(table)); } public class ImageRenderer extends DefaultTableCellRenderer{ TableCellRenderer orig; private final ImageIcon icon = new ImageIcon( ImageChangeDemo.class.getResource("/resources/exclamation-icon.png"));; private JPanel jp = new JPanel(new StackLayout()); private final JLabel pic = new JLabel(icon); { //extra initialization for PIC pic.setHorizontalAlignment(JLabel.LEADING); //so it isn't centered in stack layout } ImageRenderer(JTable table){ orig = table.getTableHeader().getDefaultRenderer(); } @Override public Component getTableCellRendererComponent(final JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { Component c = orig.getTableCellRendererComponent( table, value, isSelected, hasFocus, row, column); if(true){ int width = table.getColumnModel().getColumn(column).getWidth(); int height = table.getTableHeader().getSize().height; System.out.println("height"+height); jp.removeAll(); //clean the JPanel //move text in label to the left so it isn't covered by the icon if(c instanceof JLabel){ JLabel l = (JLabel) c; l.setPreferredSize(new Dimension(width, height)); FontMetrics fontMetrics = l.getFontMetrics(l.getFont()); int sizeOfSpace = fontMetrics.charWidth(' '); int numSpaces = (int)Math.round(icon.getIconWidth() / (double)sizeOfSpace); StringBuilder sb = new StringBuilder(); for(int i = 0; i < numSpaces; i++) sb.append(' '); //account for HTML in header messages if(l.getText().toLowerCase().startsWith("<html>")){ l.setText( l.getText().substring(0, "<html>".length()) + sb.toString() + l.getText().substring("<html>".length())); } else l.setText(sb.toString()+l.getText()); } //Add components to the JPanel & return it. jp.add(c, StackLayout.BOTTOM); //will contain modifications for spacing. jp.add(pic, StackLayout.TOP); return jp; } else return c; } } } 
+3
source
  • do not recreate the Icon inside the Renderer , prepare it earlier, otherwise you recreate the Icon in crazy periods

  • do not add Icon to Component / JComponent / JLabel returns Renderer

  • enter Renderer

code created by Darryl or Rob

 protected Icon getIcon(JTable table, int column) { SortKey sortKey = getSortKey(table, column); if (sortKey != null && table.convertColumnIndexToView( sortKey.getColumn()) == column) { switch (sortKey.getSortOrder()) { case ASCENDING: return UIManager.getIcon("Table.ascendingSortIcon"); case DESCENDING: return UIManager.getIcon("Table.descendingSortIcon"); } } return null; } 

EDIT

thanks Renderer @trashgod , UNSORTED no need to redefine for Renderer, try and enjoy

initial view

enter image description here

Ascend

enter image description here

DESCENDING

enter image description here

Unsorted

enter image description here

 import java.awt.Component; import java.util.ArrayList; import java.util.List; import javax.swing.Icon; import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.RowSorter; import javax.swing.SortOrder; import javax.swing.UIManager; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableModel; import javax.swing.table.TableRowSorter; public class ImageChangeDemo extends JFrame { private static final long serialVersionUID = 1L; private JTable table = new javax.swing.JTable(); public static void main(String args[]) { //comment out the code below to try in Metal L&F try { for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) { if ("Nimbus".equals(info.getName())) { javax.swing.UIManager.setLookAndFeel(info.getClassName()); break; } } } catch (Exception ex) { ex.printStackTrace(); } java.awt.EventQueue.invokeLater(new Runnable() { @Override public void run() { new ImageChangeDemo().setVisible(true); } }); } public ImageChangeDemo() { setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); JScrollPane pane = new javax.swing.JScrollPane(); table.setModel(new javax.swing.table.DefaultTableModel( new Object[][]{ {"a", "q", "h", "v"}, {"b", "m", "l", "h"}, {"d", "c", "a", "d"}, {"j", "o", "y", "e"} }, new String[]{ "Col 1", "Col 2", "Col 3", "Col 4" }) { private static final long serialVersionUID = 1L; Class[] types = new Class[]{ String.class, String.class, String.class, String.class }; @Override public Class getColumnClass(int columnIndex) { return types[columnIndex]; } }); TableRowSorter<TableModel> sorter = new TableRowSorter<TableModel>(table.getModel()) { @Override public void toggleSortOrder(int column) { if (column >= 0 && column < getModelWrapper().getColumnCount() && isSortable(column)) { List<SortKey> keys = new ArrayList<SortKey>(getSortKeys()); if (!keys.isEmpty()) { SortKey sortKey = keys.get(0); if (sortKey.getColumn() == column && sortKey.getSortOrder() == SortOrder.DESCENDING) { setSortKeys(null); return; } } } super.toggleSortOrder(column); } }; table.setRowSorter(sorter); table.setPreferredScrollableViewportSize(table.getPreferredSize()); table.setDefaultRenderer(ImageChangeDemo.class, new HeaderRenderer(table)); pane.setViewportView(table); add(pane); pack(); } class HeaderRenderer implements TableCellRenderer { final TableCellRenderer renderer; public HeaderRenderer(JTable table) { renderer = table.getTableHeader().getDefaultRenderer(); } @Override public Component getTableCellRendererComponent( JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) { return renderer.getTableCellRendererComponent( table, value, isSelected, hasFocus, row, col); } public Icon getIcon(JTable table, int column) { for (RowSorter.SortKey sortKey : table.getRowSorter().getSortKeys()) { if (sortKey.getColumn() == column) { switch (sortKey.getSortOrder()) { case ASCENDING: return (UIManager.getIcon("Table.ascendingSortIcon")); case DESCENDING: return (UIManager.getIcon("Table.descendingSortIcon")); } } } return null; } } } 

EDIT 2

then set the icon directly in UIManager

enter image description here

enter image description here

 import java.awt.Color; import java.awt.Component; import java.awt.Graphics; import java.util.ArrayList; import java.util.List; import javax.swing.Icon; import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.RowSorter.SortKey; import javax.swing.SortOrder; import javax.swing.UIManager; import javax.swing.table.TableModel; import javax.swing.table.TableRowSorter; public class ImageChangeDemo extends JFrame { private static final long serialVersionUID = 1L; private JTable table = new javax.swing.JTable(); public static void main(String args[]) { //comment out the code below to try in Metal L&F try { for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) { if ("Nimbus".equals(info.getName())) { javax.swing.UIManager.setLookAndFeel(info.getClassName()); UIManager.getLookAndFeelDefaults().put("Table.ascendingSortIcon", new BevelArrowIcon(BevelArrowIcon.UP, false, false)); UIManager.getLookAndFeelDefaults().put("Table.descendingSortIcon", new BevelArrowIcon(BevelArrowIcon.DOWN, false, false)); break; } } } catch (Exception ex) { ex.printStackTrace(); } java.awt.EventQueue.invokeLater(new Runnable() { @Override public void run() { new ImageChangeDemo().setVisible(true); } }); } public ImageChangeDemo() { setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); JScrollPane pane = new javax.swing.JScrollPane(); //table.setAutoCreateRowSorter(true); table.setModel(new javax.swing.table.DefaultTableModel( new Object[][]{ {"a", "q", "h", "v"}, {"b", "m", "l", "h"}, {"d", "c", "a", "d"}, {"j", "o", "y", "e"} }, new String[]{ "Col 1", "Col 2", "Col 3", "Col 4" }) { private static final long serialVersionUID = 1L; Class[] types = new Class[]{ String.class, String.class, String.class, String.class }; @Override public Class getColumnClass(int columnIndex) { return types[columnIndex]; } }); TableRowSorter<TableModel> sorter = new TableRowSorter<TableModel>(table.getModel()) { @Override public void toggleSortOrder(int column) { if (column >= 0 && column < getModelWrapper().getColumnCount() && isSortable(column)) { List<SortKey> keys = new ArrayList<SortKey>(getSortKeys()); if (!keys.isEmpty()) { SortKey sortKey = keys.get(0); if (sortKey.getColumn() == column && sortKey.getSortOrder() == SortOrder.DESCENDING) { setSortKeys(null); return; } } } super.toggleSortOrder(column); } }; table.setRowSorter(sorter); //table.getTableHeader().setDefaultRenderer(new DefaultTableHeaderCellRenderer()); //table.setDefaultRenderer(ImageChangeDemo.class, new HeaderRenderer(table)); table.setPreferredScrollableViewportSize(table.getPreferredSize()); pane.setViewportView(table); add(pane); pack(); } static class BevelArrowIcon implements Icon { public static final int UP = 0; // direction public static final int DOWN = 1; private static final int DEFAULT_SIZE = 11; private Color edge1; private Color edge2; private Color fill; private int size; private int direction; public BevelArrowIcon(int direction, boolean isRaisedView, boolean isPressedView) { if (isRaisedView) { if (isPressedView) { init(UIManager.getColor("controlLtHighlight"), UIManager.getColor("controlDkShadow"), UIManager.getColor("controlShadow"), DEFAULT_SIZE, direction); } else { init(UIManager.getColor("controlHighlight"), UIManager.getColor("controlShadow"), UIManager.getColor("control"), DEFAULT_SIZE, direction); } } else { if (isPressedView) { init(UIManager.getColor("controlDkShadow"), UIManager.getColor("controlLtHighlight"), UIManager.getColor("controlShadow"), DEFAULT_SIZE, direction); } else { init(UIManager.getColor("controlShadow"), UIManager.getColor("controlHighlight"), UIManager.getColor("control"), DEFAULT_SIZE, direction); } } } public BevelArrowIcon(Color edge1, Color edge2, Color fill, int size, int direction) { init(edge1, edge2, fill, size, direction); } @Override public void paintIcon(Component c, Graphics g, int x, int y) { switch (direction) { case DOWN: drawDownArrow(g, x, y); break; case UP: drawUpArrow(g, x, y); break; } } @Override public int getIconWidth() { return size; } @Override public int getIconHeight() { return size; } private void init(Color edge1, Color edge2, Color fill, int size, int direction) { edge1 = Color.red; edge2 = Color.blue; this.edge1 = edge1; this.edge2 = edge2; this.fill = fill; this.size = size; this.direction = direction; } private void drawDownArrow(Graphics g, int xo, int yo) { g.setColor(edge1); g.drawLine(xo, yo, xo + size - 1, yo); g.drawLine(xo, yo + 1, xo + size - 3, yo + 1); g.setColor(edge2); g.drawLine(xo + size - 2, yo + 1, xo + size - 1, yo + 1); int x = xo + 1; int y = yo + 2; int dx = size - 6; while (y + 1 < yo + size) { g.setColor(edge1); g.drawLine(x, y, x + 1, y); g.drawLine(x, y + 1, x + 1, y + 1); if (0 < dx) { g.setColor(fill); g.drawLine(x + 2, y, x + 1 + dx, y); g.drawLine(x + 2, y + 1, x + 1 + dx, y + 1); } g.setColor(edge2); g.drawLine(x + dx + 2, y, x + dx + 3, y); g.drawLine(x + dx + 2, y + 1, x + dx + 3, y + 1); x += 1; y += 2; dx -= 2; } g.setColor(edge1); g.drawLine(xo + (size / 2), yo + size - 1, xo + (size / 2), yo + size - 1); } private void drawUpArrow(Graphics g, int xo, int yo) { g.setColor(edge1); int x = xo + (size / 2); g.drawLine(x, yo, x, yo); x--; int y = yo + 1; int dx = 0; while (y + 3 < yo + size) { g.setColor(edge1); g.drawLine(x, y, x + 1, y); g.drawLine(x, y + 1, x + 1, y + 1); if (0 < dx) { g.setColor(fill); g.drawLine(x + 2, y, x + 1 + dx, y); g.drawLine(x + 2, y + 1, x + 1 + dx, y + 1); } g.setColor(edge2); g.drawLine(x + dx + 2, y, x + dx + 3, y); g.drawLine(x + dx + 2, y + 1, x + dx + 3, y + 1); x -= 1; y += 2; dx += 2; } g.setColor(edge1); g.drawLine(xo, yo + size - 3, xo + 1, yo + size - 3); g.setColor(edge2); g.drawLine(xo + 2, yo + size - 2, xo + size - 1, yo + size - 2); g.drawLine(xo, yo + size - 1, xo + size, yo + size - 1); } } } 
+5
source

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


All Articles