2

I'm pretty new to Swing and trying to write a property sheet using JTable but am seeing some strange behavior. I set aside NetBeans and coded this little gem to see if the basics worked. In case you are wondering, my real app subclasses JTextField and puts up dialog boxes to help the user specify some of the values; ImportField is one of those classes.

This program displays the JTable but it only has the column headings. JTable never invokes getValueAt so no data is displayed.

Can anyone help me understand this behavior?

import java.awt.*;
import java.awt.event.*;
import java.util.StringTokenizer;
import javax.swing.*;
import javax.swing.table.*;
import javax.swing.event.TableModelEvent;

public class Y extends JFrame {

    public Y() {
        super("Y");
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

        final Model model = new Model();
        JTable table = new JTable();
        table.setModel(model);
        JScrollPane scrollpane = new JScrollPane(table);

        JButton button = new JButton("Click Me");
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent evt) {
               model.setValueAt(model.getValueAt(0, 1), 0, 1);
            }
        });

        JPanel panel = new JPanel(new BorderLayout());
        panel.add(scrollpane, BorderLayout.CENTER);
        panel.add(button, BorderLayout.SOUTH);

        setContentPane(panel);
        pack();
    }

    public static void main (String[] args) throws Exception {
        java.awt.EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                new Y().setVisible(true);
            }
        });
    }
}


class ImportField extends JTextField implements FocusListener {

    public ImportField() {
        super();
        this.setInputVerifier(new ImportVerifier());
        this.addFocusListener(this);
        this.setBorder(null);
    }

    @Override
    public void focusGained(FocusEvent e) {

        // We only want to do this ONCE
        this.removeFocusListener(this);

        System.out.println("Text field gained focus.");
    }

    @Override
    public void focusLost(FocusEvent e) {
        // IGNORE
    }

    class ImportVerifier extends InputVerifier {

        @Override
        public boolean verify(JComponent input) {
            boolean valid = true;
            JTextField fld = (JTextField) input;
            if (fld.getText().length() > 0) {
                StringTokenizer tok = new StringTokenizer(fld.getText(), ", ");
                while (tok.hasMoreTokens()) {
                    String token = tok.nextToken().toLowerCase();
                    if ( ! token.equals("insert") && ! token.equals("update") &&
                         ! token.equals("ignore") && ! token.equals("abort") &&
                         ! token.equals("none") && ! token.equals("all")) {

                        valid = false;
                    }
                }
            }
            return valid;
        }
    }
}


class Model extends DefaultTableModel {

    private String[] colNames = { "Key", "Value" };

    private Object[][] data = { { "item 1", "something" },
                                { "item 2", "something else" },
                                { "item 3", "other thing" }    };

    @Override
    public Class<?> getColumnClass(int columnIndex) {
        System.out.format("%-15s %s\n", "getColumnClass", "String");
        return String.class;
    }

    @Override
    public int getColumnCount() {
        System.out.format("%-15s %d\n", "getColumnCount", colNames.length);
        return colNames.length;
    }

    @Override
    public String getColumnName(int columnIndex) {
        System.out.format("%-15s %s\n", "getColumnName", colNames[columnIndex]);
        return colNames[columnIndex];
    }

    public int geRowlumnCount() {
        System.out.format("%-15s %d\n", "getRowName", data.length);
        return data.length;
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        System.out.format("%-15s %s\n", "getValueAt", data[rowIndex][columnIndex]);
        return data[rowIndex][columnIndex];
    }

    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        System.out.format("%-15s %s\n", "isCellEditable", rowIndex == 1);
        return rowIndex == 1;
    }

    @Override
    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
        System.out.format("%-15s %s\n", "setValueAt", aValue);
        data[rowIndex][columnIndex] = aValue;
        fireTableChanged(new TableModelEvent(this));
    }
}

Your help will be much appreciated.

3 Answers3

1

Try this, after you set the model, call:

((DefaultTableModel)table.getModel()).fireTableDataChanged();
user489041
  • 27,916
  • 55
  • 135
  • 204
  • Thanks for the suggestion, @user489041, but that doesn't work. That is fundamentally what I was doing in my ActionListener, except that I was invoking setValueAt on the model to trigger the fireTableDataChanged event. – Aging Hacker May 13 '15 at 01:11
1

The problem comes from the fact that you extended DefaultTableModel and you did not implement method:

public int getRowCount()

Here are the steps from the moment you initialize the Model with

final Model model = new Model();
  1. The superclass (DefaultTableModel) constructor is called (but it's underlying data is empty).
  2. Model's private fields are initialized (colNames and data)
  3. The rest of Model's constructor is executed

[ For a nice explanation of these steps read this nice post Are fields initialized before constructor code is run in Java? ]

Now, as you did not override getRowCount(), Model class will use the implementation from super class DefaultTableModel which will return 0 all the time so the table will always "think" there is nothing to display.

Alex
  • 5,510
  • 8
  • 35
  • 54
0

I found out that you need to call table.validate(); (after setting the TableModel, of course)

Idloj
  • 109
  • 1
  • 6