1

I am creating what is basically I copy of excel. The user enters data into a JTable, then that data is parsed, processed, and it displays the toString() method for the appropriate type of Cell, which is an interface that I have created several subclasses for.

When a user starts to edit a FormulaCell, I would like the cell to display the formula retrieved by a getFormula() method rather than the evaluated formula retrieved by the toString() method.

I explored custom cell editors, but I could not figure out which method to override. I think the code would be something like this inside of a custom cell editor or whatever I need create:

if (cell instanceof FormulaCell) {
  startingText = (FormulaCell) cell).getFormula();
}

Each cell in the JTable has a different class, so I can't really override DefaultTableModel's getColumnClass() Method and set a custom cell editor for the Formula Cell class.

For reference, here's the main part of my program:

import java.awt.*;
import java.awt.event.*;

import javax.swing.*;
import javax.swing.table.*;

import cell.*;

public class Program extends JPanel {

    private DefaultTableModel model;
    private JTable table;

    public static final int ASCII_SHIFT = 64, HEIGHT = 10, WIDTH = 7, ROW_HEIGHT = 40;
    public static final Dimension BUTTON_SIZE = new Dimension(70,30),
            TABLE_SIZE = new Dimension(780, 400);

    //makes program runnable
    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }

    public static void createAndShowGUI() {

        //sets title and does everything necessary to
        //create and show the GUI
        JFrame frame = new JFrame("TextExcel");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        Program contentPane = new Program();
        contentPane.setOpaque(true);
        frame.setContentPane(contentPane);
        frame.setResizable(false);
        frame.pack();
        frame.setVisible(true);
    }

    public Program() {

        //sets the layout
        super(new BorderLayout());

        //creates a String[] for column headers
        String[] letter = new String[WIDTH];
        for (int i = 0; i < letter.length; i++) {
            byte[] character = {(byte) (i + ASCII_SHIFT + 1)};
            letter[i] = new String(character);
        }

        //creates the table
        model = new DefaultTableModel(letter, HEIGHT);
        table = new JTable(model);

        //makes a cell parse the input when enter is pressed
        Action action = new AbstractAction()
        {
            public void actionPerformed(ActionEvent e)
            {
                TableCellListener tcl = (TableCellListener)e.getSource();
                int row = tcl.getRow();
                int column = tcl.getColumn();
                String input = (String) model.getValueAt(row, column);
                parse(input, row, column);
            }
        };
        @SuppressWarnings("unused")
        TableCellListener tcl = new TableCellListener(table, action);

        //centers the headers
        JTableHeader header = table.getTableHeader();
        header.setDefaultRenderer(new HeaderRenderer(table));
        header.setReorderingAllowed(false);

        //centers text in cells
        DefaultTableCellRenderer centerRenderer = new DefaultTableCellRenderer();
        centerRenderer.setHorizontalAlignment(SwingConstants.CENTER);
        table.setDefaultRenderer(Object.class, centerRenderer);

        //sets the height of rows
        for (int i = 0; i < HEIGHT; i++) {
            table.setRowHeight(i, ROW_HEIGHT);
        }

        //creates a scroll-pane for the table and numbers the rows
        JScrollPane scrollPane = new JScrollPane(table);
        JTable rowTable = new RowNumberTable(table);
        scrollPane.setRowHeaderView(rowTable);
        scrollPane.setCorner(JScrollPane.UPPER_LEFT_CORNER, rowTable.getTableHeader());
        add(scrollPane);

        //sizes the table
        table.setFillsViewportHeight(true);
        table.setPreferredScrollableViewportSize(TABLE_SIZE);

        //creates a panel to place buttons
        JPanel buttonPanel = new JPanel(new FlowLayout());

        //creates a button to clear the table
        JButton clearButton = new JButton("clear");
        clearButton.setPreferredSize(BUTTON_SIZE);
        clearButton.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e) 
            {
                clearTable();
            }
        });
        buttonPanel.add(clearButton);

        //message displayed when help button is pressed
        String helpMessage = "To construct a Cell, double click a cell, "
                + "enter the argument, and press enter.\n"
                + "To clear a Cell, press the \"clear\" button.\n\n"
                + "There are 5 subclasses of Cell:\n"
                + "StringCell, DateCell, NumberCell, "
                + "FormulaCell, and SumAvgCell.\n"
                + "All numbers are displayed as Fractions and all Cells displaying numbers extend\n"
                + "NumberCell. Any double entered will be converted to a Fraction.\n\n"
                + "* StringCells display text and are simply typed into the cell. If an input is\n"
                + "invalid for all other types of Cell, it will become a StringCell.\n"
                + "* DateCells display dates in the form (Month Day, Year). They are constructed\n"
                + "in the form (m/d/yy) or (m/d/yyyy). Extra zeroes in m and d are not necessary.\n"
                + "* NumberCells simply display Fractions and can be constructed from a double,\n"
                + "int, standard fraction, or mixed fraction.\n"
                + "* FormulaCells are constructed from any combination of operations(+-*/%),\n"
                + "values, parantheses, and references. Order of operations is supported. An\n"
                + "example of a valid input would be \"(A1 + 4.4 * b3) % C2 - 3_6/8\".\n"
                + "* SumAvgCells can be used to find the sum or average of a rectangular area of\n"
                + "cells. They are constructed in the form (command reference - reference).\n"
                + "The first reference is the top-left corner and the second reference is the\n"
                + "bottom-right corner. An example of a valid input would be \"sum A1 - B2\".\n"
                + "Another valid input would be \"avg C1 - C8\".";

        //creates a help button to display a helpful message
        JButton helpButton = new JButton("help");
        helpButton.setPreferredSize(BUTTON_SIZE);
        helpButton.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e) 
            {
                JOptionPane.showMessageDialog(getComponent(0), helpMessage, "HelpBox", 1);
            }
        });
        buttonPanel.add(helpButton);

        //creates a panel to place the table
        JPanel tablePanel = new JPanel(new BorderLayout());
        tablePanel.add(scrollPane, BorderLayout.CENTER);

        //adds the button and table panels
        add(tablePanel, BorderLayout.NORTH);
        add(buttonPanel, BorderLayout.SOUTH);
    }

    //parses user input to set a cell value
    public void parse(String input, int row, int column) {

        //initializes cell to be set
        Cell cell = null;

        //helpful variables
        String ref = "([a-g]\\d*)";
        String dub = "([-+]?\\d*\\.?\\d*)";
        String frac = "((\\d+_\\d+/\\d+)|(\\d+/\\d+))";

        //parses the input according to regular expressions
        input = input.toLowerCase();
        if (input.matches("(" + ref + "|" + dub + "|" + frac + ")"
                + "( [-+*/%] (" + ref + "|" + dub + "|" + frac + "))+")) {
            cell = new FormulaCell(input, model);
        }
        else if (input.matches("((sum)|(avg)) " + ref + " - " + ref)) {
            cell = new SumAvgCell(input, model);
        }
        else if (input.matches(dub + "|" + frac)) {
            cell = new NumberCell(input);
        }
        else if (input.matches("(\\d{1}|\\d{2})/(\\d{1}|\\d{2})/(\\d{2}|\\d{4})")) {
            cell = new DateCell(input);
        }
        else {
            cell = new StringCell(input);
        }

        //sets the cell value
        model.setValueAt(cell, row, column);
    }

    //sets all cell values to null
    public void clearTable() {
        for (int i = 0; i < HEIGHT; i++) {
            for(int j = 0; j < WIDTH; j++) {
                model.setValueAt(null, i, j);
            }
        }
    }
}

