Search code examples
javaswingiconsjtableabstracttablemodel

Update ImageIcon of JTable cell


I am creating my first JTable that requires me to create a custom AbstractTableModel, TableCellEditor, and DefaultTableCellRenderer. Given that I have not needed to create these before, I've made some significant progress in getting my table to behave as desired.

However, I am getting overwhelmed with all the different methods I am overriding, and am spinning my wheels trying to figure out how to modify the ImageIcon of a particular cell. The cell must contain a JLabel, as it needs both an ImageIcon as well as a text string. I can already set the initial ImageIcon (although I am probably doing it incorrectly), but I can't set an updated ImageIcon. Nothing fails, but no change is made.

In a general sense, what is the best way to get and set an icon to a JLabel cell of a JTable, assuming all of these models, editors, and renderers have already been instantiated?

My model has already been defined to return JLabel.class for these cells, if you're wondering, and I also do a fireTableCellUpdated(row, col) once the change has supposedly been made. If I do a System.out.println(getIcon()) before and after the update, I can even see the source has changed.

Here is some of the code (updated with URL/ImageIcon fix in place):

class MonitorTable extends JTable {
   MonitorTableModel model = new MonitorTableModel(rows, columnNames);
   setModel(model);
   ...
   public void setIconAt(ImageIcon icon, int row, int col) {
      model.setIconAt(icon, row, col);
   } // End setIconAt(ImageIcon, int, int)
   ...

   class MonitorTableModel extends AbstractTableModel {
      ...
      public void setIconAt(ImageIcon icon, int row, int col) {
         StatusTableCellRenderer cell =
            (StatusTableCellRenderer)getColumnModel().getColumn(col).getCellRenderer().
            getTableCellRendererComponent(myTableObject, null, false, false, row, col);

         System.out.println(cell.getIcon()); // Shows initial icon source
         cell.setIcon(icon);
         fireTableCellUpdated(row, col);     // Should update the table
         System.out.println(cell.getIcon()); // Shows new icon source
         System.out.println("Cell updated");
      } // End setIconAt(ImageIcon, int, int)
   } // End class MonitorTableModel

   public class StatusTableCellRenderer extends DefaultTableCellRenderer {
      public Component getTableCellRendererComponent(JTable table, Object value,
         boolean isSelected, boolean hasFocus, int row, int col) {

         setIcon(imgGray);
         setText((String)value);
         return this;
      } // End getTableCellRendererComponent(JTable, Object, boolean, boolean, int, int)
   } // End class StatusTableCellRenderer
} // End class MonitorTable

Solution

  • I fixed this by changing setIcon(imgGray) to if (getIcon() == null) setIcon(imgGray);.

    The issue is my getTableCellRendererComponent method was setting the icon to imgGray every time. Apparently my setIconAt method, which calls getTableCellRendererComponent, was being overridden, even though the "new" icon value was processed after the "old" value was (re)set.

    I ended up removing all my setIcon methods and moved the relevant logic into my StatusTableCellRenderer class. That way I pass the value of the cell and let the renderer do the icon setting based on that value. It makes more sense this way, and works beautifully. I have confirmed that initial setting and all subsequent updates are performing as expected.

    The logic of setting the icon is pretty simple -- set the predefined icon based on certain predefined threshold values.

    double val;
    if (getIcon() == null) setIcon(imgGray);       // Initialize
    if ((value == null) || (value == "")) {
       val = 0;
    } else {
       val = Double.parseDouble(value.toString());
    } // End if
    
    if (val <= THRESHOLD1) {
       setIcon(icon1);
    } else if (val <= THRESHOLD2) {
       setIcon(icon2);
    ...
    } // End if
    setText(value.toString());
    

    I was very concerned about suggestions to make brand new objects to use, when the default JLabel was exactly what I needed. It was both unnecessary and a potential performance hit to the JTable. Thank you all for your insight and assistance. This was driving me batty!