JTable/setDefaultRenderer Multiple Selected Rows

Viewed 92

I have searched literally all over the Internet and could not find a working solution to my problem.

I have a JTable, to which I want to dynamically change the background colors of multiple rows without affecting the background color of the other rows which colors might have already been altered.

The color change is triggered by an actionListener on a JMenuItem of a ContextMenu, as shown in the follwowing:

enter image description here

The code that I tried so far is as follows:

    JMenu highlightMenu = new JMenu("Highlight");

    // Add null
    for (Color color : Arrays.asList(Color.RED, Color.ORANGE, Color.YELLOW, Color.GREEN, Color.BLUE, Color.MAGENTA,
        Color.PINK, Color.GRAY)) {
      JMenuItem x = new JMenuItem();
      x.setOpaque(true);
      x.setBackground(color);

      highlightMenu.add(x);

      x.addHierarchyListener(e -> x.setText(tab.getTable()
          .getValueAt(tab.getTable().getSelectedRow(), tab.getTable().getColumn("Server").getModelIndex()).toString()));

      x.addActionListener(e -> IntStream.of(tab.getTable().getSelectedRows())
          .forEach(row -> ((Component) tab.getTable().getModel().getValueAt(row, 0)).setBackground(color)));

      // x.addActionListener(e -> {
      // IntStream.of(tab.getTable().getSelectedRows())
      // .forEach(r -> tab.getTable().setDefaultRenderer(Object.class, new
      // 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);

      // if (r == row) {
      // comp.setBackground(color);
      // } else {
      // comp.setBackground(null);
      // }

      // return comp;
      // }
      // }));

      // tab.getTable().repaint();
      // });
    }

If anyone has a working solution, please share it, would be really appreciated!

EDIT 0: I cleaned-up the edits as they were too numerous, so I added debugging strings and this table is not behaving as it should at all, please see the following screenshot:

enter image description here

Note: Also not sure why but it seems that the table is iterated mutliple times (5) as shown in the above output, this should not be the case as each JMenuItem has its own event listener... And it should be triggered only once depending on which color/JMenuItem is picked...

And the resulting table:

enter image description here

From the following code:

    for (Color color : Arrays.asList(Color.RED, Color.ORANGE, Color.YELLOW, Color.GREEN, Color.BLUE, Color.MAGENTA,
        Color.PINK, Color.GRAY)) {
      JMenuItem x = new JMenuItem();
      x.setOpaque(true);
      x.setBackground(color);
      x.setForeground(Color.BLACK);

      highlightMenu.add(x);

      x.addHierarchyListener(e -> x.setText(tab.getTable()
          .getValueAt(tab.getTable().getSelectedRow(), tab.getTable().getColumn("Server").getModelIndex()).toString()));

      x.addActionListener(e -> {
        IntStream.of(tab.getTable().getSelectedRows()).forEach(row -> this.highlightedRows.put(row, color)
        // this.highlightedRows.put(row, Arrays.asList(Color.BLACK, color)
        );

        tab.getTable().setDefaultRenderer(Object.class, new DefaultTableCellRenderer() {
          @Override
          public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
              boolean hasFocus, int row, int column) {
            Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);

            stdOut.println(String.format("%s -> %s", row, highlightedRows.get(row)));

            if (highlightedRows.get(row) != null) {
              stdOut.println("XXXXXXX");
              component.setBackground(highlightedRows.get(row));
            }

            // if (!isSelected && highlightedRows.containsKey(row)) {
            // component.setForeground(highlightedRows.get(row).get(0));
            // component.setBackground(highlightedRows.get(row).get(1));
            // }

            return component;
          }
        });
      });
    }

As you see something is not right there...

