Correct sizing of Webview embedded in Tabelcell

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("", " <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:

  • Seems like it is mandatory to call webview.setPrefHeight(-1); before executing the jevascript.
  • Javascript has been modified. No big change seen, but maybe the result is more general
  • Open points:

  • For some reason i still have to add + 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("", " <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).


    Basedon BerndGirt's answer.

    public class WebviewCellFactory<S,T> implements Callback<TableColumn<S,T>, TableCell<S,T>> {
        @Override
        public TableCell<S, T> call(TableColumn<S, T> column) {
            return new TableCell<S, T>() {
                @Override
                protected void updateItem(T 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)
                        webview.setBlendMode(BlendMode.DARKEN);
    
                        setGraphic(webview);
                        engine.loadContent("<body topmargin=0 leftmargin=0 style="background-color: transparent;">"+item+"</body>");
                        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();
                            Double height = Double.parseDouble(heightText.replace("px", "")) + 10 ;  // <- Why are this 15.0 required??
                            webview.setPrefHeight(height);
                            this.setPrefHeight(height);
                        });
                    }
                }
            };
        }
    }
    

    then you just need to set

    tableColumn.setCellFactory(new WebviewCellFactory());
    

    if there are any mistakes please tell me.

    链接地址: http://www.djcxy.com/p/38828.html

    上一篇: DeployOnBuild不适用于VS2017 MSBuild工具

    下一篇: 正确调整嵌入在Tabelcell中的Webview的大小