Issue
I have a program that allows users to take notes. When they take notes, I use a JavaFX HTMLEditor, which allows for tables, lists, etc. I then need to display these notes to the user, and only way I can find to display HTML in JavaFX is WebEngine (unfortunately).
The problem is WebEngine has different behaviours inside containers than labels, and I need the WebView to behave as a label would.
I have simplified my problem with this example. Here is a VBox with 1 component inside it, a Label:
As you can see, the label (red) takes up no more vertical space than necessary inside the VBox container, and wraps text which is exactly the behaviour I want.
If I now add a WebView and then a second label, the result changes to this:
The webview in the middle (white) is now expanding vertically as much as possible, and the labels are no longer wrapping. So if in my actual program I had two WebViews stacked on top of each other, they would both fight for 50% of the available vertical space, instead of only taking up as much space as necessary, which is what I need.
Furthermore, with Label/WebView/Label like in last image, If I shrink the width of the window like so:
There is another problem where the WebView starts to scroll vertically. This is not what I want, as I want it to be just like a label (expand to take up as much space as necessary and no more at all times).
I have tried setting the vgrow property of the webview to both null and Priority.NEVER, but it seems to have no effect.
Here is the complete code. Thanks for your help.
Main.java
package main;
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.web.WebView;
public class Main extends Application {
@Override
public void start(Stage _primaryStage) {
// create the view
VBox vBox = new VBox();
vBox.setPrefHeight(600);
vBox.setPrefWidth(600);
String textString = "";
textString += "It's the end of the world as we know it and I feel fine. ";
textString += textString;
textString += textString;
textString += textString;
textString += textString;
Label testLabel = new Label(textString);
testLabel.getStyleClass().add("label");
testLabel.setWrapText(true);
vBox.getChildren().add(testLabel);
WebView testWebView = new WebView();
testWebView.getStyleClass().add("webview");
testWebView.getEngine().loadContent(textString);
vBox.getChildren().add(testWebView);
VBox.setVgrow(testWebView, Priority.NEVER);
Label testLabel2 = new Label(textString);
testLabel2.getStyleClass().add("label");
testLabel2.setWrapText(true);
vBox.getChildren().add(testLabel2);
Scene scene = new Scene(vBox);
scene.getStylesheets().add("/main/styles.css");
_primaryStage.setTitle("Notes Program");
_primaryStage.setScene(scene);
_primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
styles.css
.label {
-fx-background-color: 'red';
}
.webview {
}
Solution
Currently (at the time this answer was written), no out of the box, easily implemented or adaptable solution to your problem exists in either JavaFX or available 3rd party libraries.
This is not a concrete answer to the question, but a suggestion of approaches which may be tried to solve problems such as this one.
Fitting a WebView to its content
Your current approach to solving the problem could be summarized as: render HTML content in a resizable JavaFX node the behaves similarly to a Label.
For the most direct assistance in trying to achieve that, review the information in answers to the linked question below, it is not re-iterated in detail here.
This question is related to a similar one which provides some information on ways to address sizing WebView to the displayed content of its HTML page, though the information there is currently not definitive and compete for all cases, so it won't immediately solve your problem:
The suggestion there is to use some JavaScript scripting to determine the size of the document, then set re-size constraints on the WebView node to fit the node to the content, dynamically updating the sizing link when the content changes or is re-rendered or the available space for the WebView changes.
FAQ
Answers to additional and clarifying questions which were included in comments.
I was hoping I could accomplish this with JavaFX layouts or stylesheets to avoid scripts.
I think to accomplish your original solution idea of treating a WebView like a label, it will not be possible to avoiding writing some code in both JavaScript and JavaFX.
- The in-built JavaFX layout panes and CSS style sheets will not be sufficient.
- The current WebView doesn’t provide the appropriate support to work as nicely as desired with JavaFX layout systems.
If somebody has already wrotethe code and scripting to create the behavior and skin for a WebViewLabel control, then you could just use that and customize it with just JavaFX APIs similar to other controls, however that doesn’t exist as far as I know.
Is there another solution that could support lists and tables
I don't think RichTextFX has this support (I haven't looked at it in detail, so I don't know for sure).
Markdown will support lists well. It will also support tables, likely mapped to a GridView in a JavaFX node, not a TableView. But, the syntax for creating tables may be hard on users if the Markdown is just edited with a plain text editor without icon widget support. I suggest you search the web for JavaFX based Markdown viewers and see if they support your required functionality adequately.
My concern though is if i have a page with 100 webviews on it, do you see performance issues? User could only intereact with 1 at a time ofcourse, but there would still be 100 of them loaded into memory potentially. If it was Java Swing i could simply load content into labels but I didn't realise JavaFX doesnt have that
There might be performance issue with such a setup or it might perform OK.
You should create a minimal app to study and benchmark the performance of many WebView nodes.
A single WebView is slow to initialize with detectable lag when it is the first one shown in the app, but that might just because to display the first WebView the first time the JavaFX system may need to run a lot of runtime setup and initialization.
I never tried with many WebView nodes simultaneously displayed in a scene. Perhaps the internal implementation will allow good performance for that, but you have to test it to make sure or you might expend a lot of effort and be disappointed in the result.
I would guess maybe 100 WebViews would be an upper limit and would be surprised if performance didn't tend to drop either a lot earlier with less of them or soon thereafter with a few more of them.
JavaFX isn't really setup to simultaneously display 1000's of nodes, and, usually that isn't required anyway, you only need to display the stuff which is currently visible to the user. No user could immediately take in all of the data if there is too much of it and no output device could render it.
This is why there exist virtualized controls like TableView and ListView, which feature reusable cells created in factories to display interactive views into the underlying data rather then rendering display notes for the complete (perhaps 1000s or millions of lines) of underlying data.
If you find yourself in a situation where you want to create 1000s of nodes, usually that can be better solved by using cell factories and virtualization like ListView. Creating a new control with these facilities is an advanced topic and difficult to implement well.
What follows is a list of alternate approaches to the problem from what you outlined in your question. These approaches may not be directly applicable to your application but may be applicable to somebody with a different application who has a similar problem to solve.
Alternate Approach: Do more in HTML and less in JavaFX
Where your multiple WebViews are clustered for rendering output, instead just use a single WebView instance and manage more of the layout via HTML and JavaScript.
There are certain layout tasks (like laying out html) that a web engine is better at than JavaFX. Use the best tool for the job. JavaFX includes both tools (an internal JavaFX rendering engine for rendering the scene graph and an internal Web Engine for rendering html). So you can choose one rendering implementation or the other (or a mixture of the two), whichever best suits the current task you are implementing.
Alternate Approach: Use RichTextFX
For some similar applications RichTextFX may be a preferred solution.
RichTextFX provides a memory-efficient text area for JavaFX that allows the developer to style ranges of text, display custom objects in-line (no more HTMLEditor), and override the default behavior only where necessary without overriding any other part of the behavior.
RichTextFX may not have full support for all of the required features, but can still accomplish many tasks, so investigate its functionality to see if it is a good fit before implementing your own solution.
Alternate Approach: Use Markdown
Under this approach:
- Use an editor to edit the Markdown.
- Use a viewer to view the rendered Markdown.
It is not necessary to provide full Markdown support, only as much as might be very useful for your application users.
There are various JavaFX Markdown editors and viewers available in third party implementations that you can find if you search the web. There are also many HTML based Markdown editors and viewers which you can use from a WebView. So you could choose to integrate or modify one of those, rather than implementing this from scratch.
For a general purpose library or utility it would be best to make these two things custom controls (e.g MarkDownEditor and MarkDownView) which hide their implementation in a JavaFX control skin and expose their public API in a JavaFX control subclass.
Markdown editor
For the editor which creates (not renders) the Markdown, that can be different from the viewer.
The editor implementation, instead of using the built-in JavaFX HTMLEditor, which is a what-you-see-is-what-you-get (wysiwyg) editor the generates HTML output, you would provide either:
- A plain text editor to create the markdown OR
- A wysiwyg editor that generates Markdown output.
For the implementation you could use either:
- 100 percent JavaFX solutions OR
- HTML embedded in WebView Markdown editor solutions (e.g. similar to the editor used in StackOverflow for editing questions and answers).
A JavaFX based editor could be as simple as a TextArea, or more complex with additional formatting support widgets implemented in JavaFX (again similar to the editing toolbar provided in the StackOverflow Markdown editor JavaScript implementation, just implemented in JavaFX rather than HTML).
Markdown viewer
Again for the viewer you could choose either:
- 100 percent JavaFX solutions OR
- HTML embedded in WebView Markdown editor solutions (e.g. similar to the editor used in StackOverflow for editing questions and answers).
Whereas, for the editor, using a HTML markdown editor embedded in a WebView would likely be fine, for the viewer, you would likely be better off with a 100 percent JavaFX solution if you wanted to view many snippets of documents and display and size them like Label controls.
Essentially the JavaFX solution would be similar to the implementation of a new control called say, MarkDownLabel, which would render a formatted label like thing based on MarkDown text input.
If the view needs to render a large document, it could be equipped with scrolling, scaling and panning functionality, but a simple view for small documents would not need this. Also a cell style factory style implementation (similar to ListView) could allow the nodes used to render the view to be updated and repurposed for different content rather than recreated (for efficiency), but that is just a performance optimization (and quite tricky) so may not be necessary.
Answered By - jewelsea
Answer Checked By - Marilyn (JavaFixing Volunteer)