6

I have a JTable component in my GUI which displays psuedocode of an algorithm. I want to highlight the current line of execution by changing the background of a particular cell and then changing the cell beneath and so on.

Right now my code changes the backgrounds on all cells in my JTable as pictured below:

JTable

The code I am using to archive this current state is as below:

class CustomRenderer extends DefaultTableCellRenderer 
{
    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
    {
            JLabel d = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
            if((row == 0) && (column == 0))
                d.setBackground(new java.awt.Color(255, 72, 72));
            return d;
        }
    }

I then call jTable2.setDefaultRenderer(String.class, new CustomRenderer()); in my constructor.

I assume that:

  • This method is being called on every String type table cell.
  • That this would only change the colour of the cell at position (0,0)

How do I fix my code so that only cell (0,0) is coloured?

Jason Orendorff
  • 42,793
  • 6
  • 62
  • 96
Ashley
  • 2,256
  • 1
  • 33
  • 62

3 Answers3

13

This is not an answer (*), just too long for a comment on both answers: both are correct in that the else block is the important thingy to ensure that the default color is used for cell that are not supposed to be highlighted. They err slightly in how to reach that, both to the same overall effect: they miss any special coloring, like f.i. due to selection, focus, editable, dnd ...

They reach that "miss" by different means with slightly different effects

setBackground(Color.WHITE);

set's a fixed color which may or may not be the default "normal" table background

setBackground(null);

set's no color which leads to showing the "normal" background color - due to internal tricksery of the DefaultTableCellRenderer isOpaque implementation :-)

The basic reason for the problem (also known the infamous color memory, TM) is an unusually bad implementation of the default renderer which leaves it essentially un-extendable:

 /**
 * Overrides <code>JComponent.setBackground</code> to assign
 * the unselected-background color to the specified color. 
 *
 * JW: The side-effect is documented and looks innocent enough :-) 
 */
public void setBackground(Color c) {
    super.setBackground(c); 
    unselectedBackground = c; 
}

// using that side-effect when configuring the colors in getTableCellRendererComp
// is what leads to the horrendeous problems
// in the following lines of the else (not selected, that is normal background color)
 Color background = unselectedBackground != null
           ? unselectedBackground : table.getBackground();
 super.setBackground(background);

Seeing that, the way out (other than using SwingX and its flexible, clean, powerful, consistent .. :-) renderer support is @Hovercraft's but inverse: first do the custom coloring (or null if none intended) then call super:

  @Override
  public Component getTableCellRendererComponent(JTable table,
        Object value, boolean isSelected, boolean hasFocus, int row,
        int column) {
      if (myHighlightCondition) {
          setBackground(Color.RED);
      } else {
          setBackground(null);
      }
     super.getTableCellRendererComponent(table, value, isSelected, hasFocus,
           row, column);
     return this;
  }

(*) After all, this comment led to an answer, forgot that it's fixable on the custom renderer level :-)

BTW: Catching the "first" call to the renderer is highly brittle, there is no guaratee on which cell that will happen, might well be the last row of the last column.

kleopatra
  • 51,061
  • 28
  • 99
  • 211
  • Thanks, I've fixed this on my implementation. It actually works out well with my `HashMap` storage as `null` is returned if no value is stored :D You should consider making a community wiki QA about this. – Robadob Aug 07 '13 at 15:50
6

You forgot your else part of your if block, the code that paints the background to the default if it is not the important row:

        if (row == 0 && column == 0) {
           d.setBackground(new java.awt.Color(255, 72, 72));
        } else {
           d.setBackground(null);
        }

My SSCCE

import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.*;
import javax.swing.table.DefaultTableCellRenderer;

