2008/03/26

JTable pagination using RowFilter





Code

public class MainPanel extends JPanel {
  private static final Color evenColor = new Color(240, 255, 250);
  private static final LinkViewRadioButtonUI ui = new LinkViewRadioButtonUI();
  private static int LR_PAGE_SIZE = 5;

  private final TestModel model = new TestModel();
  private final TableRowSorter< TestModel > sorter = new TableRowSorter< TestModel >(model);
  private final Box box = Box.createHorizontalBox();
  public MainPanel() {
    super(new BorderLayout());
    JTable table = new JTable(model) {
      @Override
      public Component prepareRenderer(TableCellRenderer tcr, int row, int column) {
        Component c = super.prepareRenderer(tcr, row, column);
        if(isRowSelected(row)) {
          c.setForeground(getSelectionForeground());
          c.setBackground(getSelectionBackground());
        }else{
          c.setForeground(getForeground());
          c.setBackground((row%2==0)?evenColor:getBackground());
        }
        return c;
      }
    };
    table.setFillsViewportHeight(true);
    table.setIntercellSpacing(new Dimension());
    table.setShowHorizontalLines(false);
    table.setShowVerticalLines(false);
    table.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
    table.setRowSorter(sorter);

    for(int i=0;i <= 2008;i++) {
      model.addTest(new Test(" Test: "+i, (i%2==0)?"":"comment..."));
    }
    initLinkBox(100, 1);

    add(box, BorderLayout.NORTH);
    add(new JScrollPane(table));
    setPreferredSize(new Dimension(320, 240));
  }
  private void initLinkBox(final int itemsPerPage, final int currentPageIndex) {
    //assert currentPageIndex > 0;
    sorter.setRowFilter(makeRowFilter(itemsPerPage, currentPageIndex-1));

    ArrayList< JRadioButton > l = new ArrayList< JRadioButton >();

    int startPageIndex = currentPageIndex-LR_PAGE_SIZE;
    if(startPageIndex <= 0) startPageIndex = 1;

//#if 0
    //int maxPageIndex = (model.getRowCount()/itemsPerPage)+1;
//#else
    /* "maxPageIndex" gives one blank page if the module of the division is not zero.
     *   pointed out by erServi
     * e.g. rowCount=100, maxPageIndex=100
     */
    int rowCount = model.getRowCount();
    int maxPageIndex = (rowCount/itemsPerPage) + (rowCount%itemsPerPage==0?0:1);
//#endif
    int endPageIndex = currentPageIndex+LR_PAGE_SIZE-1;
    if(endPageIndex>maxPageIndex) endPageIndex = maxPageIndex;

    if(currentPageIndex > 1)
        l.add(makePNRadioButton(itemsPerPage, currentPageIndex-1, "Prev"));
    //for(int i=startPageIndex;i <= endPageIndex;i++) 
    //    l.add(makeRadioButton(itemsPerPage, currentPageIndex, i-1));
    //if I only have one page, Y don't want to see pagination buttons
    //suggested by erServi
    if(startPageIndex < endPageIndex) {
      for(int i=startPageIndex; i<= endPageIndex;i++) {
          l.add(makeRadioButton(itemsPerPage, currentPageIndex, i-1));
      }
    }
    if(currentPageIndex < maxPageIndex)
        l.add(makePNRadioButton(itemsPerPage, currentPageIndex+1, "Next"));

    box.removeAll();
    ButtonGroup bg = new ButtonGroup();
    box.add(Box.createHorizontalGlue());
    for(JRadioButton r:l) {
      box.add(r); bg.add(r);
    }
    box.add(Box.createHorizontalGlue());
    box.revalidate();
    box.repaint();
    l.clear();
  }

  private JRadioButton makeRadioButton(final int itemsPerPage,
                                       final int current, final int target) {
    JRadioButton radio = new JRadioButton(""+(target+1));
    radio.setForeground(Color.BLUE);
    radio.setUI(ui);
    if(target+1==current) {
      radio.setSelected(true);
      radio.setForeground(Color.BLACK);
    }
    radio.addActionListener(new ActionListener() {
      @Override public void actionPerformed(ActionEvent e) {
        initLinkBox(itemsPerPage, target+1);
      }
    });
    return radio;
  }
  private JRadioButton makePNRadioButton(final int itemsPerPage, final int target, String title) {
    JRadioButton radio = new JRadioButton(title);
    radio.setForeground(Color.BLUE);
    radio.setUI(ui);
    radio.addActionListener(new ActionListener() {
      @Override public void actionPerformed(ActionEvent e) {
        initLinkBox(itemsPerPage, target);
      }
    });
    return radio;
  }

  private RowFilter< TestModel, Integer > makeRowFilter(final int itemsPerPage, final int target) {
    return new RowFilter< TestModel, Integer >() {
      @Override
      public boolean include(Entry< ? extends TestModel, ? extends Integer > entry) {
        int ei = entry.getIdentifier();
        return (target*itemsPerPage <= ei && ei < target*itemsPerPage+itemsPerPage);
      }
    };
  }

  public static void main(String[] args) {
    EventQueue.invokeLater(new Runnable() {
      @Override public void run() {
        createAndShowGUI();
      }
    });
  }

