Search code examples
javaswingjtabletablecellrenderer

JTable renderer performance issue when using lots of Columns


Quick scenario.....

JTable has about 30 columns. Without attaching the Renderer, I can click on Column 30 and move it to any location and it is very very fast. No lag or anything.

Since most of the columns are unique and require their own type of rendering whether it be background color, Date format, font or whatever, the Renderers were assigned to specific columns instead of the Table. What happens now is there is a huge lag when moving columns to different locations.

Here is a rudimentary example with the Renderers commented out to first show how fast it is.

import java.awt.Color;
import java.awt.Component;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.TableCellRenderer;

public class LotsOfColumnsWithRenderer
{
    static JFrame f;
    static String data[][];
    static final int colums = 30;
    static final int rows = 100;
    static boolean skipThis = false;
    
      public static void main(String[] argv) throws Exception 
      {
          LotsOfColumnsWithRenderer x = new LotsOfColumnsWithRenderer();
      }

      public LotsOfColumnsWithRenderer()
      {
            f = new JFrame();
            populateData();
            String column[]={"Col 1","Col 2","Col 3", "Col 4", "Col 5", "Col 6", "Col 7","Col86","Col 9","Col 10","Col 11","Col 12","Col 13","Col 14","Col 15",
                    "Col 16","Col 17","Col 18","Col 19","Col 20","Col 21","Col 22","Col 23","Col 24","Col 25","Col 26","Col 27","Col 28","Col 29","Col 30",};         
        
            JTable table = new JTable(data,column);
            
            //for (int y=0; y < colums; y ++)
            //{
                //table.getColumnModel().getColumn(y).setCellRenderer(new MyTableCellRenderer());
            //}
            
            table.getTableHeader().addMouseListener(new MouseAdapter()
            {
                @Override
                public void mousePressed(MouseEvent e) 
                {
                    if (e.getButton() == MouseEvent.BUTTON1) 
                    {
                        skipThis = true;
                    }
                }

                @Override
                public void mouseReleased(MouseEvent e) 
                {
                    if (e.getButton() == MouseEvent.BUTTON1) 
                    {
                        skipThis = false;
                    }
                }
        
            });

            table.setBounds(30,40,200,300);          
            JScrollPane sp=new JScrollPane(table);    
            f.add(sp);  
            f.setSize(300,400);    
            f.setVisible(true); 
            
            MyTableCellRenderer xx = new MyTableCellRenderer();
            

      }
      

    private static void populateData() 
    {
        data = new String[rows][colums];
        
        for (int x=0; x < rows; x++)
        {
            for (int y=0; y < colums; y ++)
            {
                data[x][y] = "Data for ["+x+"]["+y+"]";
            }
        }
        
    }
    
    public class MyTableCellRenderer extends JLabel implements TableCellRenderer 
    {
        private static final long serialVersionUID = -6320757636864429128L;

        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
              boolean hasFocus, int rowIndex, int vColIndex) 
          {
              if (skipThis)
              {
                  System.out.println("Skipping");
                  return this;
              }
              
                setText(value.toString());
                setToolTipText((String) value);
                if (vColIndex == 1)
                    this.setBackground(Color.LIGHT_GRAY);
                else if (vColIndex == 3)
                    this.setBackground(Color.CYAN);
                else if (vColIndex == 7)
                    this.setBackground(Color.YELLOW);
                else if (vColIndex == 20)
                    this.setBackground(Color.GREEN);

                
                this.setOpaque(true);
                return this;
          }
    }
}

Run the above code.

Double click the top of the JFrame to maximize the window and see all 30 columns.

Click on the Col 30 column header and quickly move it all the way to the left and all the way to the right and notice it moves very quickly.

Now uncomment the following lines of code.

        //for (int y=0; y < colums; y ++)
        //{
            //table.getColumnModel().getColumn(y).setCellRenderer(new MyTableCellRenderer());
        //}

Run the application again and execute the same steps.

Notice this time there is a huge lag moving the column.

I did try setting a boolean value anytime the column heading was being moved to prevent the Renderer from executing but it still gets involved and didn't really seem to help.

Questions:

Is there a limit to the number of Renderers (or a rule to follow when implementing Renderers) before it starts affecting performance?

Due to the variety of column renderers and large number of fields is there a more efficient way to implement renderers?


Solution

  • is there a more efficient way to implement renderers?

    Extend the DefaultTableCellRenderer:

    //public class MyTableCellRenderer extends JLabel implements TableCellRenderer
    public class MyTableCellRenderer extends DefaultTableCellRenderer
    {
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int rowIndex, int vColIndex)
          {
                super.getTableCellRendererComponent(table, value, isSelected, hasFocus, rowIndex, vColIndex);
    
                //setText(value.toString());
                setToolTipText((String) value);
                if (vColIndex == 1)
                    this.setBackground(Color.LIGHT_GRAY);
                else if (vColIndex == 3)
                    this.setBackground(Color.CYAN);
                else if (vColIndex == 7)
                    this.setBackground(Color.YELLOW);
                else if (vColIndex == 20)
                    this.setBackground(Color.GREEN);
    
    
                //this.setOpaque(true);
                return this;
          }
    }
    

    The DefaultTableCellRenderer overrides various methods of the JLabel to make painting more efficient since it knows the component is used as a renderer and not a real component.

    For example when you invoke setText(..) the label doesn't need to revalidate() itself.

    Read the DefaultTableCellRenderer API for more information about the efficiencies.