Java Swing - JTable Pagination with Sorting

[Updated: Jan 15, 2018, Created: Jan 13, 2018]

In the last example we saw how to do pagination for JTable. There was a limitation with the sorting functionality i.e. if we sort the JTable column by clicking the column header, the sorting will only be performed on the rows of currently selected page rather than all rows (complete data set). In following example, we will add sorting for all rows.

Main class

public class PaginationExampleMain {
  public static void main(String[] args) {
      JFrame frame = createFrame();
      ObjectTableModel<Employee> objectDataModel = createObjectDataModel();
      JTable table = new JTable(objectDataModel);
      table.setAutoCreateRowSorter(true);
      PaginationDataProvider<Employee> dataProvider = createDataProvider(objectDataModel);
      PaginatedTableDecorator<Employee> paginatedDecorator = PaginatedTableDecorator.decorate(table,
              dataProvider, new int[]{5, 10, 20, 50, 75, 100}, 10);
      frame.add(paginatedDecorator.getContentPanel());
      frame.setLocationRelativeTo(null);
      frame.setVisible(true);
  }

  private static ObjectTableModel<Employee> createObjectDataModel() {
      return new ObjectTableModel<Employee>() {
          @Override
          public Object getValueAt(Employee employee, int columnIndex) {
              switch (columnIndex) {
                  case 0:
                      return employee.getId();
                  case 1:
                      return employee.getName();
                  case 2:
                      return employee.getDateOfBirth();
                  case 3:
                      return employee.getAddress();
              }
              return null;
          }

          @Override
          public int getColumnCount() {
              return 4;
          }

          @Override
          public String getColumnName(int column) {
              switch (column) {
                  case 0:
                      return "Id";
                  case 1:
                      return "Name";
                  case 2:
                      return "Date Of Birth";
                  case 3:
                      return "Address";
              }
              return null;
          }
      };
  }

  private static PaginationDataProvider<Employee> createDataProvider(
          ObjectTableModel<Employee> objectDataModel) {
      final List<Employee> list = new ArrayList<>();
      for (int i = 1; i <= 500; i++) {
          Employee e = new Employee();
          e.setId(i);
          e.setName(RandomUtil.createRandomWord(6));
          e.setDateOfBirth(RandomUtil.createRandomDate(1950, 2000));
          e.setAddress("address " + i);
          list.add(e);
      }
      return new InMemoryPaginationDataProvider<>(list, objectDataModel);
  }

  private static JFrame createFrame() {
      JFrame frame = new JFrame("JTable Pagination example");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.setSize(new Dimension(600, 300));
      return frame;
  }
}

PaginationDataProvider implementation

public class InMemoryPaginationDataProvider<T> implements PaginationDataProvider<T> {
    private SortInfo currentSortInfo;
    private InMemoryComparator<T> comparator;
    private final List<T> rows;

    public InMemoryPaginationDataProvider(List<T> rows, ObjectTableModel<T> objectTableModel) {
        this.rows = rows;
        currentSortInfo = new SortInfo();
        comparator = new InMemoryComparator(currentSortInfo, objectTableModel);
    }

    @Override
    public int getTotalRowCount() {
        return rows.size();
    }

    @Override
    public List<T> getRows(int startIndex, int endIndex, int sortColumnIndex, boolean sortDescending) {
        sort(sortColumnIndex, sortDescending);
        return rows.subList(startIndex, endIndex);
    }

    private void sort(int sortColumnIndex, boolean sortDescending) {
        if (!currentSortInfo.equals(sortColumnIndex, sortDescending)) {
            currentSortInfo.sortIndex = sortColumnIndex;
            currentSortInfo.sortDescending = sortDescending;
            Collections.sort(rows, comparator);
        }
    }

    private static class SortInfo {
        private int sortIndex = -1;
        private boolean sortDescending;

        boolean equals(int sortIndex, boolean sortDescending) {
            return sortIndex == this.sortIndex &&
                    sortDescending == this.sortDescending;
        }
    }

    private static class InMemoryComparator<T> implements Comparator<T> {
        private final SortInfo sortInfo;
        private ObjectTableModel<T> objectTableModel;

