I have a JTable inside a JScrollPane, and I override getPreferredScrollableViewportSize to fit the JScrollPane to a fixed number of table rows, allowing scroll bars to appear when needed. I also used parts of the code found in SO to resize the columns of the table to fit their content.
The problem I am facing is that when I dynamically add a wider row to the table, the horizontal JScrollBar is not displayed. I tried differently to get JTable, JViewport and JScrollPane to update correctly by calling revalidate () or repaint (), doLayout (), etc., but I didn’t.
Of course, I can recall the pack () method in the JFrame, but this will increase the width of the scroll bar, and the horizontal scroll bar will not appear anyway.
What am I missing?
There's MVCE here, it's just an example of an ugly toy, but that should be enough to reproduce unwanted behavior.
Thank you for your help!
The code:
import java.awt.*;
import java.awt.event.ActionEvent;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.table.*;
public class Main
{
public static void main (String [] a) {
SwingUtilities.invokeLater (new Runnable () {
public void run () {
initGUI ();
}
});
}
private static void initGUI () {
final DataTable table = new DataTable ();
JPanel container = new JPanel (new BorderLayout (0, 20));
container.add (new JScrollPane (table));
container.add (new JButton (new AbstractAction ("Add wider row") {
public void actionPerformed (ActionEvent e) {
table.addRow ();
}
}), BorderLayout.SOUTH);
container.setBorder (new EmptyBorder (10, 10, 10, 10));
JFrame frame = new JFrame ("Test");
frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
frame.setResizable (false);
frame.setContentPane (container);
frame.setLocationRelativeTo (null);
frame.pack ();
frame.setVisible (true);
}
}
class DataTable extends JTable
{
private static final int VISIBLE_ROWS = 5;
DataTable () {
String [][] data = new String [][] {
{"SomeText", "SomeText"},
{"SomeText", "SomeText"},
{"SomeText", "SomeText"},
{"SomeText", "SomeText"},
{"SomeText", "SomeText"},
{"SomeText", "SomeText"}
};
setModel (new DefaultTableModel (data, new String [] {"", ""}) {
@Override public boolean isCellEditable (int row, int column) {
return false;
}
});
columnModel.setColumnMargin (20);
setBorder (null);
setFocusable (false);
setRowSelectionAllowed (false);
setShowGrid (false);
setTableHeader (null);
resizeColumnWidth ();
}
public void addRow () {
((DefaultTableModel) getModel ()).addRow (new String [] {"SomeLongerText", "SomeLongerText"});
}
private int calculateColumnWidth (int column) {
TableColumn tableColumn = columnModel.getColumn (column);
int width = 0;
for (int row=0; row<getRowCount(); row++) width = Math.max (prepareRenderer (getCellRenderer (row, column), row, column).getPreferredSize ().width, width);
return width + getIntercellSpacing ().width;
}
@Override public Dimension getPreferredScrollableViewportSize () {
int columnsWidth = 0;
for (int column=0; column<getColumnCount (); column++) columnsWidth += calculateColumnWidth (column);
return new Dimension (columnsWidth, VISIBLE_ROWS * getRowHeight ());
}
protected void resizeColumnWidth () {
for (int column=0; column<getColumnCount (); column++) columnModel.getColumn (column).setPreferredWidth (calculateColumnWidth (column));
}
}
EDIT:
Thanks to @camickr, I solved the problem by setting the desired auto resize mode and calling the resizeColumnWidth method every time a row is added. I already tried them separately, I can not believe that I did not use both :)
Hovewer, I am posting below updated code, now it works well:
import java.awt.*;
import java.awt.event.ActionEvent;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.table.*;
public class Main
{
public static void main (String [] a) {
SwingUtilities.invokeLater (new Runnable () {
public void run () {
initGUI ();
}
});
}
private static void initGUI () {
final DataTable table = new DataTable ();
JPanel container = new JPanel (new BorderLayout (0, 20));
container.add (new JScrollPane (table));
container.add (new JButton (new AbstractAction ("Add wider row") {
public void actionPerformed (ActionEvent e) {
table.addRow ();
}
}), BorderLayout.SOUTH);
container.setBorder (new EmptyBorder (10, 10, 10, 10));
JFrame frame = new JFrame ("Test");
frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
frame.setResizable (false);
frame.setContentPane (container);
frame.setLocationRelativeTo (null);
frame.pack ();
frame.setVisible (true);
}
}
class DataTable extends JTable
{
private static final int VISIBLE_ROWS = 5;
DataTable () {
String [][] data = new String [][] {
{"SomeText", "SomeText"},
{"SomeText", "SomeText"},
{"SomeText", "SomeText"},
{"SomeText", "SomeText"},
{"SomeText", "SomeText"},
{"SomeText", "SomeText"}
};
setModel (new DefaultTableModel (data, new String [] {"", ""}) {
@Override public boolean isCellEditable (int row, int column) {
return false;
}
});
columnModel.setColumnMargin (20);
setAutoResizeMode (AUTO_RESIZE_OFF);
setBorder (null);
setFocusable (false);
setRowSelectionAllowed (false);
setShowGrid (false);
setTableHeader (null);
resizeColumnWidth ();
}
public void addRow () {
((DefaultTableModel) getModel ()).addRow (new String [] {"SomeLongerText", "SomeLongerText"});
resizeColumnWidth ();
}
private int calculateColumnWidth (int column) {
TableColumn tableColumn = columnModel.getColumn (column);
int width = 0;
for (int row=0; row<getRowCount(); row++) width = Math.max (prepareRenderer (getCellRenderer (row, column), row, column).getPreferredSize ().width, width);
return width + getIntercellSpacing ().width;
}
@Override public Dimension getPreferredScrollableViewportSize () {
int columnsWidth = 0;
for (int column=0; column<getColumnCount (); column++) columnsWidth += calculateColumnWidth (column);
return new Dimension (columnsWidth, VISIBLE_ROWS * getRowHeight ());
}
protected void resizeColumnWidth () {
for (int column=0; column<getColumnCount (); column++) columnModel.getColumn (column).setPreferredWidth (calculateColumnWidth (column));
}
}