Java Swing - JTable Pagination

[Updated: Jan 4, 2018, Created: Jan 4, 2018]

This example shows how to create a pagination facility for JTable.

Main class

public class PaginationExampleMain {
  public static void main(String[] args) {
      JFrame frame = createFrame();
      JTable table = new JTable(createObjectDataModel());
      table.setAutoCreateRowSorter(true);
      PaginationDataProvider<Employee> dataProvider = createDataProvider();
      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 TableModel 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.getPhoneNumber();
                  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 "Phone";
                  case 3:
                      return "Address";
              }
              return null;
          }
      };
  }

  private static PaginationDataProvider<Employee> createDataProvider() {

      final List<Employee> list = new ArrayList<>();
      for (int i = 1; i <= 500; i++) {
          Employee e = new Employee();
          e.setId(i);
          e.setName("name" + i);
          e.setPhoneNumber("phone" + i);
          e.setAddress("address " + i);
          list.add(e);
      }

      return new PaginationDataProvider<Employee>() {
          @Override
          public int getTotalRowCount() {
              return list.size();
          }

          @Override
          public List<Employee> getRows(int startIndex, int endIndex) {
              return list.subList(startIndex, endIndex);
          }
      };
  }

  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;
  }
}

This example uses in memory table data but PaginationDataProvider#getRows() can get pagination row data lazily from backend/database when deeded.

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);
    }

    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();
        }
        List<T> rows = dataProvider.getRows(startIndex, endIndex);
        objectTableModel.setObjectRows(rows);
        objectTableModel.fireTableDataChanged();
    }
}

Output

Example Project

Dependencies and Technologies Used :

  • JDK 1.8
  • Maven 3.3.9

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

See Also