Home > Enterprise >  JTable with complex formatting
JTable with complex formatting

Time:03-31

I need to make a JTable where each "cell" has a variety of separate data to display. I need to show, for each item, both as a percent of total and a dollar value, a target value, actual value, projected value, and the difference between any of the three. The attached image was done in Excel, but shows what I'm trying to produce in a JTable. enter image description here

To make things even more interesting, other than the target and actual value rows, all other rows are selectable by the user at run time. As in they can choose what rows to display by selecting options within my program. I'll need to be able to customize cell shading, font and text format on a subcell-by-subcell basis, and I'll also need to vary borders, row heights and column widths throughout the table.

I've not started coding this yet as I'm contemplating different methods. I'm looking for feedback from others as to which approach to take, or if there are other options I should consider.

Option 1 - Just use a single JTable with a custom data model, but otherwise stay as close as possible to the default cell renderers and cell editors, and get fancy with boarders and formatting of each cell to mimic the intended look-n-feel. The data model would handle the translation from the row, column provided by the JTable and what item/value each cell actually corresponds to.

Option 2 - Use a JTable as the cell editor for each cell within the JTable (a table within a table).

Option 3 - Write my own custom cell renderer using a variety of labels and text fields.

Option 4 - Are there any pre-written components that offer the kind of flexibility I'm looking for I should consider?

Thoughts on the best approach to take appreciated.

Thanks.

CodePudding user response:

I'm going with Option 2A - thank, Gilbert. The code below and image attached are a proof-of-concept. Formatting is rudimentary at the moment but as a demonstration project it works.

package tableTest;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.text.DecimalFormat;

import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingConstants;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumnModel;




class Grid {

    JFrame frame;
    /** Data array in Row,Col array order */
    MyData[][] myData;
    Double totalValue = 1234567d;
    
    public Double getTotalValue() {
        return totalValue;
    }
    
    /** Constructor */
    public Grid() {
                
        MyData[][] myData1 = {
                {new MyData(0.060, 0.052, 0.058, "Red", "Square", this), new MyData(0.040, 0.038, 0.040, "Red", "Circle", this), new MyData(0.045, 0.045, 0.045, "Red", "Triangle", this), new MyData(0.080, 0.084, 0.081, "Red", "Octagon", this)},
                {new MyData(0.160, 0.142, 0.172, "Blue", "Square", this), new MyData(0.120, 0.135, 0.123, "Blue", "Circle", this), new MyData(0.000, 0.000, 0.000, "Blue", "Triangle", this), new MyData(0.060, 0.050, 0.052, "Blue", "Octagon", this)},
                {new MyData(0.080, 0.072, 0.079, "Green", "Square", this), new MyData(0.010, 0.023, 0.011, "Green", "Circle", this), new MyData(0.090, 0.120, 0.099, "Green", "Triangle", this), new MyData(0.110, 0.105, 0.110, "Green", "Octagon", this)},
                {new MyData(0.000, 0.000, 0.000, "Pink", "Square", this), new MyData(0.080, 0.065, 0.078, "Pink", "Circle", this), new MyData(0.030, 0.010, 0.028, "Pink", "Triangle", this), new MyData(0.035, 0.060, 0.024, "Pink", "Octagon", this)},
        };
        myData = myData1;
        
        initialize();
    }
    
