Search code examples
javaswinguser-interfacejtable

JTable becomes opaque even if specified otherwise, because of DefaultTableModel getColumnClass overrides


My project requires that a table be drawn as transparent to reveal an image underneath. When I override the function getColumnClass in DefaultTableModel, it seems like table.setOpaque(false) no longer does anything. I need each cell to only draw images within, which works fine. Here is the table model:

JTable table = new JTable(model) {
            @Override
            public boolean isCellEditable(int row, int column) {
                //all cells false
                return false;
            }
            @Override
            public Class<?> getColumnClass(int column) {
                return ImageIcon.class;
            }
        };

There is a small portion of the table visible while scrolling, but the moment the table updates when I move it around or click on it, it becomes opaque.

I have a simple goal, where i am to overlay a translucent image in a cell, over an image underneath the table, as if like a selection. The image would toggle off and on and is handled in this mouse adapter:

@Override
public void mouseClicked(MouseEvent e) {
    int row = table.rowAtPoint(e.getPoint());//get mouse-selected row
    int col = table.columnAtPoint(e.getPoint());//get mouse-selected col

    String string = row+"|"+col;

    if(selectedCells.contains(string)){
    
        //cell was already selected, deselect it
        selectedCells.remove(string);
        table.setValueAt(null, row, col);
        System.out.println("Removed Cell " + string);
        
    } else {
    
        //cell was not selected
        selectedCells.add(string);
        table.setValueAt(PrimaryWindow.selection, row, col);
        System.out.println("Added Cell " + string);
    }
}

I paint the image underneath by overriding the panel the table is in, which looks like this:

JPanel background = new JPanel( new BorderLayout() )
        {
            @Override
            protected void paintComponent(Graphics g)
            {
                super.paintComponent(g);

                g.drawImage(icon.getImage(), 0, 0, scale*32, scale*32, this);
            }
        };

And then add the table in above: background.add(table);

I do have this set:

DefaultTableCellRenderer renderer = (DefaultTableCellRenderer)table.getDefaultRenderer(Object.class);
        renderer.setOpaque(false);

And this set:

table.setOpaque(false);

Here is what the wrong, but current output looks like after clicking a bunch of places

Here is what it looks like if i remove the custom renderer

Here is what it should look like (i made it in paint)

Simply clicking the mouse does put an image inside the table at the cell I click, which is exactly what I want, as well as removing it upon clicking the same spot again. However, it is useless until I can see what is underneath. How can I have the table be transparent, and also display my images over the panel underneath?

EDIT: Here is a scratch file with the problem in full display. Replace the

import javax.swing.*;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import java.util.LinkedList;

class Scratch {

    static LinkedList<String> selectedCells = new LinkedList<>();

    public static void main(String[] args) {
        JFrame frame = new JFrame();
        frame.setSize(512,512);

        //Make a red square
        BufferedImage image = new BufferedImage(1,1, BufferedImage.TYPE_4BYTE_ABGR);
        image.setRGB(0,0, Color.RED.getRGB());

        //Scale it to the size of the window
        ImageIcon icon = new ImageIcon(image.getScaledInstance(512, 512, 1));

        image.setRGB(0,0, new Color(0x49000000, true).getRGB());

        ImageIcon selection =  new ImageIcon(image.getScaledInstance(16, 16, 1));

        Object[][] data = new Object[32][32];
        String[] names = new String[32];
        DefaultTableModel model = new DefaultTableModel(data, names);


        JTable table = new JTable(model) {
            @Override
            public boolean isCellEditable(int row, int column) {
                //all cells false
                return false;
            }

            @Override
            public Class<?> getColumnClass(int column) {
                //Removing 'ImageIcon.class' and replacing it with 'Object.class' makes the table transparent, however does not display the image.
                //Keeping this now allows us to put in images into the table, but removes the red square from the background and makes the table opaque
                return ImageIcon.class;
            }
        };
        //Set the entire table to transparent
        table.setOpaque(false);
        //Set the renderer to transparent also
        DefaultTableCellRenderer renderer = (DefaultTableCellRenderer)table.getDefaultRenderer(Object.class);
        renderer.setOpaque(false);

        MouseListener tableMouseListener = new MouseAdapter() {

            @Override
            public void mouseClicked(MouseEvent e) {
                int row = table.rowAtPoint(e.getPoint());
                int col = table.columnAtPoint(e.getPoint());

                String string = row+"|"+col;

                if(selectedCells.contains(string)){
                    //cell was already selected, deselect it
                    selectedCells.remove(string);
                    table.setValueAt(null, row, col);
                    System.out.println("Removed Cell " + string);
                }else{
                    //cell was not selected
                    selectedCells.add(string);
                    table.setValueAt(selection, row, col);
                    System.out.println("Added Cell " + string);
                }
                System.out.println(selectedCells);
            }
        };


        table.addMouseListener(tableMouseListener);
        table.setName("name");

        JPanel background = new JPanel( new BorderLayout() )
        {
            @Override
            protected void paintComponent(Graphics g)
            {
                super.paintComponent(g);

                g.drawImage(icon.getImage(), 0, 0, 512, 512, this);
            }
        };
        background.setSize(512,512);
        frame.add(background);
        background.add(table);

        frame.setVisible(true);
    }
}

