0

I am struggling again with JTable and I am very stuck.

The problem is:

  • I have a button to select file pathes to fill the first column of my JTable: this is working fine.
  • Clicking a checkbox, I change the structure of the jtable to add or remove columns: this is working fine.
  • When I try again to fill my new table with file pathes, only the last one selected is copied in each row of the column.

I have found many posts with a similar problem but could not sort things out (Try cellRenderer, cellEditor, modify the model and so on). Below I tried to reduce the code to the minimum to make it simple. I have tried many different things but I really do not understand the logic of what is going on (I read the doc entirely, but obviously I am missing something).

I have also tried to do a version with DefaultTableModel, but the problem seems to be independent. Moreover I would like to keep AbstractTableModel because of other classes using it. I think that something is going wrong in the model but I do not know what (tried also different fire*** in the model).

If you see the error and can explain the logic behind it, it would be much appreciated.

Thanks a lot. Here is the code that is, I think, self-explaining:

import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Arrays;

import javax.swing.BoxLayout;
import javax.swing.DefaultCellEditor;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.table.AbstractTableModel;


public class FillTableExample extends JFrame {

private JLabel label = new JLabel("File:");
private JButton selectButton = new JButton("Select");
private JPanel labelButtonPane = new JPanel(new FlowLayout(FlowLayout.LEFT));
private Object rowDataTable[][] = {{"", ""}, {"", ""}, {"", ""}, {"", ""},
        {"", ""}, {"", ""}, {"", ""}, {"", ""},
        {"", ""}, {"", ""}, {"", ""}, {"", ""},
        {"", ""}, {"", ""}, {"", ""}, {"", ""},
        {"", ""}, {"", ""}, {"", ""}, {"", ""},
        {"", ""}, {"", ""}, {"", ""}, {"", ""},
        {"", ""}, {"", ""}, {"", ""}, {"", ""},
        {"", ""}, {"", ""}, {"", ""}, {"", ""},
        {"", ""}, {"", ""}, {"", ""}, {"", ""},
        {"", ""}, {"", ""}, {"", ""}, {"", ""}};
private String[] columnNames = {"File", "Name"};
private CustomTableModel fileTableModel = new CustomTableModel(rowDataTable, columnNames);
private JTable fileTable = new JTable(fileTableModel);
private JScrollPane scrollPane = new JScrollPane(fileTable);
private FillTable fillTableActionListener = new FillTable(fileTableModel);
private JPanel container = new JPanel();
private JCheckBox changeCol = new JCheckBox("ChangeCol");



public FillTableExample(){


    selectButton.addActionListener(fillTableActionListener);
    labelButtonPane.add(label);
    labelButtonPane.add(selectButton);

    changeCol.addActionListener(new DisplayPane());

    this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    this.setLocationRelativeTo(null);
    this.setExtendedState(MAXIMIZED_BOTH);
    this.setMinimumSize(new Dimension(400,400));
    this.setResizable(true);
    container.setLayout(new BoxLayout(container, BoxLayout.PAGE_AXIS));
    container.add(labelButtonPane);
    container.add(changeCol);
    container.add(scrollPane);
    this.setContentPane(container);
    this.setVisible(true);
}


public class CustomTableModel extends AbstractTableModel {

    private Object rowData[][];
    private String[] columnNames;

    public CustomTableModel(Object data[][], String[] titles){
        rowData = data;
        columnNames = titles;
    }

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

    public String getColumnName(int column) {
        return columnNames[column];
    }

    public int getRowCount() {
        return rowData.length;
    }

    public Object getValueAt(int row, int column) {
        return rowData[row][column];
    }

    public Class getColumnClass(int column) {
        return this.rowData[0][column].getClass();
    }


    public int getColumnIndex(String columnName){
        return Arrays.asList(columnNames).indexOf(columnName);
    }

    public void setValueAt(Object value, int row, int column) {
        rowData[row][column] = value;
       // fireTableDataChanged();
    }

    public boolean isCellEditable(int row, int column) {

        if(column == 0 && columnNames[0].equals("Sample Name"))
            return true;
        else
            return (column != 0);
    }
}

class DisplayPane implements ActionListener {