    /** Initialize console */
    private void initialize() {
        
        final int ROW_HEIGHT = 24;
        
        frame = new JFrame("Grid Window");
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.setSize(800, 800);
        frame.setLayout(new GridLayout(1, 1));  
        frame.setLocationRelativeTo(null);
        
        Font defaultFont = javax.swing.UIManager.getDefaults().getFont("Label.font");
        
        JPanel tablePanel = new JPanel();
        tablePanel.setLayout(new GridLayout(4, 4));
        
        for (int r = 0; r < 4; r  ) {
            for (int c = 0; c < 4; c  ) {
                
                // cellPanel represents one of our "cells" in tabelPanel - it encloses the table, header and super-header
                JPanel cellPanel = new JPanel();
                cellPanel.setLayout(new BorderLayout());
                cellPanel.setBorder(BorderFactory.createMatteBorder(1, 1, 1, 1, Color.black));
                
                // Create the super-header that is the title for each cell's data
                JLabel cellLabel = new JLabel(myData[r][c].getColor()   " "   myData[r][c].getShape());
                cellLabel.setFont(defaultFont.deriveFont(Font.BOLD));
                cellLabel.setHorizontalAlignment(SwingConstants.CENTER);
                cellLabel.setPreferredSize(new Dimension(102, ROW_HEIGHT));
                cellLabel.setMinimumSize(new Dimension(102, ROW_HEIGHT));
                // Add to cellPanel
                cellPanel.add(cellLabel, BorderLayout.NORTH);
                
                // Create the table
                MyTableModel cellDataModel = new MyTableModel(myData[r][c], this);
                MyCellRenderer cellRender = new MyCellRenderer();
                JTable cellTable = new JTable(cellDataModel);
                cellTable.setDefaultRenderer(Object.class, cellRender);
                cellTable.getTableHeader().setMinimumSize(new Dimension(102, ROW_HEIGHT));
                cellTable.setRowHeight(ROW_HEIGHT);
                cellTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
                cellTable.setMinimumSize(new Dimension(102, ROW_HEIGHT));
                TableColumnModel tcm = cellTable.getColumnModel();
                tcm.getColumn(0).setPreferredWidth(120);
                tcm.getColumn(1).setPreferredWidth(80);
                tcm.getColumn(2).setPreferredWidth(80);
                tcm.getColumn(0).setMinWidth(120);
                tcm.getColumn(1).setMinWidth(80);
                tcm.getColumn(2).setMinWidth(80);
                cellTable.setFillsViewportHeight(true);
                
                // Since we're not putting the table into a scroll pane, we need to explicitly add the header
                // or it won't show. We'll use another panel for this.
                JPanel dataPanel = new JPanel();
                dataPanel.setLayout(new BorderLayout());
                // Add the header
                dataPanel.add(cellTable.getTableHeader(), BorderLayout.NORTH);
                // Add the table
                dataPanel.add(cellTable, BorderLayout.CENTER);
                
                // Add dataPanel to the cell panel
                cellPanel.add(dataPanel, BorderLayout.CENTER);
                
                // Add cellPanel to the tablePlanel
                tablePanel.add(cellPanel);
                
                System.out.println(Integer.toString(tcm.getColumn(0).getWidth()));
            }
        }
        
        // We now have a panel with our home-made grid - add it to a scroll pane
        JScrollPane spTable = new JScrollPane(tablePanel);
        spTable.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
        spTable.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
        
        frame.add(spTable);
        
    }

    public void show() {
        frame.setVisible(true);
    }
    
    
    private class MyCellRenderer extends DefaultTableCellRenderer {
        
        DecimalFormat currencyFormatter = new DecimalFormat("$#,###.00");
        DecimalFormat decimalFormatter = new DecimalFormat("00.0%");

