概要
JavaFXのTableViewの列幅の制御で、困ったのでそれの対応方法のメモ。
最後の列を自動制御にして、テーブル幅いっぱいに列が埋まるようにしてみた。
ColumnResizePolicy
JavaFxのTableViewの列幅の制御については、ColumnResizePolicyという仕組みがある。
これは、初期描画時や列幅の調整時に、どのように列幅の制御をするかを決めている仕組みになるようだ。
デフォルトで用意されているのは2種類
定数 | 概要 |
---|---|
UNCONSTRAINED_RESIZE_POLICY | 特に制御は無く、ユーザーが動かした通りに列幅を処理する |
CONSTRAINED_RESIZE_POLICY | 初期描画時などは均等の列幅、列の伸縮時はそれに合わせて、他の列を均等に処理する |
これ以外で独自の処理が必要な場合は実装する必要があり、実態としてはCallbackクラスとなる。
public final void setColumnResizePolicy(Callback<ResizeFeatures, Boolean> callback)
この中で、各列の幅の変更を行い、成功した場合はtrueを返すことで実現しているようだ。
個人的に欲しかったのは、最後の列の幅の自動制御で、以下を実現したい
- 初期描画時、ウィンドウのリサイズ時は、最後の列をウィンドウ幅(ペイン)に合わせたい
- ダブルクリックのオートフィット時にも、最後の列をウィンドウ幅(ペイン)に合わせたい
JavaFxのTableView周りを覗いてどうにか出来ないか探したが、デフォルトではないようで、あきらめて実装。
何とか目的の動作になった。細かい制御や考慮はしてないけど、動作確認としては問題なさそう。
public class ResizePolicy {
public static <S> void setLastColumnResizePolicy(TableView<S> tableView) {
// リサイズポリシーをセットする
tableView.setColumnResizePolicy(prop -> {
if (prop.getTable().getWidth() < 0) {
return false;
}
resizeTargetColumn(prop);
resizeLastColumn(prop);
return true;
});
// ダブルクリック時のオートフィット後のリサイズ処理を追加する
addResizeBorderDoubleClickedEvent(tableView);
}
private static void resizeTargetColumn(TableView.ResizeFeatures<?> prop) {
var targetColumn = prop.getColumn();
var delta = prop.getDelta();
if (delta != 0 && targetColumn.isResizable()) {
var newWidth = targetColumn.getWidth() + delta;
// 最大幅、最小幅を考慮して、最終的な幅を設定する
if (newWidth > targetColumn.getMaxWidth()) {
targetColumn.setPrefWidth(targetColumn.getMaxWidth());
} else if (newWidth < targetColumn.getMinWidth()) {
targetColumn.setPrefWidth(targetColumn.getMinWidth());
} else {
targetColumn.setPrefWidth(newWidth);
}
}
}
private static void resizeLastColumn(TableView.ResizeFeatures<?> prop) {
var table = prop.getTable();
// -18.0 をしないとスクロールバーが出る。18.0は画面を見て決めただけ
var tableContentWidth = table.getWidth() - 18.0;
// 最後のカラム以外の合計値とテーブルの幅から、最後のカラムの幅を算出する
var columns = table.getVisibleLeafColumns();
var columnsOfExcludedLastColumn = columns.stream().limit(columns.size() - 1);
var widthsOfExcludedLastColumn = columnsOfExcludedLastColumn.map(TableColumnBase::getWidth);
var lastColumnWidth = widthsOfExcludedLastColumn
.reduce(tableContentWidth, (remainingWidth, columnWidth) -> remainingWidth - columnWidth);
if (lastColumnWidth > 0) {
var lastColumn = columns.get(columns.size() - 1);
lastColumn.setPrefWidth(lastColumnWidth);
}
}
private static <S> void addResizeBorderDoubleClickedEvent(TableView<S> tableView) {
// 画面描画時にはskinがnullなので、イベントとしてやりたいことを登録しておく
tableView.skinProperty().addListener((observable, oldValue, newValue) -> {
var tableViewSkin = (TableViewSkin<?>) newValue;
var tableViewSkinNodes = tableViewSkin.getChildren().stream();
var tableHeaderRowNode = tableViewSkinNodes.filter(node -> node instanceof TableHeaderRow).findFirst();
tableHeaderRowNode.ifPresent((node) -> {
var tableHeaderRow = (TableHeaderRow) node;
var tableColumnHeader = (NestedTableColumnHeader) tableHeaderRow.getChildren().get(1);
tableColumnHeader.addEventFilter(MouseEvent.MOUSE_PRESSED, mouseEvent -> {
// カラム幅の変更については、四角形の線が実際のイベント対象になる
// かつダブルクリックのオートフィットを対象とする
if (isRectangle(mouseEvent) && isDoubleClick(mouseEvent)) {
// クリックされた四角形の線の位置と、カラム幅から対象となるカラムを探し出す
var rectangle = (Rectangle) mouseEvent.getTarget();
var columnPosition = 0.0;
for (var column : tableView.getColumns()) {
columnPosition += column.getWidth();
if (rectangle.getLayoutX() < columnPosition) {
// このタイミングでリサイズポリシーを実行すると、
// オートフィットによる幅の変更前なので、実際の実行は後回しにする
Platform.runLater(() -> {
var columnResizePolicy = tableView.getColumnResizePolicy();
columnResizePolicy.call(new TableView.ResizeFeatures<S>(tableView, column, -0.1));
});
break;
}
}
}
});
});
});
}
private static boolean isRectangle(MouseEvent mouseEvent) {
return mouseEvent.getTarget() instanceof Rectangle;
}
private static boolean isDoubleClick(MouseEvent mouseEvent) {
return mouseEvent.getClickCount() == 2 && mouseEvent.isPrimaryButtonDown();
}
}
本当は、staticではなく、クラスとして作ろうとしたが、ジェネリクスの問題で警告が出て、SuppressWarningsで消すのもなんだかなと思って、staticなメソッドにしてしまった。。。
ResizeFeaturesはジェネリクスのクラスなのに、setColumnResizePolicyメソッドでは、生で扱ってるので、自分で型宣言をちゃんとやると、型エラーで受け入れてくれない。
型を付けないと警告が出る感じです。
なんでこうしたんだろうか。。。