我本着回馈的精神提交以下实施的组件。该组件满足了我的客户的要求,所以我本身并不是在寻求帮助。但该组件提供了可能对其他人有用的有趣功能。
此外,它提出了一些关于显然必要的黑客攻击的问题,而且我的实施可能比必要的复杂得多。任何建议的替代解决方案也可能对其他人有用。
简而言之,一个大型数据表被拆分为多个JTable,每个页面中有一个JTTPane。
如果您运行程序并以各种方式调整大小,您将看到该组件应该如何表现。明确地,要求是:
将显示具有不同记录数(1到500)的表格数据集。
JTabbedPane包含一个或多个标签,每个标签只包含一个JScrollPane。
JScrollPanes不能有垂直滚动条,可能有水平滚动条。
每个JScrollPane都包含一个JTable。
JTabbedPane中总是有足够的选项卡来共同包含其JTable中的所有数据行。
JTabbedPane大小因应用程序JFrame大小而异。
可以使用鼠标拖动来自由调整应用程序的大小。
在初始化时,每当应用程序调整大小时,JTabbedPane都会重建,只有足够的标签页来保存所有记录。例如。对于100条记录,如果在特定的面板大小,标签的表可以容纳8条记录,则会创建13个标签。
如果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