        protected MyCellRenderer() {
            super();
            
        }

        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
                int row, int column) {
            
            // First, adjust the value
            String newValue;
            if (column == 0) {
                newValue = (String) value;
            } else if (column == 1) {
                newValue = currencyFormatter.format(value);
            } else {
                newValue = decimalFormatter.format(value);
            }
            
            // Call super
            JComponent c = (JComponent) super.getTableCellRendererComponent(table, newValue, isSelected, hasFocus, row, column);
            JLabel cl = (JLabel) c;
            
            // Adjust formatting
            switch (column) {
            case 0:
                // row header
                cl.setBackground(Color.LIGHT_GRAY);
                cl.setHorizontalAlignment(SwingConstants.LEFT);
                break;
            case 1:
                // value column
                cl.setBackground(Color.white);
                cl.setHorizontalAlignment(SwingConstants.RIGHT);
                break;
            case 2:
                cl.setBackground(Color.white);
                cl.setHorizontalAlignment(SwingConstants.CENTER);
                break;
            default:
                // Something we don't expect
                throw new IllegalArgumentException("column unexpected.");
            }
                        
            return cl;
        }
        
    }
    
    
    private class MyTableModel extends DefaultTableModel {
        
        MyData thisData;
        Grid parent;
        private static final String[] rowNames = {"Target", "Actual", "Actual - Target", "Projected", "Projected - Target", "Projected - Actual"};
        private static final String[] columnNames = {"", "Value", "%"};

        protected MyTableModel(MyData dataItem, Grid parent) {
            super();
            thisData = dataItem;
            this.parent = parent;
        }
        
        @Override
        public int getRowCount() {
            return rowNames.length;
        }

        @Override
        public int getColumnCount() {
            return columnNames.length;
        }

        @Override
        public String getColumnName(int column) {
            return columnNames[column];
        }
        
        @Override
        public Class<?> getColumnClass(int col) {
            return String.class;
        }

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

        @Override
        public Object getValueAt(int row, int column) {
            Double ret;
            Double mult;
            
            // Evaluate the column
            switch (column) {
            case 0:
                // row header
                return rowNames[row];
            case 1:
                // value column
                mult = parent.getTotalValue();
                break;
            case 2:
                mult = 1d;
                break;
            default:
                // Something we don't expect
                throw new IllegalArgumentException("column greater than 2");
            }
            
            // Evaluate the row
            switch (row) {
            case 0:
                // Target
                ret = thisData.getTargetPct() * mult;
                break;
            case 1:
                // Actual
                ret = thisData.getActualPct() * mult;
                break;
            case 2:
                // Actual - Target
                ret = (thisData.getActualPct() - thisData.getTargetPct()) * mult;
                break;
            case 3:
                // Projected
                ret = thisData.getPlanPct() * mult;
                break;
            case 4:
                // Projected - Target
                ret = (thisData.getPlanPct() - thisData.getTargetPct()) * mult;
                break;
            case 5:
                // Projected - Actual
                ret = (thisData.getPlanPct() - thisData.getActualPct()) * mult;
                break;
            default:
                throw new IllegalArgumentException("row greater than our size");    
            }
            
            return ret;
        }

        @Override
        public void setValueAt(Object aValue, int row, int column) {
            super.setValueAt(aValue, row, column);
        }
    }
    
    private class MyData {
        
        Double pTarget;
        Double pActual;
        Double pPlan;
        String color;
        String shape;
        Grid parent;
        
        
        protected String getColor() {
            return color;
        }
        protected void setColor(String color) {
            this.color = color;
        }
        protected String getShape() {
            return shape;
        }
        protected void setShape(String shape) {
            this.shape = shape;
        }
        protected Double getTargetPct() {
            return pTarget;
        }
        protected void setTargetPct(Double pTarget) {
            this.pTarget = pTarget;
        }
        protected Double getActualPct() {
            return pActual;
        }
        protected void setActualPct(Double pActual) {
            this.pActual = pActual;
        }
        protected Double getPlanPct() {
            return pPlan;
        }
        protected void setPlanPct(Double pPlan) {
            this.pPlan = pPlan;
        }
        
        
        protected MyData(Double pTarget, Double pActual, Double pPlan, String color, String shape, Grid parent) {
            super();
            this.pTarget = pTarget;
            this.pActual = pActual;
            this.pPlan = pPlan;
            this.color = color;
            this.shape = shape;
            this.parent = parent;
        }
        protected MyData(Double pTarget, Double pActual, Double pPlan, Grid parent) {
            super();
            this.pTarget = pTarget;
            this.pActual = pActual;
            this.pPlan = pPlan;
            this.parent = parent;
        }
        
        protected MyData(Grid parent) {
            super();
            this.parent = parent;
        }
    }
}   // END OF GRID




public class TableTest {
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                Grid runwindow = new Grid();
                runwindow.show();
            }
        });
    }
}

And an image

Screen shot

Thanks.

  • Related