I was having trouble controlling the column width of JavaFX TableView, so a memo of how to deal with it.
I tried to set the last column to automatic control so that the column would be filled to the full width of the table.
ColumnResizePolicy There is a mechanism called ColumnResizePolicy for controlling the column width of JavaFx TableView. This seems to be a mechanism that determines how to control the column width at the time of initial drawing and adjustment of the column width.
Two types are prepared by default
constant | Overview |
---|---|
UNCONSTRAINED_RESIZE_POLICY | No particular control, process column widths exactly as the user moves |
CONSTRAINED_RESIZE_POLICY | Equal column width when initial drawing, and evenly process other columns when expanding and contracting columns |
If you need your own processing other than this, you need to implement it, and in reality it is a Callback class.
public final void setColumnResizePolicy(Callback<ResizeFeatures, Boolean> callback)
In this, it seems that it is realized by changing the width of each column and returning true if it succeeds.
What I personally wanted was automatic control of the width of the last column, which I wanted to achieve: --When initial drawing, when resizing the window, I want to fit the last column to the window width (pane) --I want to fit the last column to the window width (pane) even when double-clicking autofit.
I looked around the JavaFx TableView and looked for something I could do, but it didn't seem to be the default, so I gave up and implemented it. I managed to get the desired behavior. I haven't considered it in detail, but it doesn't seem to be a problem for checking the operation.
public class ResizePolicy {
public static <S> void setLastColumnResizePolicy(TableView<S> tableView) {
//Set resizing policy
tableView.setColumnResizePolicy(prop -> {
if (prop.getTable().getWidth() < 0) {
return false;
}
resizeTargetColumn(prop);
resizeLastColumn(prop);
return true;
});
//Add resizing process after autofit when double-clicking
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;
//Set the final width considering the maximum width and minimum width
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.If you do not set 0, a scroll bar will appear. 18.0 is just decided by looking at the screen
var tableContentWidth = table.getWidth() - 18.0;
//Calculate the width of the last column from the total value other than the last column and the width of the table
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) {
//Since skin is null when drawing the screen, register what you want to do as an event
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 -> {
//For changing the column width, the square line is the actual event target
//And for double-click autofit
if (isRectangle(mouseEvent) && isDoubleClick(mouseEvent)) {
//Find the target column from the position of the clicked square line and the column width
var rectangle = (Rectangle) mouseEvent.getTarget();
var columnPosition = 0.0;
for (var column : tableView.getColumns()) {
columnPosition += column.getWidth();
if (rectangle.getLayoutX() < columnPosition) {
//If you execute the resizing policy at this timing,
//Since the width has not been changed by autofit, the actual execution will be postponed.
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();
}
}
Actually, I tried to make it as a class instead of static, but I got a warning due to a generic problem and thought that it would be possible to delete it with SuppressWarnings, so I made it a static method. .. ..
ResizeFeatures is a generics class, but the setColumnResizePolicy method handles it raw, so if you do the type declaration yourself, it will not be accepted with a type error. I feel like I get a warning if I don't type.
Why is this happening? .. ..