As mentioned here and here there is no easy way to determine the required height of a webview, until "RT-25005 Automatic preferred sizing of WebView" is implemented.
Are there any workarounds to this issue? I couldn't find a solution in SO or elsewhere. However since i think this is no uncommon problem, there needs to be a workaround. Any idea?
For Webviews
embeded in a stage
I found the following solution (see here):
webView.getEngine().executeScript(
"window.getComputedStyle(document.body, null).getPropertyValue('height')"
);
or
Double.parseDouble(webView.getEngine().executeScript("document.height").toString())
However this doesn't seem to work for Webviews embedded in a treecell, like here. In this case I always get too big numbers as a result.
Minimal running example (including recommendation of jewelsea
):
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Application;
import javafx.application.Application;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import javafx.util.Callback;
import org.w3c.dom.Document;
public class TableViewSampleHTML extends Application {
private final ObservableList<MyData> data = FXCollections.observableArrayList(new MyData(1L), new MyData(3L), new MyData(2L), new MyData(4L), new MyData(1L));
public static void main(final String[] args) {
launch(args);
}
@Override
public void start(final Stage stage) {
final Scene scene = new Scene(new Group());
TableView<MyData> table = new TableView<>();
table.setPrefHeight(700);
final TableColumn<MyData, Long> nameCol = new TableColumn("So So");
nameCol.setMinWidth(200);
nameCol.setCellValueFactory(new PropertyValueFactory<>("i"));
// Allow to display Textflow in Column
nameCol.setCellFactory(new Callback<TableColumn<MyData, Long>, TableCell<MyData, Long>>() {
@Override
public TableCell<MyData, Long> call(TableColumn<MyData, Long> column) {
return new TableCell<MyData, Long>() {
@Override
protected void updateItem(Long item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText(null);
setGraphic(null);
setStyle("");
} else {
WebView webview = new WebView();
webview.setPrefWidth(700.0);
WebEngine engine = webview.getEngine();
String textHTML = new String(new char[item.intValue()]).replace("\0", " <b> bold </b> normal, ");
// textHTML = "<body>"
// + textHTML + "</body>";
engine.loadContent(textHTML);
setGraphic(webview);
engine.documentProperty().addListener((obj, prev, newv) -> {
String heightText = engine.executeScript(
"window.getComputedStyle(document.body, null).getPropertyValue('height')"
).toString();
System.out.println("heighttext: " + heightText);
webview.setPrefHeight(Double.parseDouble(heightText.replace("px", "")));
this.setPrefHeight(Double.parseDouble(heightText.replace("px", "")));
setGraphic(webview);
});
}
}
};
}
});
table.setItems(data);
table.getColumns().addAll(nameCol);
((Group) scene.getRoot()).getChildren().addAll(table);
stage.setScene(scene);
stage.show();
}
public static class MyData {
private Long i;
public MyData(Long i) {
this.i = i;
}
public Long getI() {
return i;
}
}
}
Now the outout is
heighttext: 581px
heighttext: 581px
However these values seem to be too big. See screeenshot:
Some progress has been made and cell heights are now calculated more realisticly. Kindly see the relevant the adapted code below.
Relevant changens:
webview.setPrefHeight(-1);
before executing the jevascript.Open points:
+ 15.0
to the calculated height. This is a hack. Seems like some additional lenght has to be considered somewhere.Functionality on recalculation after resize of column has is not optimal. Using table.refresh()
causes significant delay in rendering.
public class TableViewSampleHTML extends Application {
private final ObservableList<MyData> data = FXCollections.observableArrayList(new MyData(1L), new MyData(14L), new MyData(2L), new MyData(3L), new MyData(15L));
public static void main(final String[] args) {
launch(args);
}
@Override
public void start(final Stage stage) {
final Scene scene = new Scene(new Group(), 400, 800);
TableView<MyData> table = new TableView<>();
table.setPrefWidth(400);
table.setPrefHeight(800);
final TableColumn<MyData, Long> nameCol = new TableColumn("So So");
final TableColumn<MyData, Long> col2 = new TableColumn("la la");
nameCol.setPrefWidth(200);
col2.setCellValueFactory(new PropertyValueFactory<>("i"));
nameCol.setCellValueFactory(new PropertyValueFactory<>("i"));
nameCol.widthProperty().addListener((ob,oldV,newV) -> {table.refresh();} );
// Allow to display Textflow in Column
nameCol.setCellFactory(new Callback<TableColumn<MyData, Long>, TableCell<MyData, Long>>() {
@Override
public TableCell<MyData, Long> call(TableColumn<MyData, Long> column) {
return new TableCell<MyData, Long>() {
@Override
protected void updateItem(Long item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText(null);
setGraphic(null);
setStyle("");
} else {
WebView webview = new WebView();
WebEngine engine = webview.getEngine();
webview.setPrefHeight(-1); // <- Absolute must at this position (before calling the Javascript)
setGraphic(webview);
String textHTML = new String(new char[item.intValue()]).replace("\0", " <b> bold </b> normal, ");
textHTML = "<body>"
+ textHTML + "</body>";
engine.loadContent(textHTML);
engine.documentProperty().addListener((obj, prev, newv) -> {
String heightText = engine.executeScript( // <- Some modification, which gives moreless the same result than the original
"var body = document.body,"
+ "html = document.documentElement;"
+ "Math.max( body.scrollHeight , body.offsetHeight, "
+ "html.clientHeight, html.scrollHeight , html.offsetHeight );"
).toString();
System.out.println("heighttext: " + heightText);
Double height = Double.parseDouble(heightText.replace("px", "")) + 15.0; // <- Why are this 15.0 required??
webview.setPrefHeight(height);
this.setPrefHeight(height);
});
}
}
};
}
});
table.setItems(data);
table.getColumns().addAll(nameCol);
table.getColumns().addAll(col2);
((Group) scene.getRoot()).getChildren().addAll(table);
stage.setScene(scene);
stage.show();
}
public static class MyData {
private Long i;
public MyData(Long i) {
this.i = i;
}
public Long getI() {
return i;
}
}
}
Output now looks like:
From the example you linked (JavaFX webview, get document height) the height of the document is computed in a ChangeListener
on the document:
engine.documentProperty().addListener((prop, oldDoc, newDoc) -> {
String heightText = engine.executeScript(
"window.getComputedStyle(document.body, null).getPropertyValue('height')"
).toString();
System.out.println("heighttext: " + heightText);
});
Output:
heighttext: 36px
heighttext: 581px
heighttext: 581px
In the code in your question you are not executing the height check based upon a ChangeListener. So you are querying the height of the WebView document before the document has been loaded (which is why it is returning zero for your code).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With