Any guidance on how to create the desired behavior would be highly appreciated.

traviata
  • 27
  • 4

1 Answers1

4

Define a method, for example getDetailedText(), in Cell interface which returns the text which will be used in cell editor. Then return formula from this method in FormulaCell. For other cells you can return toString().

Cell interface:

public interface Cell {

     public String getDetailedText();
}

FormulaCell:

public class FormulaCell implements Cell {

    public String getDetailedText() {
         return formula;
    }
}

Other Cells (if you want to display another text for each cell just return here):

public class OtherCell implements Cell {

    public String getDetailedText() {
         return toString();
    }
}

Then create a cell editor and set it as default editor to the table.

Default cell editor:

public class MyDefaultCellEditor extends DefaultCellEditor {
    public MyDefaultCellEditor() {
        super(new JTextField());
    }

    @Override
    public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row,
            int column) {
        JTextField textField = (JTextField) super.getTableCellEditorComponent(table, value, isSelected, row, column);
        Cell cell = (Cell) value;

        textField.setText(cell.getEditorText());
        return textField;
    }
} 

Set to the table:

table.setDefaultEditor(Object.class, new MyDefaultCellEditor());
rdonuk
  • 3,921
  • 21
  • 39
  • When I ran it, the getTableCellEditorComponent(...) method endlessly called itself, resulting in a StackOverflowError. This may or may not be helpful, but I am using a custom cell listener called TableCellListener, which I found on Java Tips Weblog. – traviata Mar 28 '16 at 00:38
  • I just forgot the add `super.`. Now updated that part. – rdonuk Mar 28 '16 at 03:05
  • 1
    Great; that fixed it! The only other complication was that the call to super needed to be cast to a JTextField. – traviata Mar 28 '16 at 04:11