恩返しの精神で、次の実装済みコンポーネントを提出します。このコンポーネントはクライアントの要件を満たしているので、私はそれ自体で助けを求めているわけではありません。しかし、このコンポーネントは、他の人に役立つかもしれない興味深い機能を提供します。
また、明らかに必要なハックについていくつかの疑問が生じます。私の実装は必要以上に恐ろしく複雑である可能性があります。提案された代替ソリューションは、他の人にも役立つ可能性があります。
簡単に言えば、データの大きなテーブルが複数の JTables に分割され、各ページに 1 つのマルチタブ JTabPane が含まれます。
プログラムを実行してさまざまな方法でサイズを変更すると、コンポーネントがどのように動作するかがわかります。明示的に、要件は次のとおりです。
さまざまな数のレコード (1 ~ 500) を含む表形式のデータセットが表示されます。
JTabbedPane には 1 つ以上のタブが含まれ、それぞれに JScrollPane のみが含まれます。
JScrollPanes は垂直スクロールバーを持つことはできませんが、水平スクロールバーを持つことはできます。
各 JScrollPane には JTable が含まれています。
JTabbedPane には、すべてのデータ行をまとめて JTables に含めるのに十分なタブが常に存在します。
JTabbedPane のサイズは、アプリケーションの JFrame のサイズによって異なります。
アプリケーションは、マウスでドラッグして自由にサイズを変更できます。
初期化時、およびアプリケーションのサイズが変更されるたびに、すべてのレコードを保持するのに十分なタブ ページで JTabbedPane が再構築されます。たとえば、100 レコードの場合、特定のパネル サイズでタブのテーブルが 8 レコードを保持できる場合、13 個のタブが作成されます。
JTabbedPane のサイズが小さすぎてすべてのデータ行を保持するのに必要なタブの数を保持できない場合、何も表示されません (または警告のみが表示されます)。
Java、MigLayout、およびそれらの相互作用に学術的な関心を持っている人は、次の 4 つの点を検討することをお勧めします。
ソース コードのある時点で、次の関数呼び出しがあります。
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