Solution

  • While playing around with your code, I had issues when ever I used your JTable extensions...

    JTable table = new JTable(model) {
        @Override
        public boolean isCellEditable(int row, int column) {
            //all cells false
            return false;
        }
    
        @Override
        public Class<?> getColumnClass(int column) {
            //Removing 'ImageIcon.class' and replacing it with 'Object.class' makes the table transparent, however does not display the image.
            //Keeping this now allows us to put in images into the table, but removes the red square from the background and makes the table opaque
            return ImageIcon.class;
        }
    };
    

    This kind of makes me cringe anyway.

    If you want to change the class type of the cell, then you really should be doing this from the TableModel, that's its responsibility.

    I'd also, personally, supply my own TableCellRenderer, this way I gain full control over it's functionality directly

    For example...

    enter image description here

    import java.awt.BorderLayout;
    import java.awt.Color;
    import java.awt.Component;
    import java.awt.Dimension;
    import java.awt.EventQueue;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.Image;
    import java.awt.event.MouseAdapter;
    import java.awt.event.MouseEvent;
    import java.awt.image.BufferedImage;
    import java.io.IOException;
    import java.util.LinkedList;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    import javax.imageio.ImageIO;
    import javax.swing.Icon;
    import javax.swing.ImageIcon;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.JScrollPane;
    import javax.swing.JTable;
    import javax.swing.table.DefaultTableCellRenderer;
    import javax.swing.table.DefaultTableModel;
    
    public class Main {
    
        public static void main(String[] args) {
            new Main();
        }
    
        public Main() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    try {
                        Object[][] data = new Object[32][32];
                        String[] names = new String[32];
                        DefaultTableModel model = new DefaultTableModel(data, names) {
                            @Override
                            public Class<?> getColumnClass(int columnIndex) {
                                return Icon.class;
                            }
    
                            @Override
                            public boolean isCellEditable(int row, int column) {
                                return false;
                            }                        
                        };
    
                        JTable table = new JTable(model);
                        table.setOpaque(false);
    
                        table.setDefaultRenderer(Icon.class, new TransparentTableCellRenderer());
    
                        JScrollPane scrollPane = new JScrollPane(table);
                        scrollPane.setOpaque(false);
                        scrollPane.getViewport().setOpaque(false);
    
                        LinkedList<String> selectedCells = new LinkedList<>();
    
                        BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB);
                        Graphics2D g2d = img.createGraphics();
                        g2d.setColor(Color.RED);
                        g2d.fillRect(0, 0, 100, 100);
                        g2d.dispose();
    
                        Icon icon = new ImageIcon(img);
    
                        table.addMouseListener(new MouseAdapter() {
                            @Override
                            public void mouseClicked(MouseEvent e) {
                                int row = table.rowAtPoint(e.getPoint());
                                int col = table.columnAtPoint(e.getPoint());
    
                                String string = row + "|" + col;
    
                                if (selectedCells.contains(string)) {
                                    //cell was already selected, deselect it
                                    selectedCells.remove(string);
                                    table.setValueAt(null, row, col);
                                    System.out.println("Removed Cell " + string);
                                } else {
                                    //cell was not selected
                                    selectedCells.add(string);
                                    table.setValueAt(icon, row, col);
                                    System.out.println("Added Cell " + string);
                                }
                            }
                        });
    
                        JFrame frame = new JFrame();
                        // Supply your own back ground image
                        frame.setContentPane(new BackgroundPane(ImageIO.read(Main.class.getResource("/images/Mando01.jpeg"))));
                        frame.add(scrollPane);
                        frame.pack();
                        frame.setLocationRelativeTo(null);
                        frame.setVisible(true);
                    } catch (IOException ex) {
                        Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            });
        }
    
        public class TransparentTableCellRenderer 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);
                setOpaque(false);
                if (value instanceof Icon) {
                    setIcon((Icon)value);
                } else {
                    setIcon(null);
                }
                return this;
            }
    
        }
    
        public class BackgroundPane extends JPanel {
    
            private Image background;
    
            public BackgroundPane(Image background) {
                setLayout(new BorderLayout());
                this.background = background;
            }
    
            @Override
            public Dimension getPreferredSize() {
                return background == null ? new Dimension(200, 200) : new Dimension(background.getWidth(this), background.getHeight(this));
            }
    
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                Graphics2D g2d = (Graphics2D) g.create();
                g2d.drawImage(background, 0, 0, this);
                g2d.dispose();
            }
    
        }
    }