16.2 Large Tables with Paging


Working conveniently with very large tables can be a pain. Scrolling up and down is fine as long as the table is only a few hundred lines long, but when it gets larger, a tiny movement in the scrollbar can change your position by a few thousand rows. One way to solve this is by combining paging with scrolling. We'll create a table with 10,000 rows (large enough to make scrolling through the entire table a hassle) and add buttons to page up and down 100 rows at a time. Within any group of 100 rows, you can use the scrollbar as usual to move around. Figure 16-2 shows the result.

Figure 16-2. A paging (and scrolling) table
figs/swng2.1602.gif

In this example, we're using a simple trick. There are really two tables to worry about: a logical table that contains all 10,000 rows, which might represent records that were read from a database, and the physical table that's instantiated as a JTable object and displayed on the screen. To do this trick, we implement a new table model, PagingModel, which is a subclass of AbstractTableModel. This table model keeps track of the data for the entire logical table: all 10,000 rows. However, when a JTable asks it for data to display, it pretends it knows only about the 100 rows that should be on the screen at this time. It's actually quite simple. (And we don't even need to worry about any column models; the default column model is adequate.)

Here's the PagingModel code used to track the 10,000 records:

// PagingModel.java // A larger table model that performs "paging" of its data. This model reports a // small number of rows (e.g., 100 or so) as a "page" of data. You can switch pages // to view all of the rows as needed using the pageDown( ) and pageUp( ) methods. // Presumably, access to the other pages of data is dictated by other GUI elements // such as up/down buttons, or maybe a text field that allows you to enter the page // number you want to display. // import javax.swing.table.*; import javax.swing.*; import java.awt.event.*; import java.awt.*; public class PagingModel extends AbstractTableModel {   protected int pageSize;   protected int pageOffset;   protected Record[] data;   public PagingModel( ) {     this(10000, 100);   }   public PagingModel(int numRows, int size) {     data = new Record[numRows];     pageSize = size;          // Fill our table with random data (from the Record( ) constructor).     for (int i=0; i < data.length; i++) {       data[i] = new Record( );     }   }   // Return values appropriate for the visible table part.   public int getRowCount( ) { return Math.min(pageSize, data.length); }   public int getColumnCount( ) { return Record.getColumnCount( ); }   // Work only on the visible part of the table.   public Object getValueAt(int row, int col) {     int realRow = row + (pageOffset * pageSize);     return data[realRow].getValueAt(col);   }   public String getColumnName(int col) {     return Record.getColumnName(col);   }   // Use this method to figure out which page you are on.   public int getPageOffset( ) { return pageOffset; }   public int getPageCount( ) {      return (int)Math.ceil((double)data.length / pageSize);   }   // Use this method if you want to know how big the real table is. You could also   // write "getRealValueAt( )" if needed.   public int getRealRowCount( ) {     return data.length;   }   public int getPageSize( ) { return pageSize; }   public void setPageSize(int s) {      if (s == pageSize) { return; }     int oldPageSize = pageSize;     pageSize = s;     pageOffset=(oldPageSize * pageOffset) / pageSize;     fireTableDataChanged( );   }   // Update the page offset and fire a data changed event (all rows).   public void pageDown( ) {     if (pageOffset < getPageCount( ) - 1) {       pageOffset++;       fireTableDataChanged( );     }   }   // Update the page offset and fire a data changed (all rows).   public void pageUp( ) {     if (pageOffset > 0) {       pageOffset--;       fireTableDataChanged( );     }   }   // We provide our own version of a scrollpane that includes   // the Page Up and Page Down buttons by default.   public static JScrollPane createPagingScrollPaneForTable(JTable jt) {     JScrollPane jsp = new JScrollPane(jt);     TableModel tmodel = jt.getModel( );     // Don't choke if this is called on a regular table . . .      if (! (tmodel instanceof PagingModel)) {       return jsp;     }     // Go ahead and build the real scrollpane.     final PagingModel model = (PagingModel)tmodel;     final JButton upButton = new JButton(new ArrowIcon(ArrowIcon.UP));     upButton.setEnabled(false);  // Starts off at 0, so can't go up     final JButton downButton = new JButton(new ArrowIcon(ArrowIcon.DOWN));     if (model.getPageCount( ) <= 1) {       downButton.setEnabled(false);  // One page...can't scroll down     }     upButton.addActionListener(new ActionListener( ) {       public void actionPerformed(ActionEvent ae) {         model.pageUp( );         // If we hit the top of the data, disable the Page Up button.         if (model.getPageOffset( ) == 0) {           upButton.setEnabled(false);         }         downButton.setEnabled(true);       }     } );     downButton.addActionListener(new ActionListener( ) {       public void actionPerformed(ActionEvent ae) {         model.pageDown( );         // If we hit the bottom of the data, disable the Page Down button.         if (model.getPageOffset( ) == (model.getPageCount( ) - 1)) {           downButton.setEnabled(false);         }         upButton.setEnabled(true);       }     } );     // Turn on the scrollbars; otherwise, we won't get our corners.     jsp.setVerticalScrollBarPolicy         (ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);     jsp.setHorizontalScrollBarPolicy         (ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);     // Add in the corners (page up/down).     jsp.setCorner(ScrollPaneConstants.UPPER_RIGHT_CORNER, upButton);     jsp.setCorner(ScrollPaneConstants.LOWER_RIGHT_CORNER, downButton);     return jsp;   } } 