    public void actionPerformed(ActionEvent e){

        final String[] tmpContent1 = {"", "", ""};
        final String[] newColumnNames = {"File", "Name", "Extension"};

        for (int i = 0; i < rowDataTable.length; i++)
                rowDataTable[i] = tmpContent1;
        fileTableModel = new CustomTableModel(rowDataTable, newColumnNames);

        fileTable.setModel(fileTableModel);
        fillTableActionListener.setModel(fileTableModel);
        fileTableModel.fireTableStructureChanged();
        fileTableModel.fireTableDataChanged();
    }
}


class FillTable implements ActionListener {

    private CustomTableModel model;

    public FillTable(CustomTableModel model){

        this.model = model;
    }

    public void actionPerformed(ActionEvent e){

        final JFileChooser fc = new JFileChooser();
        int returnVal;

        fc.setMultiSelectionEnabled(true);
        returnVal = fc.showOpenDialog(FillTableExample.this);

        model.setValueAt("toto", 0, 0);
        //I tried this, but you can see that toto is duplicated when clicking on cell
        //((AbstractTableModel) fileTable.getModel()).fireTableCellUpdated(0, 0);
        model.fireTableDataChanged();
    }

    public void setModel(CustomTableModel model){
        this.model = model;
    }
}


class FileEditor extends DefaultCellEditor {

    public FileEditor(){
        super(new JTextField());
    }
    public Component getTableCellEditorComponent(JTable table, Object value,
              boolean isSelected, int row, int column) {

        JTextField editor = (JTextField) super.getTableCellEditorComponent(table, value, isSelected, row, column);

        if(column == 0){
            if(value != "toto")
                editor.setText("");
            else
                editor.setText(value.toString());
        }

        return editor;
    }
}


public static void main(String[] args) {

    FillTableExample example = new FillTableExample();
    example.setVisible(true);
}
}

[****************EDIT]

I have added the command "fileTable.getColumnModel().getColumn(0).setCellEditor(new FileEditor());" in the constructor. Same error

Here is the code using DefaultTableModel (same error):

import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Arrays;

import javax.swing.BoxLayout;
import javax.swing.DefaultCellEditor;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.table.DefaultTableModel;


public class FillTableExample2 extends JFrame {

private JLabel label = new JLabel("File:");
private JButton selectButton = new JButton("Select");
private JPanel labelButtonPane = new JPanel(new FlowLayout(FlowLayout.LEFT));
private Object rowDataTable[][] = {{"", ""}, {"", ""}, {"", ""}, {"", ""},
        {"", ""}, {"", ""}, {"", ""}, {"", ""},
        {"", ""}, {"", ""}, {"", ""}, {"", ""},
        {"", ""}, {"", ""}, {"", ""}, {"", ""},
        {"", ""}, {"", ""}, {"", ""}, {"", ""},
        {"", ""}, {"", ""}, {"", ""}, {"", ""},
        {"", ""}, {"", ""}, {"", ""}, {"", ""},
        {"", ""}, {"", ""}, {"", ""}, {"", ""},
        {"", ""}, {"", ""}, {"", ""}, {"", ""},
        {"", ""}, {"", ""}, {"", ""}, {"", ""}};
private String[] columnNames = {"File", "Name"};
private DefaultTableModel fileTableModel = new DefaultTableModel(rowDataTable, columnNames);
private JTable fileTable = new JTable(fileTableModel);
private JScrollPane scrollPane = new JScrollPane(fileTable);
private FillTable fillTableActionListener = new FillTable(fileTableModel);
private JPanel container = new JPanel();
private JCheckBox changeCol = new JCheckBox("ChangeCol");



public FillTableExample2(){


    selectButton.addActionListener(fillTableActionListener);
    labelButtonPane.add(label);
    labelButtonPane.add(selectButton);

    changeCol.addActionListener(new DisplayPane());
    fileTable.getColumnModel().getColumn(0).setCellEditor(new FileEditor());

    this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    this.setLocationRelativeTo(null);
    this.setExtendedState(MAXIMIZED_BOTH);
    this.setMinimumSize(new Dimension(400,400));
    this.setResizable(true);
    container.setLayout(new BoxLayout(container, BoxLayout.PAGE_AXIS));
    container.add(labelButtonPane);
    container.add(changeCol);
    container.add(scrollPane);
    this.setContentPane(container);
    this.setVisible(true);
}


class DisplayPane implements ActionListener {