        private InMemoryComparator(SortInfo sortInfo, ObjectTableModel<T> objectTableModel) {
            this.sortInfo = sortInfo;
            this.objectTableModel = objectTableModel;
        }

        @Override
        public int compare(T e1, T e2) {
            Object v1 = objectTableModel.getValueAt(e1, sortInfo.sortIndex);
            Object v2 = objectTableModel.getValueAt(e2, sortInfo.sortIndex);
            if (v1 instanceof Comparable && v2 instanceof Comparable) {
                return sortInfo.sortDescending ? ((Comparable) v2).compareTo(v1) :
                        ((Comparable) v1).compareTo(v2);
            }
            return 0;
        }
    }
};

The pagination decorator

public class PaginatedTableDecorator<T> {
  private JTable table;
  private PaginationDataProvider<T> dataProvider;
  private int[] pageSizes;
  private JPanel contentPanel;
  private int currentPageSize;
  private int currentPage = 1;
  private JPanel pageLinkPanel;
  private ObjectTableModel objectTableModel;
  private static final int MaxPagingCompToShow = 9;
  private static final String Ellipses = "...";

  private PaginatedTableDecorator(JTable table, PaginationDataProvider<T> dataProvider,
                                  int[] pageSizes, int defaultPageSize) {
      this.table = table;
      this.dataProvider = dataProvider;
      this.pageSizes = pageSizes;
      this.currentPageSize = defaultPageSize;
  }

  public static <T> PaginatedTableDecorator<T> decorate(JTable table,
                                                        PaginationDataProvider<T> dataProvider,
                                                        int[] pageSizes, int defaultPageSize) {
      PaginatedTableDecorator<T> decorator = new PaginatedTableDecorator<>(table, dataProvider,
              pageSizes, defaultPageSize);
      decorator.init();
      return decorator;
  }

  public JPanel getContentPanel() {
      return contentPanel;
  }

  private void init() {
      initDataModel();
      initPaginationComponents();
      initListeners();
      paginate();
  }

  private void initListeners() {
      objectTableModel.addTableModelListener(this::refreshPageButtonPanel);
      if (table.getRowSorter() != null) {
          table.getRowSorter().addRowSorterListener(new RowSorterListener() {
              @Override
              public void sorterChanged(RowSorterEvent e) {
                  if(e.getType()== RowSorterEvent.Type.SORT_ORDER_CHANGED) {
                      currentPage = 1;
                      paginate();
                  }
              }
          });
      }
  }

  private void initDataModel() {
      TableModel model = table.getModel();
      if (!(model instanceof ObjectTableModel)) {
          throw new IllegalArgumentException("TableModel must be a subclass of ObjectTableModel");
      }
      objectTableModel = (ObjectTableModel) model;
  }

  private void initPaginationComponents() {
      contentPanel = new JPanel(new BorderLayout());
      JPanel paginationPanel = createPaginationPanel();
      contentPanel.add(paginationPanel, BorderLayout.NORTH);
      contentPanel.add(new JScrollPane(table));
  }

  private JPanel createPaginationPanel() {
      JPanel paginationPanel = new JPanel();
      pageLinkPanel = new JPanel(new GridLayout(1, MaxPagingCompToShow, 3, 3));
      paginationPanel.add(pageLinkPanel);

      if (pageSizes != null) {
          JComboBox<Integer> pageComboBox = new JComboBox<>(
                  Arrays.stream(pageSizes).boxed()
                        .toArray(Integer[]::new));
          pageComboBox.addActionListener((ActionEvent e) -> {
              //to preserve current rows position
              int currentPageStartRow = ((currentPage - 1) * currentPageSize) + 1;
              currentPageSize = (Integer) pageComboBox.getSelectedItem();
              currentPage = ((currentPageStartRow - 1) / currentPageSize) + 1;
              paginate();
          });
          paginationPanel.add(Box.createHorizontalStrut(15));
          paginationPanel.add(new JLabel("Page Size: "));
          paginationPanel.add(pageComboBox);
          pageComboBox.setSelectedItem(currentPageSize);
      }
      return paginationPanel;
  }