EDIT N: Almost solved this issue with the following code:

    for (Color color : Arrays.asList(Color.RED, Color.ORANGE, Color.YELLOW, Color.GREEN, Color.BLUE, Color.MAGENTA,
        Color.PINK, Color.GRAY)) {
      final JMenuItem x = new JMenuItem();
      x.setOpaque(true);
      x.setBackground(color);
      x.setForeground(Color.BLACK);

      highlightMenu.add(x);

      x.addHierarchyListener(e -> x.setText(tab.getTable()
          .getValueAt(tab.getTable().getSelectedRow(), tab.getTable().getColumn("Server").getModelIndex()).toString()));

      x.addActionListener(e -> {
        IntStream.of(tab.getTable().getSelectedRows())
            .forEach(row -> this.highlightedRows.put(row, Arrays.asList(Color.BLACK, color)));
        // row -> this.highlightedRows.put(row, color)

        tab.getTable().setDefaultRenderer(Object.class, new DefaultTableCellRenderer() {
          @Override
          public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
              boolean hasFocus, int row, int column) {
            final Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row,
                column);

            stdOut.println(String.format("%s -> %s", row, highlightedRows.get(row)));

            if (highlightedRows.containsKey(row)) {
              component.setForeground(highlightedRows.get(row).get(0));
              component.setBackground(highlightedRows.get(row).get(1));
            } else {
              if (row % 2 == 0) {
                component.setBackground(javax.swing.UIManager.getLookAndFeelDefaults().getColor("Table.background"));
              } else {
                component
                    .setBackground(javax.swing.UIManager.getLookAndFeelDefaults().getColor("Table.alternateRowColor"));
              }

              component.setForeground(javax.swing.UIManager.getLookAndFeelDefaults().getColor("Table.foreground"));
            }

            if (isSelected) {
              component
                  .setForeground(javax.swing.UIManager.getLookAndFeelDefaults().getColor("Table.selectionForeground"));
              component
                  .setBackground(javax.swing.UIManager.getLookAndFeelDefaults().getColor("Table.selectionBackground"));
            }

            return component;
          }
        });
      });
    }

The only thing now is that the event listener is called more than once, see previous EDIT, whilst this addressed then this would be 100% solved, thanks all!

1 Answers

You could keep a map which maps row indices to colors and use it in your default renderer. In the action listener you just need to put the row index with the assigned color into this map.

final Map<Integer, Color> highlightedRows = new HashMap<>();
highlightedRows.put(1, Color.GREEN);
highlightedRows.put(2, Color.YELLOW);
    
table.setDefaultRenderer(Object.class, new DefaultTableCellRenderer() {
        
    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
        final var cmp = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
        final var color = highlightedRows.get(row);
        if (color != null && !isSelected) {
            cmp.setBackground(color);
        }
        return cmp;
    }
});

Result

enter image description here

EDIT: Fully working example

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.util.HashMap;
import java.util.Map;

import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableCellRenderer;

public class TableRowHighlightExample {

    private final Map<Integer, Color> highlightedRows = new HashMap<>();

    private JTable table;

    private JPopupMenu createHighlightMenu() {
        final JPopupMenu highlightMenu = new JPopupMenu();

        final Color[] colors = { 
                Color.RED, 
                Color.ORANGE, 
                Color.YELLOW, 
                Color.GREEN, 
                Color.BLUE, 
                Color.MAGENTA,
                Color.PINK, 
                Color.GRAY 
        };
                
        // Add null
        for (final Color color : colors) {
            final JMenuItem x = new JMenuItem();
            x.setOpaque(true);
            x.setBackground(color);

            highlightMenu.add(x);

            x.addActionListener(e -> {
                final int[] selectedRows = this.table.getSelectedRows();
                for (final int row : selectedRows) {
                    highlightedRows.put(row, color);
                }
            });
        }
        
        return highlightMenu;
    }

    private JTable createTable() {
        final String[] colNames = { "Column 0" };
        final Object[][] data = { { "Row 0" }, { "Row 1" }, { "Row 2" }, { "Row 3" } };

        final var table = new JTable(data, colNames);
        table.setPreferredSize(new Dimension(500, 200));
        table.setComponentPopupMenu(this.createHighlightMenu());
        
        table.setDefaultRenderer(Object.class, new DefaultTableCellRenderer() {

            @Override
            public Component getTableCellRendererComponent(
                    JTable table, Object value, boolean isSelected,
                    boolean hasFocus, int row, int column) {
                
                final var cmp = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
                cmp.setBackground(highlightedRows.get(row));
                return cmp;
            }
        });
        
        return table;
    }

    public void run() {
        final var frame = new JFrame();
        this.table = this.createTable();
        
        frame.add(new JScrollPane(this.table));
        frame.setSize(500, 200);        
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);     
    }
    
    public static void main(String[] args) {
        new TableRowHighlightExample().run();
    }
}

enter image description here

Related