Search code examples
javaswinguser-interfacejtablejscrollpane

JTable inside a JScrollPane not showing up properly


I am working on a the GUI of a piece of code that I have been patching together. I am stuck at this part of the program where I would like a datafile the user chooses to be displayed in a JTable in a preview manner (i.e. the user should not be able to edit the data on the table).

With a button click from Experiment Parameters tab (see screenshot below), I create and run a "PreviewAction" which creates a new tab, and fills it up with the necessary components. Below is the code for DataPreviewAction. EDIT: I also posted a self-contained, minimal version of this that mimics the conditions in the real project, and exhibits the same behaviour.

import java.awt.BorderLayout;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableModel;

public class MyFrame extends JFrame {

private JPanel panel1;
private JTabbedPane tabs;
private JButton runButton;

public MyFrame() {
    tabs = new JTabbedPane();
    panel1 = new JPanel();
    runButton = new JButton("go!");
    runButton.addActionListener(new java.awt.event.ActionListener() {
        public void actionPerformed(java.awt.event.ActionEvent evt) {
            runButtonActionPerformed(evt);
        }
    });
    panel1.add(runButton);
    tabs.addTab("first tab", panel1);
    this.add(tabs);
    pack();
}

public static void main(String args[]) {
    try {
        for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager
                .getInstalledLookAndFeels()) {
            if ("Nimbus".equals(info.getName())) {
                javax.swing.UIManager.setLookAndFeel(info.getClassName());
                break;
            }
        }
    } catch (ClassNotFoundException ex) {
        java.util.logging.Logger.getLogger(MyFrame.class.getName()).log(
                java.util.logging.Level.SEVERE, null, ex);
    } catch (InstantiationException ex) {
        java.util.logging.Logger.getLogger(MyFrame.class.getName()).log(
                java.util.logging.Level.SEVERE, null, ex);
    } catch (IllegalAccessException ex) {
        java.util.logging.Logger.getLogger(MyFrame.class.getName()).log(
                java.util.logging.Level.SEVERE, null, ex);
    } catch (javax.swing.UnsupportedLookAndFeelException ex) {
        java.util.logging.Logger.getLogger(MyFrame.class.getName()).log(
                java.util.logging.Level.SEVERE, null, ex);
    }

    /* Create and display the form */
    java.awt.EventQueue.invokeLater(new Runnable() {

        public void run() {
            MyFrame frame = new MyFrame();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        }
    });
}

private void runButtonActionPerformed(java.awt.event.ActionEvent evt) {
    /*
     * Normally there is more stuff happening here but this much will do for
     * the sake of example
     */
    List<String[]> data = new LinkedList<String[]>();
    for (int i = 1; i < 1000; i++)
        data.add(new String[] { "entry1", "value1", "value2", "value3" });

    SwingUtilities.invokeLater(new DataPreviewAction(data, tabs));
}

public class DataPreviewAction implements Runnable {

    private JTabbedPane contentHolder;
    private List<String[]> data;

    public DataPreviewAction(List<String[]> data, JTabbedPane comp) {
        this.contentHolder = comp;
        this.data = data;
    }

    @Override
    public void run() {
        DefaultTableModel previewModel = new DefaultTableModel() {
            @Override
            public boolean isCellEditable(int row, int column) {
                return false;
            }
        };

        for (String[] datarow : data) {
            previewModel.addRow(Arrays.copyOf(datarow, datarow.length,
                    Object[].class));
        }

        JTable table = new JTable(previewModel);

        JPanel buttonPanel = new JPanel();
        buttonPanel.add(new JButton("A button"));
        buttonPanel.add(new JLabel(
                "Some description for the awesome table below "));
        buttonPanel.add(new JButton("another button"));

        JScrollPane tablePanel = new JScrollPane(table);
        JPanel container = new JPanel();
        container.setLayout(new BorderLayout());
        container.add(buttonPanel, BorderLayout.NORTH);
        container.add(tablePanel, BorderLayout.CENTER);
        contentHolder.addTab("Preview", container);
        contentHolder.validate();
        contentHolder.repaint();
    }
}
}

There are at least two problems here:

  1. The JTable (or the JScrollPane) does not render at all
  2. The JScrollPane is not as wide as the frame itself, I have no idea why

I am not all that good in Swing so I might be missing something fundamental. I have checked that the datafile is read properly, and the data model contains the right amount of rows (1000+). SO the table should not be empty.

Suggestions?

screenshot of the problem


Solution

  • Following the footsteps of mKorbel I ended up doing some debugging. I am providing it here in case others run into the same problem.

    It felt quite odd that the table looked OK when the underlying DataModel was supplied a data matrix upon initialisation

    private DefaultTableModel model = new DefaultTableModel(data, columnNames)
    

    but it would not show up properly when created with the empty constructor

    private DefaultTableModel model = new DefaultTableModel()
    

    and adding rows later with model.addRow(Object[] row);

    I started look through the source code, and it turns out with the empty constructor the number of rows and columns for the model (private fields) is initiated to 0 and not updated properly afterwards. I noticed this while debugging since my tables had the dimension of 1370 x 0, which of course does not display properly.

    Since I do not want to hardcode the number of rows/cols in advance the best course of action was to convert my "rows" to a matrix and provide the data to the model via constructor (much like mKorbel did). Here comes the fun part, if you want to supply the data then you need to supply the column names as well. THe fact that you have to have column names is counter-intuitive (IMHO), what happens if you dont have/need headers? The data is already in a table form, so I dont understand why column names is so important.

    At any rate the following code renders the table at least:

    String[] colNames = new String[data[1].length];
    for(int i=0; i<colNames.length; i++)
        colNames[i] = "C" + i;
    
    DefaultTableModel model = new DefaultTableModel(data,colNames){
        @Override
        public boolean isCellEditable(int row, int column){
            return false;
        }};
    

    I am accepting this because it points to the origin of the problem, but I would not be able to pinpoint the problem without mKorbel's answer, so give the upvote to his/her answer :)