我本着回馈的精神提交了以下实施的组件。该组件满足了我客户的要求,所以我本身并不是在寻求帮助。但是该组件提供了可能对其他人有用的有趣功能。
此外,它提出了一些关于明显必要的黑客攻击的问题,而且我的实现可能比必要的复杂得多。任何建议的替代解决方案也可能对其他人有用。
简而言之,一个大型数据表被拆分为多个 JTable,每个页面中都有一个多选项卡 JTabPane。
如果您运行该程序并以各种方式调整它的大小,您将看到该组件的行为方式。明确地,要求是:
将显示具有不同数量记录(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 来解决需求?我重新发明轮子只是为了得到拖拉机的胎面吗?
制作: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