    public void actionPerformed(ActionEvent e){
        fileTableModel.addColumn("Extension");
    }
}


class FillTable implements ActionListener {

    private DefaultTableModel model;

    public FillTable(DefaultTableModel model){

        this.model = model;
    }

    public void actionPerformed(ActionEvent e){

        final JFileChooser fc = new JFileChooser();
        int returnVal;

        fc.setMultiSelectionEnabled(true);
        returnVal = fc.showOpenDialog(FillTableExample2.this);

        model.setValueAt("toto", 0, 0);
    }
}


class FileEditor extends DefaultCellEditor {

    public FileEditor(){
        super(new JTextField());
    }
    public Component getTableCellEditorComponent(JTable table, Object value,
              boolean isSelected, int row, int column) {

        JTextField editor = (JTextField) super.getTableCellEditorComponent(table, value, isSelected, row, column);

        if(value != "toto")
            editor.setText("");
        else
            editor.setText(value.toString());

        return editor;
    }
}


public static void main(String[] args) {

    FillTableExample example = new FillTableExample();
    example.setVisible(true);
}
}
nicoluca
  • 105
  • 7
  • I see no reason for you to extend AbstractTableModel. You can use the DefaultTableModel and just override the methods where appropriate, like the isCellEditable(...) method. `tried also different fire*** in the model).` - actually you are not doing it in the model which is another reason to just use the DefaultTableModel, since it does this for you already. You should never invoke these methods outside the TableModel. – camickr Feb 02 '18 at 19:06
  • For your custom editor, you should not be checking the column. The editor should only be added to the first column so there is no need for the check. All the other columns will then just use the default editor. – camickr Feb 02 '18 at 19:07
  • Thanks. I have done a code (see EDIT) with DefaultTableModel and removing the fire***. Same problem. – nicoluca Feb 02 '18 at 20:05
  • `I have done a code (see EDIT) with DefaultTableModel` - don't know how you test it since the main() method still invokes the first example. `When I try again to fill my new table with file pathes, only the last one selected is copied in each row of the column.` - don't know what that means the "Select" button doesn't do anything except display a file chooser. I have no idea how to test your code. – camickr Feb 02 '18 at 20:51
  • the comment on file pathes is valid for my initial program. I simplified it to make the example minimalistic. – nicoluca Feb 02 '18 at 21:08
  • `I simplified it to make the example minimalistic` - but if we can't reproduce your problem, then the simplification is a waste of time. The point of an [mcve] is to demonstrate the problem. So we need to know the steps required to reproduce the problem. – camickr Feb 03 '18 at 02:42

2 Answers2

0

Thank you so much! The problem was that I was copying the same String[] in each row. So modifying one string was propagated in the whole column. Declaring a new String[] ensures that a different content is in each row.

Putting a fireTableDataChanged() in the model and removing fileTableModel.fireTableStructureChanged() and fileTableModel.fireTableDataChanged() from the class does the trick.

Here is the full operational code:

import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Arrays;

import javax.swing.BoxLayout;
import javax.swing.DefaultCellEditor;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.table.AbstractTableModel;


