MigLayout and JTables in JScrollPanes on tabs

I present the following implemented component in the spirit of bestowal. The component meets my customer requirements, so I do not ask for help as such. But the component provides interesting features that may be useful to others.

In addition, there are some questions about clearly needed hacks, and it is possible that my implementation is terribly more complicated than necessary. Any proposed alternative solutions may be useful to others.

In a nutshell, a large data table is divided into several JTables, one on each page - a multitask JTabPane.

If you run the program and resize it differently, you will see how this component should work. Obviously, the requirements are:

  • A tabular data set with a different number of records (from 1 to 500) should be shown.

  • JTabbedPane contains one or more tabs, each of which contains only JScrollPane.

  • JScrollPanes cannot have vertical scrollbars, can have horizontal scrollbars.

  • Each JScrollPane contains a JTable.

  • JTabbedPane always has enough tabs to collectively store all rows of data in their JTables.

  • The size of the JTabbedPane depends on the size of the JFrame application.

  • The application can be freely modified by drag and drop.

  • During initialization and when resizing the application, JTabbedPane is rebuilt with just tabs to store all records. For instance. for 100 entries, if at a certain panel size 8 entries can be stored in the tabulation table, then 13 tabs are created.

  • JTabbedPane , , , , ( ).

Java, MigLayout :

  • :

    tb = tabbedPane.getBoundsAt(0);

. , ; ( -) .

  • ScrollPane.getViewportBorderBounds() , . , , ?

  • - paint(), repaint(), validate(), invalidate(), revalidate(), update(). , . , , . , AWT, Swing . MigLayout, , .

  • Java, , , ? , ?

make: javac -classpath ScrollTableTest.java

: java -classpath ScrollTableTest [ ]

import java.awt.*;
import java.awt.event.*;
import java.net.*;
import java.util.*;
import java.io.*;
import java.text.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.filechooser.*;
import javax.swing.table.*;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;
import javax.swing.table.TableColumn;
import net.miginfocom.swing.MigLayout;