public class TestJTable {
   private static int highlightedRow = 0;
   private static void createAndShowGui() {
      String[] columnNames = {"Program"};
      Object[][] rowData = {{"Row 1"}, {"Row 2"}, {"Row 3"}, {"Row 4"}, 
            {"Row 1"}, {"Row 2"}, {"Row 3"}, {"Row 4"}, 
            {"Row 1"}, {"Row 2"}, {"Row 3"}, {"Row 4"}};
      final JTable myTable = new JTable(rowData , columnNames );
      myTable.setDefaultRenderer(Object.class, new DefaultTableCellRenderer()
      {
         @Override
         public Component getTableCellRendererComponent(JTable table,
               Object value, boolean isSelected, boolean hasFocus, int row,
               int column) {
            Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus,
                  row, column);
            if (row == highlightedRow && column == 0) {
               c.setBackground(new java.awt.Color(255, 72, 72));
            } else {
               c.setBackground(null);
            }
            return c;
         }
      });


      JFrame frame = new JFrame("TestJTable");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.getContentPane().add(new JScrollPane(myTable));
      frame.pack();
      frame.setLocationRelativeTo(null);
      frame.setVisible(true);

      new Timer(1000, new ActionListener() {

         @Override
         public void actionPerformed(ActionEvent arg0) {
            highlightedRow++;
            int rowCount = myTable.getRowCount();
            highlightedRow %= rowCount;
            myTable.repaint();
         }
      }).start();
   }

   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            createAndShowGui();
         }
      });
   }
}
Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
  • 1
    +1 great code as you are, but there are (not important) two points 1) I think that for whatever related with Model or View is there [prepareRenderer](http://tips4java.wordpress.com/2010/01/24/table-row-rendering/), 2) hmmm why not test for isSelected and hasFocus – mKorbel Mar 07 '12 at 21:51
  • 1
    @mKorbel: you know JTables better than I do. You may want to show your own SSCCE here so we can all be educated on this. – Hovercraft Full Of Eels Mar 07 '12 at 21:53
  • that9s about down_votes, most of them I remember..., I think that OP's get two great answers, why pour water into the sea from the cup – mKorbel Mar 07 '12 at 22:14
  • Ash's earlier question suggests that overriding `prepareRenderer()` may also be helpful. This [answer](http://stackoverflow.com/a/6057983/230513) leads to a [comparison](http://stackoverflow.com/a/5799016/230513) that includes helpful comments. – trashgod Mar 08 '12 at 01:10
  • 1
    see my musings as to why this has a very slight drawback :-) – kleopatra Mar 08 '12 at 13:06
  • @Hovercraft Full Of Eels that's an answer, isn't it – mKorbel Mar 08 '12 at 13:29
4

Add an else clause to your if:

if ((row == 0) && (column == 0)) {
    d.setBackground(new java.awt.Color(255, 72, 72));
}
else {
    d.setBackground(Color.WHITE);
}

Remember that the same renderer instance is used to paint all the cells.

JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • This works. Since you answered first I will award you the answer. – Ashley Mar 07 '12 at 19:33
  • Sorry Eels if I could choose both I would. I +1 your answer also. – Ashley Mar 07 '12 at 19:37
  • In fact, I didn't answer first. – JB Nizet Mar 07 '12 at 19:39
  • One small question. How does the renderer satisfy the if statement for cells which are not (0,0)? I don't understand why the color is applied to all cells. – Ashley Mar 07 '12 at 19:39
  • @Ash: don't lose any sleep over this. I think that both JB and I have more than enough points to go around. Now if you could get free beer and pizza from these points, that would be a different matter altogether. – Hovercraft Full Of Eels Mar 07 '12 at 19:40
  • 2
    You modify the background color when the renderer renders the first cell. The same instance is then reused to render all the other cells, and since you don't reset the background color for them, it keeps the one you set for the first cell. – JB Nizet Mar 07 '12 at 19:41
  • 1
    @Ash: The renderer is first applied to the 0th row, and then applied to the others. Since it has already been set to use a red color background, the renderer doesn't change the color. Understand that you're not seeing several JLabels here, but instead you're seeing *one* JLabel that is used as a rubber stamp to be displayed as if it were different labels. – Hovercraft Full Of Eels Mar 07 '12 at 19:42
  • @Ash see my musings as to why this has a very slight drawback :-) – kleopatra Mar 08 '12 at 13:06