public class FillTableExample extends JFrame {

private JLabel label = new JLabel("File:");
private JButton selectButton = new JButton("Select");
private JPanel labelButtonPane = new JPanel(new FlowLayout(FlowLayout.LEFT));
private Object rowDataTable[][] = {{"", ""}, {"", ""}, {"", ""}, {"", ""},
        {"", ""}, {"", ""}, {"", ""}, {"", ""},
        {"", ""}, {"", ""}, {"", ""}, {"", ""},
        {"", ""}, {"", ""}, {"", ""}, {"", ""},
        {"", ""}, {"", ""}, {"", ""}, {"", ""},
        {"", ""}, {"", ""}, {"", ""}, {"", ""},
        {"", ""}, {"", ""}, {"", ""}, {"", ""},
        {"", ""}, {"", ""}, {"", ""}, {"", ""},
        {"", ""}, {"", ""}, {"", ""}, {"", ""},
        {"", ""}, {"", ""}, {"", ""}, {"", ""}};
private String[] columnNames = {"File", "Name"};
private CustomTableModel fileTableModel = new CustomTableModel(rowDataTable, columnNames);
private JTable fileTable = new JTable(fileTableModel);
private JScrollPane scrollPane = new JScrollPane(fileTable);
private FillTable fillTableActionListener = new FillTable(fileTableModel);
private JPanel container = new JPanel();
private JCheckBox changeCol = new JCheckBox("ChangeCol");



public FillTableExample(){


    selectButton.addActionListener(fillTableActionListener);
    labelButtonPane.add(label);
    labelButtonPane.add(selectButton);

    changeCol.addActionListener(new DisplayPane());

    this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    this.setLocationRelativeTo(null);
    this.setExtendedState(MAXIMIZED_BOTH);
    this.setMinimumSize(new Dimension(400,400));
    this.setResizable(true);
    container.setLayout(new BoxLayout(container, BoxLayout.PAGE_AXIS));
    container.add(labelButtonPane);
    container.add(changeCol);
    container.add(scrollPane);
    this.setContentPane(container);
    this.setVisible(true);
}


public class CustomTableModel extends AbstractTableModel {

    private Object rowData[][];
    private String[] columnNames;

    public CustomTableModel(Object data[][], String[] titles){
        rowData = data;
        columnNames = titles;
    }

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

    public String getColumnName(int column) {
        return columnNames[column];
    }

    public int getRowCount() {
        return rowData.length;
    }

    public Object getValueAt(int row, int column) {
        return rowData[row][column];
    }

    public Class getColumnClass(int column) {
        return this.rowData[0][column].getClass();
    }


    public int getColumnIndex(String columnName){
        return Arrays.asList(columnNames).indexOf(columnName);
    }

    public void setValueAt(Object value, int row, int column) {
        rowData[row][column] = value;
       fireTableDataChanged();
    }

    public boolean isCellEditable(int row, int column) {

        if(column == 0 && columnNames[0].equals("Sample Name"))
            return true;
        else
            return (column != 0);
    }
}

class DisplayPane implements ActionListener {


    public void actionPerformed(ActionEvent e){

        final String[] newColumnNames = {"File", "Name", "Extension"};

        for (int i = 0; i < rowDataTable.length; i++)
                rowDataTable[i] = new String[]{"", "", ""};
        fileTableModel = new CustomTableModel(rowDataTable, newColumnNames);

        fileTable.setModel(fileTableModel);
        fillTableActionListener.setModel(fileTableModel);
    }
}


class FillTable implements ActionListener {

    private CustomTableModel model;

    public FillTable(CustomTableModel model){

        this.model = model;
    }

    public void actionPerformed(ActionEvent e){

        final JFileChooser fc = new JFileChooser();
        int returnVal;

        fc.setMultiSelectionEnabled(true);
        returnVal = fc.showOpenDialog(FillTableExample.this);

        model.setValueAt("toto", 0, 0);
    }

    public void setModel(CustomTableModel model){
        this.model = model;
    }
}


public static void main(String[] args) {

    FillTableExample example = new FillTableExample();
    example.setVisible(true);
}
}

[**********EDIT]

You are right, with DefaultTableModel the code is much lighter and straightforward. Here is the corrected example2:

import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Arrays;

import javax.swing.BoxLayout;
import javax.swing.DefaultCellEditor;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.table.DefaultTableModel;