The PagingModel constructor fills an array with all our data. (The Record object does something simple to generate meaningless data; in real life, we could be getting data from a database, J2EE call, XML-RPC, or any number of live sources.) Most of the methods in the PagingModel are fairly self-explanatory. The interesting ones have something to do with the table's rows. getRowCount( ) isn't computationally complex, but is typical of what we'll see: if the table is too big for one page, it returns pageSize, which is the number of rows we want to display. getValueAt( ) is only slightly more complex: it translates the desired row from the physical table into the actual row within the much larger logical table and returns the appropriate data. (We don't need to do anything to the column, but we would if we were making a table that paged in two directions.) We've added some convenience methods getPageOffset( ), getPageCount( ), and getRealRowCount( ) to provide information about the table's actual size and the portion we're looking at.

The pageDown( ) and pageUp( ) methods are a bit more interesting. These are called when the user clicks on either of the paging buttons displayed with the table. When the user pages, these methods increment or decrement the model's pageOffset variable, which records the current offset into the table. This effectively means that the data in the physical table (the table we display) has changed, although nothing has changed in the logical table at all. Because the physical table has changed, we call fireTableDataChanged( ), which fires a TableModelEvent that tells the JTable to reload all the data. When it's created, the JTable registers itself as a listener for table model events.

Our table doesn't let you change the page increment, but that would be a useful feature, so we provide a setPageSize( ) method. This method is interesting because, again, changing the page size does nothing to the logical table, but it effectively adds or deletes rows from the physical JTable on the screen.

The last important task that our table model has to perform is to build a JScrollPane that knows how to work properly with our table. This is implemented in the createPagingScrollPaneForTable( ) method. This method starts by getting a JScrollPane and then modifying it to work appropriately. The modifications are really quite simple. We create a pair of buttons to control the paging (the icons for the buttons are implemented by the rather simple ArrowIcon class, which we haven't shown); we wire the buttons to the pageUp( ) and pageDown( ) methods of our table model; and we include some logic to disable buttons when we reach the top or bottom of the table. Finally, we turn on the scrollpane's scrollbars and add the buttons in the upper- and lower-right corners. Remember that if the scrollbars aren't enabled, there won't be any place to put the buttons.

Our table is currently static: it displays but cannot update data. How would you implement table updates? We'll leave this as a thought experiment. There's some simple bookkeeping that you'd have to do, but the most interesting part would be implementing setValueAt( ) in the PagingModel class. Like getValueAt( ), it would have to translate between logical rows in the data and physical rows in the JTable. It would have to call fireTableDataChanged( ) to generate a table model event and cause the JTable to update the display. But you would also need a way to set the value of the cells that are not visible. For real applications, you might consider writing your own getRealValueAt( ) and setRealValueAt( ) that do not map incoming row values.

Here's the very simple Record class; it just provides column names and generates meaningless data, one record (row) at a time:

// Record.java // A simple data structure for use with the PagingModel demo // public class Record {   static String[] headers = { "Record Number", "Batch Number", "Reserved" };   static int counter;   String[] data;   public Record( ) {     data = new String[] { "" + (counter++), "" + System.currentTimeMillis( ),              "Reserved" };   }   public String getValueAt(int i) { return data[i]; }   public static String getColumnName(int i) { return headers[i]; }   public static int getColumnCount( ) { return headers.length; } }

Here's the application that brings up the JTable using our paging model:

// PagingTester.java // import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.table.*; public class PagingTester extends JFrame {   public PagingTester( ) {     super("Paged JTable Test");     setSize(300, 200);     setDefaultCloseOperation(EXIT_ON_CLOSE);          PagingModel pm = new PagingModel( );     JTable jt = new JTable(pm);     // Use our own custom scrollpane.     JScrollPane jsp = PagingModel.createPagingScrollPaneForTable(jt);     getContentPane( ).add(jsp, BorderLayout.CENTER);   }   public static void main(String args[]) {     PagingTester pt = new PagingTester( );     pt.setVisible(true);   } }

We just create an instance of our PagingModel to hold the data and construct a JTable with that model. Then we get a paging scrollpane from the JTable and add that scrollpane to the content pane of a JFrame.



Java Swing
Graphic Java 2: Mastering the Jfc, By Geary, 3Rd Edition, Volume 2: Swing
ISBN: 0130796670
EAN: 2147483647
Year: 2001
Pages: 289
Authors: David Geary

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net