Rendering BufferedImage in JTable cell

2019-01-15 22:48发布

问题:

I need to display a BufferedImage in one JTable column. I overwrote JTable method

@Override
public Class<?> getColumnClass(int column) {
    if (column == 1){
        return BufferedImage.class;
    }
    return super.getColumnClass(column);
}

But I am still obtaining String representation of the object instead of image itself.Does anyone have idea what I am missing?

回答1:

I'd populate the column that needs to show an image with ImageIcons and have the getColumnClass() method return Icon.class, and then render it with a JLabel that displays the Icon. In fact, I believe that the DefaultCellRenderer really is a JLabel, and so it should already know how to handle Icons.

Yep, all the model needs is to know that it holds Icons. For example, this code below works in the program below:

  DefaultTableModel model = new DefaultTableModel(COL_NAMES, 0) {
     @Override
     public Class<?> getColumnClass(int column) {
        if (getRowCount() > 0) {
           return getValueAt(0, column).getClass();
        }

        return super.getColumnClass(column);
     }
  };

For example:

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.table.DefaultTableModel;

public class ImageColumnTest2 {
   public static final String IMAGE_SHEET_PATH = "http://speckycdn.sdm.netdna-cdn.com/"
         + "wp-content/uploads/2010/08/flag_icons_04.jpg";
   public static final String[] COUNTRIES = {
      "Denmark", "China", "Chile", "Canada", "Belgium", "Austria",
      "Argentina", "France", "Malaysina", "Lebanon", "Korea", "Japan",
      "Italy", "Ireland", "India", "Hong Kong", "Greece", "Germany"
   };
   public static final int COLS = 6;
   public static final int ROWS = 3;
   private static final String[] COL_NAMES = {"Country", "Flag"};

   private JTable table = new JTable();
   private JScrollPane mainPane = new JScrollPane(table);

   public ImageColumnTest2() throws IOException {
      DefaultTableModel model = new DefaultTableModel(COL_NAMES, 0) {
         @Override
         public Class<?> getColumnClass(int column) {
            if (getRowCount() > 0) {
               return getValueAt(0, column).getClass();
            }

            return super.getColumnClass(column);
         }
      };
      URL url = new URL(IMAGE_SHEET_PATH);
      BufferedImage img = ImageIO.read(url);
      int x1 = 15;  // sorry about the magic numbers
      img = img.getSubimage(x1, 0, img.getWidth() - 2 * x1, img.getHeight());

      int y1 = 20 ;  // ditto!
      int w = img.getWidth() / COLS;
      int h = img.getHeight() / ROWS;
      for (int row = 0; row < ROWS; row++) {
         int y = (row * img.getHeight()) / ROWS;
         for (int col = 0; col < COLS; col++) {
            int x = (col * img.getWidth()) / COLS;
            BufferedImage subImg = img.getSubimage(x, y, w, h);

            subImg = subImg.getSubimage(x1, 0, subImg.getWidth() - 2 * x1, subImg.getHeight() - y1);

            ImageIcon icon = new ImageIcon(subImg);
            String country = COUNTRIES[col + row * COLS];
            Object[] rowData = {country, icon};
            model.addRow(rowData);
         }
      }


      table.setModel(model);
      table.setRowHeight(((ImageIcon)model.getValueAt(0, 1)).getIconHeight());
   }

   public JComponent getMainComponent() {
      return mainPane;
   }

   private static void createAndShowGui() {
      ImageColumnTest2 imgColumnTest = null;
      try {
         imgColumnTest = new ImageColumnTest2();
      } catch (MalformedURLException e) {
         e.printStackTrace();
         System.exit(-1);
      } catch (IOException e) {
         e.printStackTrace();
         System.exit(-1);
      }

      JFrame frame = new JFrame("ImageColumnTest");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.getContentPane().add(imgColumnTest.getMainComponent());
      frame.pack();
      frame.setLocationByPlatform(true);
      frame.setVisible(true);
   }

   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            createAndShowGui();
         }
      });
   }
}


回答2:

There is no default TableCellRenderer for BufferedImage, you're going to have to supply one

Create yourself a new class that extends from DefaultTableCellRenderer. Override the getTableCellRendererComponent method

In this method, check the value been passed in is a BufferedImage, if it is, create a instance of ImageIcon, passing the BufferedImage into it.

Use the cell renderes setIcon method, passing the new instance of ImageIcon to it

With your table instance, use the setDefaultRenderer method to associate the cell renderer with the BufferedImage class

table.setDefaultRenderer(BufferedImage.class, myInstanceOfBufferedImageCellRenderer)

Check out Using Custom Renderers for more info

Added example

So a threw a quick example together using both ideas from myself and Hovercraft.

My personally feeling is that Hovercraft's idea will use less resources and be quicker then using a cell renenderer, so long as you create a ImageIcon once for each BufferedImage and maintain that reference.

You could get the custom cell renderer to do the same, but you would need to dabble with WeakHashMaps to maintain a reference between the BufferedImage and Icon and there is still the risk that the BufferedImage in question will never be collected, leaving the Icon reference hanging around.

If you weren't doing anything special with the BufferedImage in the way of rendering, I would use Hovercraft's suggest, purly from a ease of use and resource management perspective.

public class BufferedImageTableCellRenderer {

    public static void main(String[] args) {
        new BufferedImageTableCellRenderer();
    }

    public BufferedImageTableCellRenderer() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                File[] files = new File("some folder some where").listFiles(new FileFilter() {
                    @Override
                    public boolean accept(File pathname) {
                        String name = pathname.getName().toLowerCase();
                        return name.endsWith(".gif") || name.endsWith(".jpg") || name.endsWith(".png");
                    }
                });

                ImageTableModel model = new ImageTableModel();
                for (File file : files) {
                    try {
                        model.add(ImageIO.read(file));
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    }
                }

                JTable table = new JTable(model);
                table.setRowHeight(100);
                table.setDefaultRenderer(BufferedImage.class, new BufferedImageCellRenderer());

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new JScrollPane(table));
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class BufferedImageCellRenderer extends DefaultTableCellRenderer {
        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
            super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
            if (value instanceof BufferedImage) {
                setIcon(new ImageIcon((BufferedImage)value));
                setText(null);
            } else {
                setText("Bad image");
            }
            return this;
        }
    }

    public class ImageTableModel extends AbstractTableModel {

        private List<BufferedImage> images = new ArrayList<>(25);
        private List<Icon> icons = new ArrayList<>(25);

        @Override
        public int getRowCount() {
            return images.size();
        }

        public void add(BufferedImage image) {
            images.add(image);
            icons.add(new ImageIcon(image));
            fireTableRowsInserted(images.size() - 1, images.size() - 1);
        }

        @Override
        public int getColumnCount() {
            return 2;
        }

        @Override
        public Object getValueAt(int rowIndex, int columnIndex) {
            Object value = null;
            switch (columnIndex) {
                case 0:
                    value = images.get(rowIndex);
                    break;
                case 1:
                    value = icons.get(rowIndex);
                    break;
            }
            return value;
        }

        @Override
        public Class<?> getColumnClass(int columnIndex) {
            Class clazz = String.class;
            switch (columnIndex) {
                case 0:
                    clazz = BufferedImage.class;
                    break;
                case 1:
                    clazz = Icon.class;
                    break;
            }
            return clazz;
        }

        @Override
        public String getColumnName(int column) {
            String name = null;
            switch (column) {
                case 0:
                    name = "BufferedImage";
                    break;
                case 1:
                    name = "Icon";
                    break;
            }
            return name;
        }
    }
}