public class FillTableExample2 extends JFrame {

private JLabel label = new JLabel("File:");
private JButton selectButton = new JButton("Select");
private JPanel labelButtonPane = new JPanel(new FlowLayout(FlowLayout.LEFT));
private Object rowDataTable[][] = {{"", ""}, {"", ""}, {"", ""}, {"", ""},
        {"", ""}, {"", ""}, {"", ""}, {"", ""},
        {"", ""}, {"", ""}, {"", ""}, {"", ""},
        {"", ""}, {"", ""}, {"", ""}, {"", ""},
        {"", ""}, {"", ""}, {"", ""}, {"", ""},
        {"", ""}, {"", ""}, {"", ""}, {"", ""},
        {"", ""}, {"", ""}, {"", ""}, {"", ""},
        {"", ""}, {"", ""}, {"", ""}, {"", ""},
        {"", ""}, {"", ""}, {"", ""}, {"", ""},
        {"", ""}, {"", ""}, {"", ""}, {"", ""}};
private String[] columnNames = {"File", "Name"};
private DefaultTableModel fileTableModel = new DefaultTableModel(rowDataTable, columnNames);
private JTable fileTable = new JTable(fileTableModel);
private JScrollPane scrollPane = new JScrollPane(fileTable);
private FillTable fillTableActionListener = new FillTable(fileTableModel);
private JPanel container = new JPanel();
private JCheckBox changeCol = new JCheckBox("ChangeCol");



public FillTableExample2(){


    selectButton.addActionListener(fillTableActionListener);
    labelButtonPane.add(label);
    labelButtonPane.add(selectButton);

    changeCol.addActionListener(new DisplayPane());

    this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    this.setLocationRelativeTo(null);
    this.setExtendedState(MAXIMIZED_BOTH);
    this.setMinimumSize(new Dimension(400,400));
    this.setResizable(true);
    container.setLayout(new BoxLayout(container, BoxLayout.PAGE_AXIS));
    container.add(labelButtonPane);
    container.add(changeCol);
    container.add(scrollPane);
    this.setContentPane(container);
    this.setVisible(true);
}


class DisplayPane implements ActionListener {


    public void actionPerformed(ActionEvent e){
        fileTableModel.addColumn("Extension");
    }
}


class FillTable implements ActionListener {

    private DefaultTableModel model;

    public FillTable(DefaultTableModel model){

        this.model = model;
    }

    public void actionPerformed(ActionEvent e){

        final JFileChooser fc = new JFileChooser();
        int returnVal;

        fc.setMultiSelectionEnabled(true);
        returnVal = fc.showOpenDialog(FillTableExample2.this);

        model.setValueAt("toto", 0, 0);
    }
}

public static void main(String[] args) {

    FillTableExample2 example = new FillTableExample2();
    example.setVisible(true);
}
}
nicoluca
  • 105
  • 7
  • The problem was with you not creating a new String as you stated. As I already suggested, there is no need for you to extend AbstractTableModel. You should NOT be invoking fireTableDataChanged(...) in the setValueAt() method. There is a more appropriate fireXXX method to use. This is all done properly in the DefaultTableModel. Just use the DefaultTableModel for something basic like this. – camickr Feb 02 '18 at 20:56
  • Thanks, you are right. I have updated the answer with DefaultTableModel (see edit). Thank you to camickr5 and blueEOS. – nicoluca Feb 02 '18 at 21:02
  • + the FileEditor class is not necessary – nicoluca Feb 02 '18 at 21:05
-1

Please find the fixed class :

class DisplayPane implements ActionListener {

        public void actionPerformed(ActionEvent e) {

            final String[] newColumnNames = { "File", "Name", "Extension" };

            for (int i = 0; i < rowDataTable.length; i++){
                rowDataTable[i] =new String[] { "", "", "" }; // See below
            }
            fileTableModel = new CustomTableModel(rowDataTable, newColumnNames);

            fileTable.setModel(fileTableModel);
            fillTableActionListener.setModel(fileTableModel);
            fileTableModel.fireTableStructureChanged();
            fileTableModel.fireTableDataChanged();
        }
    }

The error was caused by the usage of the same String array for every rows. A good practice is to fire event from the model.

BluEOS
  • 576
  • 6
  • 13
  • You are wrong, fireXXX must be invoked from the Event Dispatch Thread... The error was caused by the usage of the same String array for every rows. Thank you for the down vote! I bet you didn't run the program to figure the issue. – BluEOS Feb 02 '18 at 23:14
  • `fireXXX must be invoked from the Event Dispatch Thread` - correct. however, the TableModel (not the application code) is responsible for invoking the fireXXX(...) method. The update to the TableModel must be done of the EDT. `The error was caused by the usage of the same String array for every rows` - correct, but that was not what the down vote was for. The fireXXX() code is wrong! Also, when you answer a question, you should state the problem and the solution, not just post a few random lines of code making everybody guess what the problem is. – camickr Feb 03 '18 at 02:25