public class ScrollTableTest
  extends    JFrame
{
  public final static int APPWIDTH = 500; 
  public final static int APPHEIGHT = 300;
  public final static String[] CLIENT_COL_NAMES = { "Col 1", "Col 2", "Col 3", "Col 4" };
  public final static int COLS = CLIENT_COL_NAMES.length; 
  public final static int MAXTABS = 50; // arbitrary limit
  public final static int arbitraryTweek1 = 20;    

  String migDebugString = "";

  int[] dataRowsPerTabCount = new int [MAXTABS];
  JPanel topPane = null;
  DefaultTableModel clientsTableModel;
  String[][] clientData;
  JScrollPane scrollPane;
  Rectangle viewportBounds;
  JTable clientsTable;
  JTabbedPane tabbedPane;
  int dataRows, maxVisibleRow = -1;
  int rowsToShow = 1;
  int dataRowHeight;

  void printBasics()
  {
    if (scrollPane == null)
      return;
    System.out.println("");
    System.out.println("clientsTable height " + clientsTable.getHeight());
    System.out.println("topPane height:  " + topPane.getHeight());
    System.out.println("tabbedPane height " + tabbedPane.getHeight());
    System.out.println("scrollPane height:  " + scrollPane.getHeight());
    System.out.println("viewport bounds:  y " + viewportBounds.getY() +
          " height " + (int)viewportBounds.getHeight());
  }
  void printDims()
  {
    printBasics();
    double diff = viewportBounds.getHeight() - clientsTable.getHeight();
    System.out.println("dataRowHeight: " + dataRowHeight);
    System.out.println("differential:  " + diff);
  }
  void getGuiMetrics()
  {
    double diff;
    Rectangle tb;
    int clientRows = 20;
    int viewable = 0;
    int bottom;
    int computedSpHeight;
    int tabIx;
    boolean scrollbarHeightSet = false;
    int scrollbarHeight = 0;
    String title;
    topPane = new JPanel(new MigLayout("fill" + migDebugString, "[100%]", "[100%]"));
    setContentPane(topPane);
    validate();

    tabbedPane = new JTabbedPane();
    topPane.add(tabbedPane, "cell 0 0, grow");

    // create a temporary table of nominal size to use for table metrics
    clientData = new String[clientRows][COLS];
    clientsTableModel = new DefaultTableModel(clientData, CLIENT_COL_NAMES);
    clientsTable = new JTable(clientRows, COLS);
    clientsTable.setModel(clientsTableModel);
    clientsTable.setPreferredScrollableViewportSize(null);
    clientsTable.getTableHeader().setReorderingAllowed(false);
    clientsTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    clientsTable.getSelectionModel().setSelectionInterval(0, 0);

    // created scroll pane containing table, and contained in tabbed pane
    scrollPane = new JScrollPane(clientsTable);
    scrollPane.setVerticalScrollBarPolicy(scrollPane.VERTICAL_SCROLLBAR_NEVER);
    scrollPane.setHorizontalScrollBarPolicy(scrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
    tabbedPane.setMaximumSize(new Dimension(topPane.getWidth(),  topPane.getHeight() - arbitraryTweek1));

    // For the entire allowed range of tabbed pages, calculate the area
    // within the tabbed pane available to hold a table.
    for (tabIx = 0; tabIx < MAXTABS; ++tabIx)
    {
      JPanel panel = new JPanel(new MigLayout("fill" + migDebugString, "[100%]", "[100%]"));
      title = "Page " + (tabIx +1);
      tabbedPane.addTab(title, panel);
      panel.add(scrollPane, "cell 0 0, grow");
      if (tabIx == 0)
      {
        validate();
        dataRowHeight = clientsTable.getHeight() / clientRows;
      }
      else
        tabbedPane.revalidate();

      // we need to know how high the hz scrollbar is 
      if (!scrollbarHeightSet)
      {
        JScrollBar hzScrollBar = scrollPane.getHorizontalScrollBar();
        if (hzScrollBar != null)
          scrollbarHeight = hzScrollBar.getHeight();
        else
          scrollbarHeight = 0;
        scrollbarHeightSet = true;
      }
      // pick one
      boolean useViewport = false;
      boolean compViewport = false;
      boolean compViewport2 = true; // this one works best.

      // this presumptively correct method barely works
      if (useViewport)
      {
        viewportBounds = scrollPane.getViewportBorderBounds();
        viewable = ((int)viewportBounds.getHeight()) / dataRowHeight;
      }
      // this hack works better
      if (compViewport) 
      {
        tb = tabbedPane.getBoundsAt(0);
        bottom = (int)(tb.getY() + tb.getHeight());
        computedSpHeight = tabbedPane.getHeight() - (dataRowHeight + bottom);
        viewable = (computedSpHeight - scrollbarHeight) / dataRowHeight;
      }
      // this works well.  But what does JTabbedPane.getBoundsAt() have to do with it?
      if (compViewport2)
      {
        tb = tabbedPane.getBoundsAt(0); // !!! Worse Than Failure - this must be here!
        viewable = (scrollPane.getHeight() - scrollbarHeight) / dataRowHeight;
      }
      if (viewable > 0)
        viewable -= 1; // take out the title row

      dataRowsPerTabCount[tabIx] = viewable;
    }      
  } // getGuiMetrics

  void updateTable()
  {
    int tabIx, numTabs, rowsPerTab = 0, maxDisplayableRows = 0, rowsAdded, rowsThisTime;
    boolean accepted = false;

    getGuiMetrics();

    topPane = new JPanel(new MigLayout("fill" + migDebugString, "[100%]", "[100%]"));
    setContentPane(topPane);

    // how many tabs are needed to display all the data rows?
    for (tabIx = 0; !accepted && tabIx < MAXTABS; ++tabIx)
    {
      rowsPerTab = dataRowsPerTabCount[tabIx];
      maxDisplayableRows = rowsPerTab * (tabIx +1);
      if (maxDisplayableRows >= dataRows)
      {
        accepted = true;
        numTabs = tabIx +1;
      }
    }
    // did we find a best fit solution?
    if (!accepted)
    {
      topPane.add(new JLabel("Not enough space for all data rows"));
      return;
    }
    tabbedPane = new JTabbedPane();
    validate();
    tabbedPane.setMaximumSize(new Dimension(topPane.getWidth(),  topPane.getHeight() - arbitraryTweek1));
    topPane.add(tabbedPane, "cell 0 0, grow");

    // create and fill the tab pages
    for (tabIx = 0, rowsAdded = 0; rowsAdded < dataRows; ++tabIx)
    {
      if (rowsAdded + rowsPerTab > dataRows)
        rowsThisTime = dataRows - rowsAdded;
      else
        rowsThisTime = rowsPerTab;

      // create the table for the page
      clientData = new String[rowsThisTime][COLS];
      clientsTableModel = new DefaultTableModel(clientData, CLIENT_COL_NAMES);
      clientsTable = new JTable(rowsThisTime, COLS);
      clientsTable.setModel(clientsTableModel);
      clientsTable.setPreferredScrollableViewportSize(null);
      clientsTable.getTableHeader().setReorderingAllowed(false);
      clientsTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
      clientsTable.getSelectionModel().setSelectionInterval(0, 0);

      // fill the table with test data
      for (int row = 0; row < rowsThisTime; ++row)
      {
        for (int col = 0; col < COLS; ++col)
        {
          String cellVal = "tab " + (tabIx +1) + " cell row " + (row+1) + " col " + (col+1);
          clientsTableModel.setValueAt(cellVal, row, col);
        }
      }
      // create scroll pane holding table
      scrollPane = new JScrollPane(clientsTable);
      scrollPane.setVerticalScrollBarPolicy(scrollPane.VERTICAL_SCROLLBAR_NEVER);
      scrollPane.setHorizontalScrollBarPolicy(scrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);

      // create tab panel holding the scroll pane
      JPanel panel = new JPanel(new MigLayout("fill" + migDebugString, "[100%]", "[100%]"));
      String title = "Page " + (tabIx +1);
      tabbedPane.addTab(title, panel);
      panel.add(scrollPane, "cell 0 0, grow");

      rowsAdded += rowsPerTab;
    }
    tabbedPane.revalidate();
  } // updateTable

  void init(String[] args)
  {
    //  uncomment this to see the migLayout component border highlighting
    //  migDebugString = ", debug";

    // total of how many data rows?
    if (args.length < 1)
    {
      dataRows = 20;
    } 
    else
    {
      dataRows = Integer.valueOf(args[0]);
      if (dataRows <= 0)
      {
        System.out.println("bad arg");
        System.exit(0);
      } 
    }
    setSize(APPWIDTH, APPHEIGHT);
    addComponentListener(new ComponentAdapter()
    {
      public void componentShown(ComponentEvent evt)
      {
      }
      public void componentHidden(ComponentEvent evt)
      {
      }
      public void componentResized(ComponentEvent evt)
      {
        updateTable();
      } // componentResized()
    }); // addComponentListener

    topPane = new JPanel(new MigLayout("fill" + migDebugString, "[100%]", "[100%]"));
    setContentPane(topPane);

    // center app window
    GraphicsConfiguration gc = getGraphicsConfiguration();
    Rectangle bounds = gc.getBounds();
    setLocation((int)((bounds.width-APPWIDTH) /2),
                           (int)((bounds.height - APPHEIGHT) /2));
    setVisible(true);
  }
  public static void main(String[] args)
  {
    try
    {
      ScrollTableTest thisTest = new ScrollTableTest();
      thisTest.init(args);
    }
    catch (Exception e)
    {
      System.out.println("runTest caught exception:  " + e.getMessage());
      e.printStackTrace();
    }
  }
} // class test
+3
source share

All Articles