Search code examples
javaswingjtablejscrollpanetablerowsorter

JScrollPane scrollbar does not appear until after rowSorter.toggleSortOrder() is called


I have noticed that when I have a JTable with a TableRowSorter contained by a JScrollPane, the vertical scrollbar does not appear until after I have created SortKeys for the sorter (which is done by calling toggleSortOrder() for one of the columns).

My question is really why? What do SortKeys have to do with a vertical scrollbar?

Update: Added SSCCE that opens a JFrame with a JTable inside a JScrollPane, that sits in a Container along with a "Populate" button. When the table is initially painted, there is no data and hence no need for a scroll bar. After I populate it with 20 rows, there is a need for a scroll bar, but none appears.

There are two ways to make the scroll bar appear:

  • Click on either of the header cells to cause a sort to occur.
  • Remove the commented call toggleSortOrder() in the Container's refresh() method.

    // table.getRowSorter().toggleSortOrder(0);

toggleSortOrder() calls setSortKeys() calls sort() calls fireRowSorterChanged() and eventually something catches the event and adds the scroll bar.

package test;

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.WindowConstants;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableRowSorter;

@SuppressWarnings("serial")
public class TestFrame extends JFrame
{
    class MyTableModel extends AbstractTableModel
    {
        public List<String> names = new ArrayList<String>();

        public int getRowCount ()
        {
            return names.size();
        }

        public int getColumnCount ()
        {
            return 2;
        }

        public String getColumnName (int columnIndex)
        {
            return columnIndex > 0 ? "Name" : "Number";
        }

        public Class<?> getColumnClass (int columnIndex)
        {
            return String.class;
        }

        public Object getValueAt (int row, int col)
        {

            return row < names.size() ? col == 0 ? Integer.toString(row) : names.get(row) : "";
        }

        public void refresh (List<String> names)
        {
            this.names = names;
        }
    }

    class MyContainer extends java.awt.Container implements ActionListener
    {
        JTable                               table;
        MyTableModel                         model = new MyTableModel();
        private TableRowSorter<MyTableModel> sorter;

        public MyContainer()
        {
        }

        public void init ()
        {
            sorter = new TableRowSorter<MyTableModel>(model);
            table = new JTable(model);
            table.setBorder(BorderFactory.createEmptyBorder());
            table.setRowHeight(35);
            table.getTableHeader().setPreferredSize(new Dimension(200, 35));
            table.setRowSorter(sorter);
            table.setPreferredScrollableViewportSize(new Dimension(200, 70));
            table.setFillsViewportHeight(true);
            table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
            //Create the scroll pane and add the table to it.
            JScrollPane scrollPane = new JScrollPane(table);
            scrollPane.setBounds(10, 10, 200, 210);

            //Add the scroll pane to this panel.
            add(scrollPane);
            JButton btn = new JButton("Populate");
            btn.setActionCommand("populate");
            btn.addActionListener(this);
            btn.setBounds(10, 220, 200, 35);
            add(btn);
        }

        public void refresh (List<String> rows)
        {
            model.refresh(rows);
            try
            {
                // Notify sorter that model data (possibly number of rows) has changed.
                // Without this call, the sorter assumes the number of rows is the same.
                table.getRowSorter().allRowsChanged();
                // Do we really want to toggle the sort order every time we refresh?
                // User can toggle the sort order himself by clicking on the 
                // appropriate header cell.
                List<?> keys = table.getRowSorter().getSortKeys();
                if (null == keys || keys.isEmpty())
                {
//                    table.getRowSorter().toggleSortOrder(0);
                }
            } catch (Exception e)
            {
                e.printStackTrace();
            }
            table.repaint();
        }

        public void actionPerformed(ActionEvent e)
        {
            if ("populate".equals(e.getActionCommand()))
            {
                List<String> rows = new ArrayList<String>();
                for (int ii = 0; ii < 20; ii++)
                {
                    rows.add(String.format("%02d", new Integer(ii)));
                }
                refresh(rows);
            }
        }

        MyTableModel getModel ()
        {
            return model;
        }
    }

    public static void main (String args[])
    {
        new TestFrame();
    }

    MyContainer myContainer = new MyContainer();

    TestFrame() 
    {
        myContainer.init();
        myContainer.table.getSelectionModel().clearSelection();
        add(myContainer);
        this.setSize(240, 310);
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        //pack();
        setVisible(true);
    }

}

Solution

  • Well, that is not really a SSCCE because you are using a custom TableModel. If you would have created a proper SSCCE you would be using the DefaultTableModel so that you are testing your code with standard JDK classes. If you did this then you would have noticed that the code would work.

    So then your next step would be to try the code with your custom TableModel and you would notice that the code did not work.

    So then your question on the forum would be why doesn't the code work with my custom TableModel? The point of the SSCCE is to do basic debugging to isolate where the error is happening so we have information to work with. In your original question we had no idea you where using custom classes.

    Anyway, the problem is that your custom TableModel is not notifying the table when a change to the data is made. In your refresh(...) method you need to add the following after you reset the List containing the data:

      fireTableRowsInserted(0, names.size()-1);
    

    There is no need for table.repaint() in any of your code.