  private void refreshPageButtonPanel(TableModelEvent tme) {
      pageLinkPanel.removeAll();
      int totalRows = dataProvider.getTotalRowCount();
      int pages = (int) Math.ceil((double) totalRows / currentPageSize);
      ButtonGroup buttonGroup = new ButtonGroup();
      if (pages > MaxPagingCompToShow) {
          addPageButton(pageLinkPanel, buttonGroup, 1);
          if (currentPage > (pages - ((MaxPagingCompToShow + 1) / 2))) {
              //case: 1 ... n->lastPage
              pageLinkPanel.add(createEllipsesComponent());
              addPageButtonRange(pageLinkPanel, buttonGroup, pages - MaxPagingCompToShow + 3, pages);
          } else if (currentPage <= (MaxPagingCompToShow + 1) / 2) {
              //case: 1->n ...lastPage
              addPageButtonRange(pageLinkPanel, buttonGroup, 2, MaxPagingCompToShow - 2);
              pageLinkPanel.add(createEllipsesComponent());
              addPageButton(pageLinkPanel, buttonGroup, pages);
          } else {//case: 1 .. x->n .. lastPage
              pageLinkPanel.add(createEllipsesComponent());//first ellipses
              //currentPage is approx mid point among total max-4 center links
              int start = currentPage - (MaxPagingCompToShow - 4) / 2;
              int end = start + MaxPagingCompToShow - 5;
              addPageButtonRange(pageLinkPanel, buttonGroup, start, end);
              pageLinkPanel.add(createEllipsesComponent());//last ellipsis
              addPageButton(pageLinkPanel, buttonGroup, pages);//last page link
          }
      } else {
          addPageButtonRange(pageLinkPanel, buttonGroup, 1, pages);
      }
      pageLinkPanel.getParent().validate();
      pageLinkPanel.getParent().repaint();
  }

  private Component createEllipsesComponent() {
      return new JLabel(Ellipses, SwingConstants.CENTER);
  }

  private void addPageButtonRange(JPanel parentPanel, ButtonGroup buttonGroup, int start, int end) {
      for (; start <= end; start++) {
          addPageButton(parentPanel, buttonGroup, start);
      }
  }

  private void addPageButton(JPanel parentPanel, ButtonGroup buttonGroup, int pageNumber) {
      JToggleButton toggleButton = new JToggleButton(Integer.toString(pageNumber));
      toggleButton.setMargin(new Insets(1, 3, 1, 3));
      buttonGroup.add(toggleButton);
      parentPanel.add(toggleButton);
      if (pageNumber == currentPage) {
          toggleButton.setSelected(true);
      }
      toggleButton.addActionListener(ae -> {
          currentPage = Integer.parseInt(ae.getActionCommand());
          paginate();
      });
  }

  private void paginate() {
      int startIndex = (currentPage - 1) * currentPageSize;
      int endIndex = startIndex + currentPageSize;
      if (endIndex > dataProvider.getTotalRowCount()) {
          endIndex = dataProvider.getTotalRowCount();
      }

      int sortColumn = -1;
      boolean sortDescending = false;
      RowSorter<? extends TableModel> rowSorter = table.getRowSorter();
      if (rowSorter != null) {
          for (RowSorter.SortKey sortKey : rowSorter.getSortKeys()) {
              if (sortKey.getSortOrder() != SortOrder.UNSORTED) {
                  sortColumn = sortKey.getColumn();
                  sortDescending = sortKey.getSortOrder() == SortOrder.DESCENDING;
                  break;

              }
          }
      }
      List<T> rows = dataProvider.getRows(startIndex, endIndex, sortColumn, sortDescending);
      objectTableModel.setObjectRows(rows);
      objectTableModel.fireTableDataChanged();
  }
}

Output

Example Project

Dependencies and Technologies Used :

  • h2 1.4.196: H2 Database Engine.
  • hibernate-core 5.2.12.Final: The core O/RM functionality as provided by Hibernate.
  • datafactory 0.8: Library to generate data for testing.
  • JDK 1.8
  • Maven 3.3.9

JTable Pagination with Sorting Example Select All Download
  • table-pagination-with-sorting
    • src
      • main
        • java
          • com
            • logicbig
              • example

See Also