Search code examples
javaswingjtabletablecellrenderer

How to make JLabel still appear after JTable column swap in the runtime?


I have a JTable:

enter image description here

after I swap columns unfortunately the flags are not rendered anymore:

enter image description here

I assume it is the fault of my getColumnClass method where I have fixed class for every column, but I don't know how to fix this issue.

package zad1;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.Arrays;
import java.util.Vector;

import javax.imageio.ImageIO;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableModel;

public class CountryTable extends JTable {




    public CountryTable(String countriesFileName) {
        Vector<String> columnNames = new Vector<String>();
        Vector<Vector<Object>> data = new Vector<Vector<Object>>();
        try {
            BufferedReader br = new BufferedReader(new FileReader(countriesFileName));
            columnNames.addAll(Arrays.asList(br.readLine().split("\t")));
            System.out.println(columnNames);
            String line;
            while ((line = br.readLine()) != null) {
                String[] attributes = line.split("\t");
                Vector<Object> rowData = new Vector<Object>();
                rowData.add(attributes[0]);
                rowData.add(attributes[1]);
                rowData.add(Long.valueOf(attributes[2]));
                BufferedImage icon = ImageIO.read(new File("./data/" + attributes[3]));
                double ratio = (double) icon.getWidth() / icon.getHeight();
                rowData.add(new ImageIcon(icon.getScaledInstance(30, 20, Image.SCALE_FAST)));
                data.add(rowData);
            }
            br.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        setModel(new MyTableModel(data, columnNames));
        setRowHeight(30);
        getColumnModel().getColumn(2).setCellRenderer(new PopulationCellRenderer());
        for (int i = 0; i < getColumnCount(); i++) {
            getColumnModel().getColumn(i).setWidth(400);
        }
    }

    @Override
    public Class getColumnClass(int column) {
        switch (column) {
        case 0:
            return String.class;// Panstwo
        case 1:
            return String.class;// stolica
        case 2:
            return Long.class;// ludnosc
        case 3:
            return Icon.class; // flaga jakiej klasy jest dana komorka w danej kolumnie aby tabela ja poprawnie wyswietlila
        default:
            return String.class;
        }
    }


    class MyTableModel extends DefaultTableModel {
        public MyTableModel(Vector<Vector<Object>> data, Vector<String> columnNames) {
            super(data, columnNames);//wywolanie konstruktora z nadklasy
            System.out.println(columnNames);
        }

        @Override
        public boolean isCellEditable(int row, int column) {
            return column == 3;
        }
    }

    class PopulationCellRenderer extends JLabel implements TableCellRenderer {

        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
            Long population = (Long) value;
            setHorizontalAlignment(JLabel.RIGHT);
            if (population > 20000000) {
                setForeground(Color.red);
            } else {
                setForeground(Color.BLACK);
            }
            setText(population + "");
            return this;
        }

    }
}

Solution

  • There's no need to extend JTable for this. Your example overrides getColumnClass() in the view, when it should do so in the model. The default renderer for Icon.class will be applied wherever the column is moved.

    private final TableModel model = new DefaultTableModel(data, columnNames) {
    
        @Override
        public Class<?> getColumnClass(int column) {
            switch (column) {
                 …
            }
        }
    };
    

    This will also make it possible to apply the PopulationCellRenderer on a per-type basis:

    table.setDefaultRenderer(Long.class, new PopulationRenderer());
    

    Also consider extending DefaultTableCellRenderer, as shown here, using a NumberFormat, as shown below. Based on an example by @mKorbel, the self-contained code below shows a corrected TableModel and updated TableCellRenderer.

    image

    import java.awt.*;
    import java.text.NumberFormat;
    import javax.swing.*;
    import javax.swing.table.*;
    
    public class TableExample {
    
        private static final long CUSP = 20000000;
        private JFrame frame = new JFrame("Table Demo");
        private Icon errorIcon = (Icon) UIManager.getIcon("OptionPane.errorIcon");
        private Icon infoIcon = (Icon) UIManager.getIcon("OptionPane.informationIcon");
        private Icon warnIcon = (Icon) UIManager.getIcon("OptionPane.warningIcon");
        private String[] columnNames = {"String", "Long", "Float", "Double", "Boolean", "Icon"};
        private Object[][] data = {
            {"aaa", CUSP - 1, 12.15F, 100.05, true, (errorIcon)},
            {"bbb", CUSP, 7.154F, 6.1555, false, (infoIcon)},
            {"ccc", CUSP + 1, 0.1135F, 3.1455, true, (warnIcon)},
            {"ddd", 42L, 31.15F, 10.05, true, (errorIcon)},
            {"eee", 12345L, 5.154F, 16.1555, false, (infoIcon)},
            {"fff", 54321L, 4.1135F, 31.1455, true, (warnIcon)}};
        private final TableModel model = new DefaultTableModel(data, columnNames) {
    
            @Override
            public Class<?> getColumnClass(int column) {
                switch (column) {
                    case 0:
                        return String.class;
                    case 1:
                        return Long.class;
                    case 2:
                        return Float.class;
                    case 3:
                        return Double.class;
                    case 4:
                        return Boolean.class;
                    case 5:
                        return Icon.class;
                    default:
                        return String.class;
                }
            }
        };
        private final JTable table = new JTable(model);
    
        private static class PopulationRenderer extends DefaultTableCellRenderer {
    
            NumberFormat f = NumberFormat.getInstance();
    
            @Override
            public Component getTableCellRendererComponent(JTable table, Object value,
                boolean isSelected, boolean hasFocus, int row, int col) {
                JLabel r = (JLabel) super.getTableCellRendererComponent(
                    table, value, isSelected, hasFocus, row, col);
                if (col == 1) {
                    r.setHorizontalAlignment(JLabel.RIGHT);
                    Long population = (Long) value;
                    if (population > CUSP) {
                        r.setForeground(Color.red);
                    } else {
                        r.setForeground(Color.BLACK);
                    }
                    r.setText(f.format(population));
                }
                return r;
            }
        }
    
        public TableExample() {
            int h = infoIcon.getIconHeight();
            table.setRowHeight(h);
            table.setPreferredScrollableViewportSize(
                new Dimension(table.getPreferredSize().width, 4 * h));
            table.setAutoCreateRowSorter(true);
            table.setDefaultRenderer(Long.class, new PopulationRenderer());
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.add(new JScrollPane(table));
            frame.pack();
            frame.setLocationByPlatform(true);
            frame.setVisible(true);
        }
    
        public static void main(String[] args) {
            EventQueue.invokeLater(() -> {
                TableExample tableExample = new TableExample();
            });
        }
    }