  public static void createAndShowGUI() {
    try{
      UIManager.getInstalledLookAndFeels();
      UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
    }catch(Exception e) {
      e.printStackTrace();
    }
    final JFrame frame = new JFrame("@title@");
    frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    frame.getContentPane().add(new MainPanel());
    frame.pack();
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
  }
}

References

13 comments:

info said...

Hi Atsuhiro,
Very nice Work, thanks a lot.
But i recognised an extreme CPU-usage when using your class on my system.

There seems to be a circuit, which can be prevented by changing 3 lines of code in your paint-Method:
- b.setForeground(Color.BLUE);
- b.setForeground(Color.BLACK);
+ if (b.getForeground()!=Color.BLACK) b.setForeground(Color.BLACK);
and the same for the gray color.

Maybe not elegant but it helped

Thanks again,
Peter

aterai said...

Thanks for your help!
This is clearly an infinite repaint loop bug.

89c89,92
< if(target+1==current) radio.setSelected(true);
---
> if(target+1==current) {
> radio.setSelected(true);
> radio.setForeground(Color.BLACK);
> }
195c198
< b.setForeground(Color.BLUE);
---
> //b.setForeground(Color.BLUE);
197c200
< b.setForeground(Color.GRAY);
---
> //b.setForeground(Color.GRAY);
199c202
< b.setForeground(Color.BLACK);
---
> //b.setForeground(Color.BLACK);

Thanks again for the correction.

vivek said...
This comment has been removed by the author.
ranjeeth said...

LinkViewRadioButtonUI is giving compilation error pls help
paramupk@gmail.com

aterai said...

Hi, ranjeeth.
Hmm, what OS and Java version are you using?
(Works fine for me on Windows XP using Java 1.6.0_22)

ranjeeth said...

Hi,
Nice One.... and thanks for Ur work great
Can anyone explain the code. like Role of table model, Underline in radio-button and working flow

In my case i will be getting an array-list of bean(i.e setters)
So i need to iterate and copy to object[][].So if huge number of record is coming it will be a performance issue ???
And i need to apply pagination so were i have to change ??

Plz do help

aterai said...

I'm not sure of the problem, but how about to use a DefaultTableModel#addRow(Object[]) instead of "copy to object[][]".
BTW, what has become of LinkViewRadioButtonUI compilation error?

ranjeeth said...

I solved Everything.....
But have a problem
When user click on the header whole model should get sorted not the view??
What to do ??
For Example u have data from A - Z your current view contains a-c so sorting will become c -a but i need Z to come first....

aterai said...

One way is to do something like this: TablePaginationTest.java
(But this looks like ugly hack!)

erServi said...

Very good code, thanks!
BTW, there is one or two little improvements:

1. "maxPageIndex" gives one blank page if the module of the division is not zero.

So:

int maxPageIndex = (getTableModel(false).getRowCount() % itemsPerPage) == 0 ? (getTableModel(false).getRowCount() / itemsPerPage) : ((getTableModel(false).getRowCount() / itemsPerPage) + 1);


2. Also, if I only have one page, Y don't want to see pagination buttons, so:

if(startPageIndex < endPageIndex)
for(int i = startPageIndex; i <= endPageIndex; i ++) paginationButtons.add(makeRadioButton(itemsPerPage, currentPageIndex, i - 1));




Thank you again!

aterai said...

Perfect! Thanks for the corrections, erServi.

Majid said...

Hi,
I am newbie in Swing, I like this pagination in JTable, my question please how to use it with from a resultset from a jdbc connection from a database or using hibernate ?
thanks, your help is appreciated.

aterai said...

Hi, Majid.
Loading all data at once, or step by step, at any rate, you might be able to use a SwingWorker: TablePaginationLoadingTest.java
private int currentPageIndex = 1;
static class MyBean{
private String name;
private int age;
public MyBean() {
name = "empty";
age = -1;
}
public MyBean(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
private final JButton button = new JButton(new AbstractAction("get") {
@Override public void actionPerformed(ActionEvent e) {
table.setEnabled(false);
button.setEnabled(false);
SwingWorker> worker = new SwingWorker>() {
private int itemsPerPage = 100;
private int max = 3000;
@Override public String doInBackground() {
int current = 0;
// Hibernate: not tested
// SessionFactory factory = new Configuration().configure().buildSessionFactory();
// Session session = factory.openSession();
// Criteria criteria = session.createCriteria(MyBean.class);
// criteria.setMaxResults(itemsPerPage);
while(current result = criteria.list();
// make dummy list
Thread.sleep(2000);
java.util.List result = new java.util.ArrayList(itemsPerPage);
for(int i=0;i> chunks) {
for(java.util.List list : chunks) {
for(MyBean mb: list) {
model.addRow(new Object[] {mb.getName(), mb.getAge()});
}
}
initLinkBox(itemsPerPage, currentPageIndex);
}
@Override public void done() {
String text = null;
if(isCancelled()) {
text = "Cancelled";
}else{
try {
text = get();
}catch(Exception ex) {
ex.printStackTrace();
text = "Exception";
}
}
table.setEnabled(true);
button.setEnabled(true);
}
};
worker.execute();
}
});
//......